一、单链表的引出
在学习单链表之前,我们先了解数据结构中其他的表。
1.1 线性表:线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储 时通常以数组和链式结构的形式存储。
也就是说,线性表是存放相同特征的数据的有限序列,每一个元素都具有两个值域,一个是存放元素信息的data域,另一个是存放下一个元素的地址的next域。前面的元素存储着下一个元素的next域,下一个元素再存储它的下一个的next域,这样这些元素一一相连就组成了线性表。我们这篇文章学习的单链表也是线性表的一种。
1.2 顺序表: 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表是一种使用数组存取的线性表。它一般可以分类为:
静态顺序表 | 动态顺序表 |
---|
使用一定长度的数组存储 | 使用动态开辟的数组储存 |
静态顺序表一般使用于已知需要存储多少数据的场景。 静态顺序表可以会因为数组长度N给大了,浪费空间,N给小了不够用的问题。 与此相比,动态顺序表可以先初始化一个小的数组长度,用满了在进行扩容,比静态顺序表更加灵活。
1->静态顺序表代码实现
C语言版本
#define N 100
typedef int SLDataType;
typedef int size_t;
typedef struct SeqList
{
SLDataType array[N];
size_t size;
}Seqlist;
Java版本
class SeqList{
public int []elem=new int[100];
public int usedSize;
}
2->动态顺序表代码实现
C语言版本
typedef struct SeqList
{
SLDataType * array;
size_t size;
size_t capicity;
}SepList;
Java版本
public class MyArraylist {
public int []elem;
public int usedSize;
public MyArraylist(){
this.elem=new int[5];
}
}
二、动态顺序表的9种方法(函数)
以下代码用Java语言演示
2.1、 新增元素
public void add(int pos, int data) {
}
假如我们的顺序表中已经有了3个元素,我们还需要在pos位置,继续增加元素。那这时候问题来了,你传参传过去的pos合法吗?顺序表空间够你放吗?数据结构是一门逻辑非常严谨的学科,所以考虑的时候也一定要考虑全面。下面是add方法的实现思路:
0、当前的顺序表有没有满: 如果当前的顺序表满了的话,那当然就放不了元素了,这时候需要扩容,那怎么判断它有没有满呢?详情可以先跳转到目录2.2。那扩容怎么扩容呢?详情可以跳转到目录2.3。
1、pos位置是否合法 : pos位置的大小需要在顺序表的大小范围内,不能超出,假如你pos位置传了一个-2过去,那一定是不行的,数组就没有-2下标啊,也不能传大于数组长度的pos,不然会数组越界。在有一个需要考虑的点如果我要在4下标存储数据可以吗?4下标又没有已经存储的数据,而且也没有数组越界的情况。 答案是不可以!!!回到上文1.2中,提到了顺序表是物理连续的也需要满足逻辑上连续,假如4下标存放了88,但是3下标这时没有元素,逻辑上并不连续,所以不可以隔着空新增元素。
插入元素的时候,一定要有一个唯一的前驱信息
2、不能抹掉原来pos位置的元素:用上图举个例子,你要在elem[1]下标新增元素88,当前1下标存储的是数据2,那要怎么移?总不能直接把数据2覆盖掉,在1下标直接加data吧。正确做法是从顺序表的最后一个位置开始向后挪,把elem[2]的3挪到3下标,再把elem[1]的2挪到2下标,最后再把88添加到pos位置1上面,然后usedSize加1变成4。
上代码~
public void add(int pos, int data) {
if(this.isFull()){
CapacityExpansion();
}
if(pos<0 ||pos>this.usedSize){
System.out.println("pos位置不合法");
return;
}
for (int i = this.usedSize-1; i >=pos ; i--) {
this.elem[i+1]=this.elem[i];
}
this.elem[pos]=data;
this.usedSize++;
}
2.2 、判断当前顺序表是否已满
这个方法实现起来非常容易,满和没满之间的关系就是usedSize和数组elem.length的关系,如果他们相等就是使用的元素个数和当前数组的个数相同就是满了,返回true,如果不想等就是没有满,返回false
上代码~
public boolean isFull(){
if(this.elem.length==this.usedSize){
return true;
}
return false;
}
2.3 、扩容
用Arrays.copyOf方法将原来的数组长度扩大两倍即可。
public void CapacityExpansion(){
if(isFull()){
this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
}
2.4 、判断是否包含某个元素
这个方法实现起来很容易,for循环遍历顺序表即可。如果数组中的某个值和传入的参数相同返回true,要是遍历完了还没找到就返回false。
public boolean contains(int toFind) {
for (int i = 0; i <this.usedSize ; i++) {
if(this.elem[i]==toFind){
return true;
}
}
return false;
}
2.5、 查找某个元素对应的位置
和2.4很类似,只不过需要在找到时返回当前下标i,找不到时返回-1即可。
public int search(int toFind) {
for (int i = 0; i <this.usedSize ; i++) {
if(this.elem[i]==toFind){
return i;
}
}
return -1; }
2.6 、获取 pos 位置的元素
需要先判断pos的合法性,也就是pos需要在[0-usedSize]这个区间里。如果合法直接返回elem[pos]即可。如果不和法丢出一个异常。
可能有小伙伴要问什么要这么麻烦丢异常?而不是返回-1? 解释:-1也有可能是当前数组的元素啊。
public int getPos(int pos) throws UnsupportedOperationException{
if(pos<0 ||pos>=this.usedSize){
throw new UnsupportedOperationException("位置不合法") ;
}
return this.elem[pos];
}
2.7、 获取顺序表长度
顺序表当前长度就是usedSize,直接返回就好
public int size() {
return this.usedSize; }
2.8、 给 pos 位置的元素设为 value
相当于更新pos下标的值,还是要先检查pos的合法性,如果不合法打印提示,如果合法直接修改pos下标的值即可。
public void setPos(int pos, int value) {
if(pos<0 ||pos>=this.usedSize){
System.out.println("pos位置不合法");
return;
}
this.elem[pos]=value;
}
2.9 、删除第一次出现的关键字key
如下图,当前顺序表元素2出现了两次,需要删除第一次出现的2。 实现思路: 1、先找到要删除的关键字的位置,把它记为index,并且在当前下标定义一个i。 2、[i]=[i+1] i++,意思是把i+1的元素赋值给i下标的元素,i再继续往后走。i需要满足 i<usedSize-1。 3、usedSize–因为要删除一个key,所以usedSize需要减一次。 代码实现:
public void remove(int key) {
int index=this.search(key);
if(index==-1){
System.out.println("没有关键字key");
return;
}
for (int i = index; i <this.usedSize-1 ; i++) {
this.elem[i]=this.elem[i+1];
}
this.usedSize--;
}
三、结尾
上面的这些方法就足顺序表的使用了,大家加油哦!!!
|