文章目录
一、双向链表的简单介绍
双向链表,顾名思义和单向链表很相似,均为链表,两者之间的操作也十分相近,最明显的不同之处就是双向链表的单个结点带有两个指针域,分别指向前后两个元素。
二、双向链表的实现
1.基本框架的构建
节点的结构如图:
首先,在实现各项操作前,应该首先实现结点的构建,以静态内部类来实现。
代码如下:
//实现静态内部类,用于实现结点
static class ListNode{
public int val;
//定义存放头尾结点的域
public ListNode next;
public ListNode prev;
public ListNode(int val){
this.val = val;
}
}
接下来,我们需要了解应该实现哪些不同的方法,如下:
1.打印链表
2.查找链表长度
3.头插法
4.尾插法
5.任意位置插入
6.查找是否包含关键词key
7.删除第一次出现的key结点
8.删除全部key结点
9.清空链表
注:
首先定义出 head 指针和 tail 指针,分别指向头尾。
public ListNode head;
public ListNode tail;
下面,我将会详细进行逐一实现。
2.打印链表
public void display(){
ListNode cur = head;
while(cur != null){
System.out.print(cur.val+" ");
cur = cur.next;
}
}
3.查找链表长度
public int size(){
int count = 0;
ListNode cur = head;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
4.头插法
简单分析:
这里有两个需要考虑的地方。
1.当链表起始时为空时
2.当链表有元素时
代码如下:
public void addFirst(int data){
//申请一个新的节点
ListNode node = new ListNode(data);
//当不存在元素时
if(head == null){
head = node;
tail = node;
}else{
//当存在元素时
head.prev = node;
node.next = head;
head = node;
}
}
详细分析:
这里主要分析情况 2。
分析如图:
5.尾插法
简单分析:
同样,这里也有两个需要考虑的地方。
1.当链表为空时。
2.当链表中存在元素时。
代码实现
public void addLast(int data){
ListNode node = new ListNode(data);
if(head == null){
head = node;
tail = node;
}else{
tail.next = node;
node.prev = tail;
tail = node;
}
}
详细分析
这里主要分析情况 2 。
6.任意位置插入
简单分析
这里有需要考虑的三个地方。
1.插入的位置 index 是否合法
2.插入的 index 位置是否在头尾
3.插入的 index 位置位于一般位置
代码如下:
public void addIndex(int index,int data){
ListNode node = new ListNode(data);
//1.判断index的合法性
if(index < 0 || index > size()){
System.out.println("index不合法");
throw new IndexWrongFulException("index不合法");
}
//2.判断是头插还是尾插
if(index == 0){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
//3.找到index位置的结点地址
ListNode cur = find(index);
cur.prev.next = node;
node.prev = cur.prev;
node.next = cur;
cur.prev = node;
}
注:
1.这里判断合法性使用了异常判断类
代码如下:
public class IndexWrongFulException extends RuntimeException{
public IndexWrongFulException(String message) {
super(message);
}
}
2.在寻找 index 位置时,定义了 find 方法,
代码如下:
private ListNode find(int index){
ListNode cur = head;
while(index-1 != 0){
cur = cur.next;
index--;
}
return cur;
}
详细分析:
这里主要分析一般情况下在链表中间插入的情况
注:紫色数字为指针顺序。
7.查找是否存在关键词key
简单分析:
这个方法难度不大,只需要遍历寻找即可。
代码如下
public boolean contains(int key){
ListNode cur = head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
8.删除第一次出现的关键词key
简单分析:
这里有三个地方需要考虑。
1.当要删除的结点为一个单独的头结点
2.要删除的节点恰好为尾部结点
3.要删除的结点的位置为链表中的一般位置
代码如下:
public void remove(int key){
ListNode cur = head;
while(cur != null){
//循环寻找 key 的值
if(cur.val == key){
//当要删除的值是头节点
if(cur == head){
head = head.next;
//判断删除的是不是单独的一个头节点
if(head != null){
head.prev = null;
}else{
tail = null;
}
}else{
//一般情况删除
cur.prev.next = cur.next;
//判断删除的是否为尾部结点
if(cur.next != null){
cur.next.prev = cur.prev;
}else{
this.tail = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
详细分析:
1.删除头节点
2.删除中间节点
3.删除尾部结点
9.删除所有关键词key
简单分析:
这个方法实现非常简单,只要将上一个方法中的 return 删除即可。
代码实现:
public void remove(int key){
ListNode cur = head;
while(cur != null){
if(cur.val == key){
if(cur == head){
head = head.next;
if(head != null){
head.prev = null;
}else{
tail = null;
}
}else{
cur.prev.next = cur.next;
if(cur.next != null){
cur.next.prev = cur.prev;
}else{
this.tail = cur.prev;
}
}
}
cur = cur.next;
}
}
10、清空链表
简单分析:
双向链表的清空并非是简单的将 head 和 tail 置为 null ,而是要将所有结点的指向全部置为 null
代码如下:
public void clear(){
ListNode cur = head;
ListNode pre = cur.next;
while(cur != null){
cur.next = null;
cur.prev = null;
cur = pre;
pre = pre.next;
}
head = null;
tail = null;
}
至此,所有的方法实现结束。