最近在学习数据结构,对链表的理解也仅仅停留在书本上。现就链表的手写代码记录如下,新手上路避免不了错误的出现,还望批评指正。

单链表带头结点与不带头结点有何区别?

带头结点的优点

  • 链表的第一个结点不存储任何数据,仅仅作为链表存在的标志。
  • 插入/删除第一个​​存储数据​​的结点比较方便。
  • 空表和非空表的操作比较统一。

对​​带头结点​​对链表进行以下的处理

带头结点与不带头结点的操作相类似,只是在考虑是否为空表上的处理略有差异

  • ​插入​​ 头插法、尾插法、中间插入
  • ​删除​​ 清除链表、删除第一个值为str、删除所有值为str
  • ​反转​​ 将链表反转

1-插入

插入的思想:申请一个结点,该结点的指针指向要插入位置的下一个结点。随后插入位置的上一个结点指向该结点(次序不能倒置)。当然尾插法就比较容易,链表的最后一个位置指向此申请的结点即可。

  • 头插法及尾插法的代码如下
  //头插法
public void headadd(String str){
Lnode temp=new Lnode();
temp.data=str;
temp.next=head.next;
head.next=temp;
}
//尾插法
public void endadd(String str){
if(head.next==null){
Lnode temp=new Lnode();
temp.data=str;
head.next=temp;
}
Lnode p=head.next;
while(p.next!=null){
p=p.next;
}
Lnode temp=new Lnode();
temp.data=str;
p.next=temp;
}

头插法不需要判定是否为空,尾插法需要判定是否为空。同时尾插法需要遍历。

  • 中间插入的代码操作如下
//将str1插入到结点(结点的`data` 为str)之前。
public void add(String str1,String str){
Lnode p=head.next;
Lnode q=p;
while(q != null && str .equals(q.data)!=true){//这里最好不要用q.data==str
p = q;
q = q.next;
}
if(q == head.next){
Lnode temp=new Lnode();
temp.data=str1;
temp.next=q;
head.next = temp;
// add(str1,str);//如果将此处与下处打开,则就是将所有的str之前均加一个str1
}
else if(q != null){
Lnode temp=new Lnode();
temp.data=str1;
temp.next=q;
p.next = temp;
// add(str1,str);//如果将此处打开,则就是将所有的str之前均加一个str1
}else{
return;
}
}

其实中间插入与尾插法有类似的地方,就是都需要遍历。

2-删除

删除链表中的某个数据,也需要遍历操作。

  • 下面介绍两种删除操作
//方法一  删除操作
public void delete(String str){
Lnode q = head.next;
Lnode p = q;
while(q != null && str .equals(q.data)!=true){
p = q;
q = q.next;
}
if(q == head.next){
head.next = q.next;
q.next = null;
q = null;
delete(str);
}
else if(q != null){
p.next = q.next;
q.next = null;
q = null;
delete(str);
}else{
return;
}

方法一是递归的遍历操作。假设链表为a->b->c->d->e,我们要删除c,那么必须要找到b才能够进行删除。如果删除所有c,那么就需要从头开始递归操作。工作量大,代码也不好写。那么如何缩短工作量,并且减少代码呢?

  //方法二     删除链表中所有为str的结点
public void delete(String str){
Lnode p=head.next;
Lnode q=p;
while(p!=null){
if(p.data.equals(str)){
p.data=p.next.data;
p.next=p.next.next;
}
else
p=p.next;
}
}

假设链表为a->b->c->d->e,如果我们要删除 c,我们可以先遍历到c,然后将c的下一个结点(d)的值赋给b,然后在用指针指向e。此方法一次遍历即可删除所有c。

  • ​清除链表​
  public void clear(){
Node temp= new Node();
while(head!=null){
temp=head;
head=head.next;
temp=null;

}
}

链表的清除可以用头结点的指针的的指向来进行删除操作。

3-反转
public void reverse(){
Lnode p=head.next;
head.next=null;//如果不指定 head.next为空,则会出现双倍的结点。
while(p!=null){
Lnode q=new Lnode();
q.data=p.data;
p=p.next;
q.next=head.next;
head.next=q;
}
}

带头结点的链表反转,将一个指针指向头结点的后一个结点,并将头结点指向空。后面就是一个头插法的插入操作。

总结

无论是带头结点还是不带头结点,所考虑的核心问题都差不多。
​​​带头结点​​的单链表操作注意一下几个要点。

  • 头插法比较容易些,尾插法比较麻烦需要一次完整的遍历。
  • 删除操作,注意注意遍历结点时要存储前一个结点,或者用第二种删除操作(一次遍历即可)。
  • 插入/删除操作掌握的差不多后,其余的打印、空表、获得单向链表倒数第i个结点、获取单向链表环的长度等方法,基本由这些方法演变出来的。