📚 目录
-
[1. 无头双向非循环链表的介绍](#1. 无头双向非循环链表的介绍)
-
[2. 双向链表属性](#2. 双向链表属性)
-
[3. 模拟实现双向链表](#3. 模拟实现双向链表)
-
[4. 代码汇总](#4. 代码汇总)
1. 无头双向非循环链表的介绍

无头双向非循环链表不像我们单向链表,只有一个方向,走过了就不能够再找回来。
双向链表可以通过当前节点找到后面的节点,也能找到前面的节点,双向链表比单向链表更加灵活。

[🔙 返回目录](#🔙 返回目录)
2. 双向链表属性
无头双向肺循环链表和单链表基本上类似,主要区别就是当前节点能找到前后的节点。
定义一个接口:
java
public interface ILink {
//头插
void addFirst(int val);
//尾插
void addLast(int val);
//任意位置插入
void addIndex(int index,int val);
//获取链表大小
int size();
//清除
void clear();
//删除
void remove(int key);
//删除所以有关这个值
void removeAll(int key);
//查找
boolean contains(int val);
//打印内容
void display();
}
在我们模拟实现双向链表的类中实现:
我们需要在类中定义一个节点:用来连接其他节点。

java
public class MyLinkedList implements ILink{
//节点:
static class LinkNode {
public int val;
public LinkNode prev;
public LinkNode next;
//构造方法
public LinkNode(int val) {
this.val = val;
}
}
//头节点
public LinkNode head;
//尾节点
public LinkNode last;
//---------头插-----------
@Override
public void addFirst(int val) {
}
//---------尾插插-----------
@Override
public void addLast(int val) {
}
//---------任意位置插-----------
@Override
public void addIndex(int index, int val) {
}
//---------求链表大小-----------
@Override
public int size() {
}
//--------------清空链表-------------
@Override
public void clear() {
}
//-------------删除链表----------------
@Override
public void remove(int key) {
}
//----------删除所有在链表中的key值------------
@Override
public void removeAll(int key) {
}
//-----------判断当前值是否在链表中--------
@Override
public boolean contains(int val) {
}
//-----------打印链表-----------
@Override
public void display() {
}
}
以整型链表来实现:
[🔙 返回目录](#🔙 返回目录)
3. 模拟实现双向链表
头插:

** 插入前我们得先判断是否是第一次插入,如果是第一次插入,就让头节点和尾节点都指向当前节点。**
java
public void addFirst(int val) {
//创建节点
LinkNode node = new LinkNode(val);
//首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
if(head == null) {
head = node;
last = node;
return;
}
//此时不是第一次添加数据
node.next = head;
head.prev = node;
head = node;
}
java
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.addFirst(2);
myLinkedList.addFirst(3);
}
此时,我们就完成了头插:

尾插:

注意事项:
** 和头插一样,需要判断是不是第一次插入数据。
直接让last节点的下一个指向要插入的节点。**
java
public void addLast(int val) {
//创建节点
LinkNode node = new LinkNode(val);
//首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
if(head == null) {
head = node;
last = node;
}
//此时不是第一次添加数据
last.next = node;
node.prev = last;
last = node;
}
此时我们需要插入4个节点:来判断我们的尾插是否成功:

java
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(4);
}
}
结果上看:没有任何问题。

任意位置插入:

** 如果是头插,就调用我们的头插的方法,如果是尾插就调用我们尾插的方法。
如果是中间位置插入:
首先让插入节点记住插入位置后面的节点,我们此时是双向链表,不需要找到插入位置前一个节点,只需要找到插入的节点即可。此时我们还要自定义一个异常来帮我们检查插入位置的合法性**
java
public class PosExcep extends RuntimeException{
public PosExcep() {
}
public PosExcep(String message) {
super(message);
}
}
java
public void addIndex(int index, int val) {
//首先判断插入位置的合法性
posLegal(index);
//此时下标是合法的,再判断是不是头插,或者尾插
if(index == 0) {
addFirst(val);
return;
}
if(index==size()) {
addLast(val);
return;
}
//此时既不是头插也不是尾插。
LinkNode node = new LinkNode(val);
//找到当前要插入的位置的那个值
LinkNode cur = getIndexPos(index);
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
专门服务于当前插入的方法:
java
//定义一个专门判断下标是否合理的方法
private void posLegal(int index) {
if(index<0||index>size()) {
throw new PosExcep("下标不合法");
}
}
专门服务于当前插入的方法,获取当前插入位置。
java
private LinkNode getIndexPos(int index) {
LinkNode cur = head;
int count = 0;
while (count<index) {
cur = cur.next;
count++;
}
return cur;
}
Test测试类:
java
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(4);
myLinkedList.addIndex(0,99);
myLinkedList.addIndex(5,100);
myLinkedList.addIndex(3,666);
myLinkedList.display();
}
}
结果:

链表的打印:
此时我们只需要遍历我们的链表:
java
public void display() {
//如果链表为空,则直接返回
if(head==null) {
System.out.println("链表为空");
return;
}
//此时链表不位空,定义一个临时的cur节点来遍历head,防止head被改变。
LinkNode cur = head;
while (cur!=null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
删除

首先得先找到当前要删除的节点,让当前节点的前一个节点指向后一个节点。
当前节点没有被引用系统就会回收掉。
定义cur来遍历链表。
java
//-------------删除链表----------------
@Override
public void remove(int key) {
//判断当前链表是否为空
if(head == null) {
System.out.println("链表为空");
return;
}
LinkNode cur = head;
while (cur!=null) {
if(cur.val == key) {
//判断是不是头节点
if(head == cur) {
head = head.next;
if(head!=null) {
//防止 当前双向链表只有一个节点
head.prev = null;
}
}else {
//此时就不是头节点
cur.prev.next = cur.next;
//尾节点
if(cur.next == null) {
last = last.prev;
}else {
//删除中间节点
cur.next.prev = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
Test测试类:
java
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(4);
myLinkedList.addLast(5);
myLinkedList.remove(1);
myLinkedList.remove(3);
myLinkedList.remove(5);
myLinkedList.display();
}
结果上看,当前删除没有问题。

删除所有等于这个值的节点

定义一个cur节点来遍历链表。
和删除一个节点方法类似:只不过我们将return给去掉了。
java
public void removeAll(int key) {
if(head == null) {
System.out.println("链表为空");
return;
}
LinkNode cur = head;
while (cur!=null) {
if(cur.val == key) {
//判断是不是头节点
if(head == cur) {
head = head.next;
if(head!=null) {
//防止 当前双向链表只有一个节点
head.prev = null;
}
}else {
//此时就不是头节点
cur.prev.next = cur.next;
if(cur.next == null) {
last = last.prev;
}else {
//删除中间节点
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;
}
}
Test测试类:
java
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(2);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(2);
myLinkedList.addLast(5);
myLinkedList.removeAll(2);
myLinkedList.display();
}
}
结果:

判断当前链表是否有这个值
我们只需要遍历一遍这个链表,如果有这个值则返回true反之返回false。
java
//-----------判断当前值是否在链表中--------
@Override
public boolean contains(int val) {
if(head==null) {
return false;
}
//此时链表不为空
LinkNode cur = head;
while (cur!=null) {
if(cur.val == val) {
return true;
}
cur = cur.next;
}
return false;
}
求链表长度
java
//---------求链表大小-----------
@Override
public int size() {
int count = 0;
LinkNode cur = head;
while (cur!=null) {
cur = cur.next;
count++;
}
return count;
}
清空链表

首先还是得判断当前链表是否为null。
然后通过cur遍历链表让每一个节点置为null,最后将头和尾都置为null。
java
public void clear() {
if(head == null) {
System.out.println("链表为空,不需要清空链表");
return;
}
//此时链表不为空。
LinkNode cur = head;
while (cur != null) {
LinkNode next = cur.next;
// 断开当前节点的所有引用
cur.prev = null;
cur.next = null;
cur = next;
}
// 最后把头尾指针置空
head = null;
last = null;
}
Test测试类:
java
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(2);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.clear();
myLinkedList.display();
System.out.println("==========");
}
}
结果:

[🔙 返回目录](#🔙 返回目录)
4. 代码汇总
java
public interface ILink {
//头插
void addFirst(int val);
//尾插
void addLast(int val);
//随便插
void addIndex(int index,int val);
//获取链表大小
int size();
//清除
void clear();
//删除
void remove(int key);
//删除所以有关这个值
void removeAll(int key);
//查找
boolean contains(int val);
//打印内容
void display();
}
java
public class PosExcep extends RuntimeException{
public PosExcep() {
}
public PosExcep(String message) {
super(message);
}
}
java
public class MyLinkedList implements ILink{
static class LinkNode {
public int val;
public LinkNode prev;
public LinkNode next;
//构造方法
public LinkNode(int val) {
this.val = val;
}
}
//头节点
public LinkNode head;
//尾节点
public LinkNode last;
//---------头插-----------
@Override
public void addFirst(int val) {
//创建节点
LinkNode node = new LinkNode(val);
//首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
if(head == null) {
head = node;
last = node;
return;
}
//此时不是第一次添加数据
node.next = head;
head.prev = node;
head = node;
}
//---------尾插插-----------
@Override
public void addLast(int val) {
//创建节点
LinkNode node = new LinkNode(val);
//首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
if(head == null) {
head = node;
last = node;
}
//此时不是第一次添加数据
last.next = node;
node.prev = last;
last = node;
}
//---------任意位置插-----------
@Override
public void addIndex(int index, int val) {
//首先判断插入位置的合法性
posLegal(index);
//此时下标是合法的,再判断是不是头插,或者尾插
if(index == 0) {
addFirst(val);
return;
}
if(index==size()) {
addLast(val);
return;
}
//此时既不是头插也不是尾插。
LinkNode node = new LinkNode(val);
//找到当前要插入的位置的那个值
LinkNode cur = getIndexPos(index);
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
//定义一个专门判断下标是否合理的方法
private void posLegal(int index) {
if(index<0||index>size()) {
throw new PosExcep("下标不合法");
}
}
private LinkNode getIndexPos(int index) {
LinkNode cur = head;
int count = 0;
while (count<index) {
cur = cur.next;
count++;
}
return cur;
}
//---------求链表大小-----------
@Override
public int size() {
int count = 0;
LinkNode cur = head;
while (cur!=null) {
cur = cur.next;
count++;
}
return count;
}
//--------------清空链表-------------
@Override
public void clear() {
if(head == null) {
System.out.println("链表为空,不需要清空链表");
return;
}
//此时链表不为空。
LinkNode cur = head;
while (cur != null) {
LinkNode next = cur.next;
// 断开当前节点的所有引用
cur.prev = null;
cur.next = null;
cur = next;
}
// 最后把头尾指针置空
head = null;
last = null;
}
//-------------删除链表----------------
@Override
public void remove(int key) {
//判断当前链表是否为空
if(head == null) {
System.out.println("链表为空");
return;
}
LinkNode cur = head;
while (cur!=null) {
if(cur.val == key) {
//判断是不是头节点
if(head == cur) {
head = head.next;
if(head!=null) {
//防止 当前双向链表只有一个节点
head.prev = null;
}
}else {
//此时就不是头节点
cur.prev.next = cur.next;
if(cur.next == null) {
last = last.prev;
}else {
//删除中间节点
cur.next.prev = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
//----------删除所有在链表中的key值------------
@Override
public void removeAll(int key) {
if(head == null) {
System.out.println("链表为空");
return;
}
LinkNode cur = head;
while (cur!=null) {
if(cur.val == key) {
//判断是不是头节点
if(head == cur) {
head = head.next;
if(head!=null) {
//防止 当前双向链表只有一个节点
head.prev = null;
}
}else {
//此时就不是头节点
cur.prev.next = cur.next;
if(cur.next == null) {
last = last.prev;
}else {
//删除中间节点
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;
}
}
//-----------判断当前值是否在链表中--------
@Override
public boolean contains(int val) {
if(head==null) {
return false;
}
//此时链表不为空
LinkNode cur = head;
while (cur!=null) {
if(cur.val == val) {
return true;
}
cur = cur.next;
}
return false;
}
//-----------打印链表-----------
@Override
public void display() {
if(head==null) {
System.out.println("链表为空");
return;
}
//此时链表不位空
LinkNode cur = head;
while (cur!=null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
}
[🔙 返回目录](#🔙 返回目录)