双向链表的实现(双向链表与单向链表的简单区别联系和实现)
Posted 小海浪.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双向链表的实现(双向链表与单向链表的简单区别联系和实现)相关的知识,希望对你有一定的参考价值。
🎓前言
之前写了一些文章简单地介绍顺序表,链表的基础知识,还总结了几道链表的笔试题,今天继续开干,向老铁们简单地介绍一下无头双向循环链表及其代码的实现。
为什么要引入无头双向循环链表呢?????
我们可以简单地这样去思考,单向链表只能单向地从头节点去访问其他的节点,不能会退的访问其他的节点,也不能循环地访问,与单向链表不同,双向链表在插入删除的时候不需要寻找前驱节点,因为本身就能回到前面一个节点,**查找时,我们可以用二分发的思路,从首节点(head)向后查找操作和last(尾结点)向前查找操作同步进行,这样双链表的查找可以提高一倍,**在实现双向链表的增删改查的时候他们的操作也有不同,我们可以前后对比一下,就能更好地理解他们了。
🎓单向链表和双向链表的优缺点及使用场景
单向链表:只有一个指向下一个节点的指针。 优点:单向链表增加删除节点简单。遍历时候不会死循环;
缺点:只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。 适用于节点的增加删除。双向链表:有两个指针,一个指向前一个节点,一个指向后一个节点。 优点:可以找到前驱和后继,可进可退;
缺点:增加删除节点复杂,需要多分配一个指针存储空间。 适用于需要双向查找节点值的情况
下面是需要注意的地方:
1、引入了前驱域,解决了单链表只能单向访问的痛点。
2、对于双向链表来说,要注意:第一个节点的前驱是null和最后一个节点后继是null
3、引入一个last,标志尾巴节点。
📝双向链表的简单实现及图示分析
双向链表的简单模型如图:
声明两个类一个 ListNode,节点这个类它的成员属性包括节点的数值data,prev前驱域,next后继域,一个 MyLinkedList链表这个类它的成员属性,包括头结点head,尾巴节点last,curNext节点等。
代码如下:
//节点类
class ListNode {
public int data;
public ListNode prev;
public ListNode next;
public ListNode(int data) {
this.data = data;
}
}
//链表这个类
public class MyLinkedList {
public ListNode head;//头结点
public ListNode last;//尾结点
ListNode curNext;//记录下一个节点
}
头插法
基本思路
**新插入的节点是头结点,**如果是第一次插入,之前没有一个节点,新插入的节点就是头节点,如果在头插之前有节点,则使node的next指向头节点,head的prev指向node ,之后新的头结点变为新插入的节点即可。
如图分析:
代码实现:
//头插法
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head == null) {
this.head = node;
this.last = node;//可写可不写
} else {
node.next = head;
this.head.prev = node;
this.head = node;
}
}
尾插法:
插入的节点是尾巴节点,如果是第一插入,插入的节点既是头也是尾,如果不是第一插入,使最后一个节点的next指向node,node的prev指向last节点即可。
如图:
//尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
this.head = node;
this.last = node;
} else {
last.next = node;
node.prev = last;
last = node;
}
}
任意位置插入,第一个数据节点为0号下标:
首先判断插入位置的合法性如果,插入位置index为负或者大于链表的长度,则输入的位置不合法,如果插入的位置等于链表的长度,则为尾插法,插入位置为0位置,则为头插法,中间的位置则正常插入。在插入的时候我们还用到一个查找某一位置的函数 findIndex,通过遍历的方式,寻找插入的位置,返回给插入函数。在插入的时候插入的节点和前后的节点要连起来,连接的顺序不管,但一定要连接对。
图示分析:
代码实现:
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
//判断位置的合法性,任意位置插入,第一个数据节点为0号下标
if (index < 0 || index > size()) {
System.out.println("输入的位置不合法");
return;
} else {
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
} else {
ListNode node = new ListNode(data);
ListNode cur;
cur = findIndex(index);
cur.prev.next = node;//当index==size时,如果不采用尾插法,会出现空指针异常
node.prev = cur.prev;
cur.prev = node;
node.next = cur;
}
}
}
//找到某一位置的节点
//从头节点开始,要到0位置走0步,要到n位置走n步
public ListNode findIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
删除第一次出现关键字为key的节点
大致思路就是,遍历链表寻找要删除的节点,如果找到删除就结束删除了,没找到一直遍历完链表,找到的节点又要分是头结点还是尾结点中间节点,找到头结点还要分是不是只有头结点一个节点还是有多个节点,尾结点不用再分了,中间节点正常删除即可。
下面结合代码和图示再理解下:
//删除第一次出现关键字为key的节点
public void remove(int key) {
ListNode cur = this.head;
while (cur != null) {
//
if (cur.data == key) {
//判断是不是头结点
if (cur == this.head) {
this.head =this.head.next;
//头结点是唯一的一个节点
if (this.head == null) {
this.last = null;
} else {
//头结点不是唯一的节点
this.head.prev = null;
}
} else {
cur.prev.next = cur.next;
//判断是不是最后一个节点
if (cur.next == null) {
this.last = cur.prev;
} else {
cur.next.prev = cur.prev;
}
}
return;
} else {
cur = cur.next;
}
}
}
删除所有值为key的节点
这个思路和上面删除第一次出现的key值几乎一样,只是要删除所有出现的key值,我们只要在判断是否是key值if语句后面加个cur=cur.next,这样如果找到要删除的元素继续向后找了删除,没有找到也继续向后找,直到遍历完链表即可。
//删除所有值为key的节点
public void removeAllKey(int key) {
ListNode cur = head;
while (cur != null) {
//
if (cur.data == key) {
//判断是不是头结点
if (cur == this.head) {
this.head = this.head.next;
//头结点是唯一的一个节点
if (cur.next == null) {
this.last = null;
} else {
//头结点不是唯一的节点
this.head.prev = null;
}
} else {
cur.prev.next = cur.next;
//判断是不是最后一个节点
if (cur.next == null) {
this.last = cur.prev;
} else {
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;继续往后走 不要停 直到为null 的时候
}
}
得到单链表的长度:
这个就很简单,遍历链表节点不为空,计数器count加一,一直遍历完即可。
//得到单链表的长度
public int size() {
ListNode cur =this.head;
int count = 0;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
打印链表
public void display() {
ListNode cur = this.head;
while (cur != null) {
System.out.print(cur.data + " ");
cur = cur.next;
}
System.out.println();
}
查找是否包含关键字key是否在单链表当中
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
/
if(this.head==null) return false;
ListNode cur = this.head;
while (cur != null) {
if (cur.data == key) {
return true;
}
cur=cur.next;
}
//return false;
return false;
}
清空链表
清空链表不能简单的把head置为null,last置为null,要遍历链表把每个节点的前驱后继都置为null,但要注意在把节点的值置为null的时候要提前记录下一个节点的位置。
public void clear() {
ListNode cur = this.head;
while (cur != null) {
curNext = cur.next;
cur.prev = null;
cur.next = null;
cur = curNext;//提前记录下一个节点的位置
}
this.head = null;
this.last = null;
}
完整代码:
MyLinkedList.java
class ListNode {
public int data;
public ListNode prev;
public ListNode next;
public ListNode(int data) {
this.data = data;
}
}
public class MyLinkedList {
public ListNode head;//头结点
public ListNode last;//尾结点
ListNode curNext;//记录下一个节点
//找到某一位置的节点
public ListNode findIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
//头插法
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head == null) {
this.head = node;
this.last = node;//可写可不写
} else {
node.next = head;
this.head.prev = node;
this.head = node;
}
}
//尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
this.head = node;
this.last = node;
} else {
last.next = node;
node.prev = last;
last = node;
}
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
//判断位置的合法性,任意位置插入,第一个数据节点为0号下标
if (index < 0 || index > size()) {
System.out.println("输入的位置不合法");
return;
} else {
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
} else {
ListNode node = new ListNode(data);
ListNode cur;
cur = findIndex(index);
cur.prev.next = node;//当index==size时,如果不采用尾插法,会出现空指针异常
node.prev = cur.prev;
cur.prev = node;
node.next = cur;
}
}
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
//修改错误
if(this.head==null) return false;
ListNode cur = this.head;
while (cur != null) {
if (cur.data == key) {
return true;
}
cur=cur.next;
}
//return false;
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
ListNode cur = this.head;
while (cur != null) {
//
if (cur.data == key) {
//判断是不是头结点
if (cur == this.head) {
this.head =this.head.next;
//头结点是唯一的一个节点
if (cur.next == null) {
this.last = null;
} else {
//头结点不是唯一的节点
this.head.prev = null;
}
} else {
cur.prev.next = cur.next;
//判断是不是最后一个节点
if (cur.next == null) {
this.last = cur.prev;
} else {
cur.next.prev = cur.prev;
}
}
return;
} else {
cur = cur.next;
}
}
}
//删除所有值为key的节点
public void removeAllKey(int key) {
ListNode cur = head;
while (cur != null) {
//
if (cur.data == key) {
//判断是不是头结点
if (cur == this.head) {
this.head = this.head.next;
//头结点是唯一的一个节点
if (cur.next == null) {
this.last = null;
} else {
//头结点不是唯一的节点
this.head.prev = null;
}
} else {
cur.prev.next = cur.next;
//判断是不是最后一个节点
if (cur.next == null) {
this.last = cur.prev;
} else {
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;继续往后走 不要停 直到为null 的时候
}
}
//得到单链表的长度
public int size() {
ListNode cur =this.head;
int count = 0;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
public void display() {
ListNode cur = this.head[单向链表与双向链表的实现]