数据结构:链表
链表的概念及结构:
链表的概念:
链表是一种物理储存结构上非连续的储存结构,数据元素的逻辑顺序是通过引用链接次序实现的
那物理存储结构连续是什么意思?
之前我们讲过顺序表,顺序表的底层是数组,如下:
因为数组开辟的空间,是连续的,这里所储存的数据,它们在物理储存结构上是连续的。
链表的结构:
链表是一个节点链接另一个节点的,一个节点都是一个对象。
如果我用链表储存11,22,33,44,55这几个数据,首先我们有五个节点,因为节点是对象,所以会分配地址。
并把这些数据储存到对应的节点里面,然后再将他们连接起来。
这个链表叫做单向不带头非循环链表。
这是单向带头非循环链表,两个的区别是,带头链表多了一个头(也叫哨兵位),头插一个元素时,带头链表会将新的元素放在头(哨兵位)的后面。带头链表的值没有意义,所以也可以不用放数据。但是不带头链表,头插的时候,头插在第一个元素的前面即可。
链表分类:
这就是循环链表,最后一个节点又指向第一个节点,形成一个圈。
单向不带头非循环链表的实现:
我先创建一个内部类(节点):
static class ListNode(){int val;Listnode next;
//构造方法public ListNode(int val){this.val=val;}
}
//还要有一个头结点
ListNode head=null;
在ListNode类中,val用来存储数据,而next用来指向下一个节点,再写一个构造方法用来初始化val值。
定义一个头节点可以利于遍历链表。
实现链表dispaly(打印链表):
如何打印上面链表?我们要打印出每个节点的val值,并利用每个节点的next找到下一个节点。
一直这样循环,一直到最后一个节点的next值为null停止。
public void dispaly(){ListNode cur=head;while(next!=null){System.out.print(cur.val+" ");cur=cur.next;}
}
这里要注意一点:不要直接拿头结点head去遍历链表,会导致最后head指向链表的尾部,后续操作就不方便了。所以重新定义一个cur节点等于头结点。用cur来遍历数组。
实现链表addFirst(头插):
在实现链表头插时要分情况:
1.如果链表为空:
这种情况,只用定义新的节点,让头结点指向新节点
public void addFirst(int data){
//链表为空的情况if(head==null){ListNode node=new ListNode(data);head=node;}
}
2、如果链表不为空:
这种情况怎么头插呢?
首先:我们还是要第一个新的节点,让这个新的节点的next值指向head头节点,最后将head头节点指向新的头结点。
public void addFirst(int data){
//链表为空的情况if(head==null){ListNode node=new ListNode(data);head=node;
//链表不为空的情况}else{ListNode node=new ListNode(data);node.next=head;head=node;}
}
最后发现,这两种情况可以合并为:
public void addFirst(int data){ListNode node=new ListNode(data);node.next=head;head=node;}
实现链表addLast(尾插):
实现尾插时也要分情况
1.如果链表不为空:
如果对这个链表进行尾插,应该怎么做呢?
首先我们要遍历链表到这个链表的尾部,然后将要插入的节点与尾节点连接,完成尾插。
public void addLast(int data){
//创建一个新的节点ListNode node=new ListNode(data);ListNode cur=head;
//遍历到链表尾部while(cur.next!=null){cur=cur.next;}
//插入尾部cur.next=node;
}
2.如果链表为空:
如果用上面1的方法,会出现空指针异常。
所以如果为空链表,我们就直接将head头节点指向需要插入的节点,加一个if语句进行判断。
public void addLast(int data){ListNode node=new ListNode(data);
//为空链表的情况if(head==null){head=node;}
//不为空链表的情况ListNode cur=head;while(cur.next!=null){cur=cur.next;}cur.next=node;
}
实现链表size(求链表长度):
既然要求链表的长度,并定义一个计数器,遍历链表时,只要当前节点不为null,计数器就加1
注意:这里遍历链表时,我们不用head,重新定义一个节点指向head,然后往后遍历。
public int size(){
int count=0;//计数器
ListNode node=head;
//开始遍历数组while(node!=null){count++;node=node.next;}return count;
}
实现链表addIndex(指定位置下插入数据):
既然要在指定位置下插入数据,我们首先就要考虑所提供的下标是否越界:
所以Index(下标)不能小于0,也不能大于链表的长度。
然后如果Index==0,则直接调用头插的方法,如果index==链表的长度,就直接调用尾插的方法。
这里有一个新节点ListNode node =new ListNode(25),要把新节点25插入到22和33之间,应该怎么做呢?
这里我们重新定义一个cur=head;
将cur遍历到22节点的位置
因为我们要将25这个节点插入2下标的位置,所以要将cur从head移动2-1个位置到22这个位置(这样才方便插入)
node.next=cur.next;
cur.next=node;
public void addIndex(int Index,int data){
int len=size();//求链表的长度
//判断Index下标是否异常 if(Index<0||Index>len){System.out.print("下标异常");return ;}
//特殊处理Index==0或者Index==len
if(Index==0){addFirst(data);}
if(Index==len){addLast(data);}
//插入到链表的中间下标
ListNode cur=head;
ListNode node=new ListNode(data);
//找到插入下标的前一个位置
while(Index-1!=0){cur=cur.next;
Index--;}
//开始插入
node.next=cur.next;
cur.next=node;
}
`实现链表remove(删除指定节点):
给一个链表,我们如果要删除33这个节点,应该怎么删除呢?
首先,如果这个节点为空,我们直接提前返回。
我们如果要删除33这个节点,那我们要重新定义一个节点,让这个节点指向被删除的节点的前一个节点, 也就是22这个节点。怎么找呢?
先有一个大循环,遍历链表,直到链表为空,然后嵌套一个if,找到被删除节点的前一个节点。
//这个函数用来删除节点
public void remove(int key){if(head==null){return ;}
//如果第一个节点是要删除的节点if(head.val==key){head=head.next;}
//第三种情况
ListNode node=findKey(key);//这时node节点就是要删除的节点的前一个节点
node.next=node.next.next;//删除key节点
}
//这个函数用来找到被删除节点的前一个节点public ListNode findKey(int key){ListNode cur=head;
//遍历链表
while(cur!=null){if(cur.next.val==key){return cur;}cur=cur.next;}
return null;
}
实现链表clear(清空链表):
清空链表有两种方法:
1.最简单的方法:将链表的头节点置为空。
public void clear(ListNode head){head=null;
}
2.第二种方法:遍历链表,将链表的所有的节点全部置为空。
public void clear(ListNode head){
ListNode cur=head;while(cur!=null){ListNode curN=cur.next;//curN指向后一个节点cur.next=null;//将当前的cur节点的next域置为nullcur=curN;//将cur移到curN的位置}
}