一、双向链表的定义
每个结点存在两个指针域,一个指向前驱节点的引用,一个指向后继结点引用。即可以通过该节点向前走,也可以向后走。总之,单节点能做的事,双向链表都能做。
二、双向链表的实现
- 创建双节点类
class DoubleNode{
int data;
//保存前驱节点的地址
DoubleNode pre;
//保存后继节点的地址
DoubleNode next;
//定义三个构造方法
public DoubleNode(){}
public DoubleNode(int data){
this.data = data;
}
public DoubleNode(DoubleNode pre, int data, DoubleNode next) {
this.pre = pre;
this.data = data;
this.next = next;
}
}
- 创建双向链表类
public class DoubleLinkedList {
//头节点的地址
private DoubleNode head = null;
//尾结点的地址
private DoubleNode tail = null;
//记录节点数
private int size;
}
3.在双向链表的头部插入新结点
public void addFir(int data) {
DoubleNode node = new DoubleNode(null,data,head);
//链表中没有结点
if (tail == null) {
tail = node;
} else {
head.pre = node;
}
head = node;
size ++;
}
4.在双向链表的尾部插入新结点
//和头插法类似
public void addTail(int data){
DoubleNode node = new DoubleNode(tail,data,null);
if (head == null) {
head = node;
} else {
tail.next = node;
node.pre = tail;
}
tail = node;
size ++;
}
- 根据索引值去找到对应的节点
private DoubleNode node(int index) {
//inde在前半部分,从前遍历
if(index <= size / 2){
DoubleNode x = head;
for (int i = 0; i < index; i++) {
x = x.next;
}
return x;
} else {
//index在后半部分,从后遍历
DoubleNode x = tail;
for (int i = size - 1; i > index; i--) {
x = x.next;
}
return x;
}
}
6.在索引为index位置插入结点
public void addByIndex(int index, int data) {
if (index < 0 || index > size) {
System.out.println("输出错误");
return;
}
if(index == 0){
addFir(data);
}
else if (index == size) {
addTail(data);
} else{
//从中间插入,找前驱
DoubleNode pre = node(index - 1);
DoubleNode newNode = new DoubleNode(pre,data,pre.next);
pre.next.pre = newNode;
pre.next = newNode;
size ++;
}
}
7.查找索引位置的结点,返回结点值
public int findByIndex(int index) {
if(index < 0 || index >= size) {
return -1;
}
DoubleNode ret = node(index);
return ret.data;
}
8.修改索引位置的结点,返回旧的结点值
public int setByIndex(int index,int newVal) {
if(index < 0 || index >= size) {
return -1;
}
DoubleNode ret = node(index);
int oldVal = ret.data;
ret.data = newVal;
return oldVal;
}
9.删除当前双向链表中的node节点
一共分为四种情况
- 无前驱结点有后继结点
- 有前驱结点无后继结点
- 有前驱结点有后继结点
- 无前驱结点无后继结点(只有一个结点)
我们可以采用”分治思想“,先只管前驱节点的删除操作,在管后继结点的删除操作
public void ulink(DoubleNode node) {
//前驱结点
DoubleNode pre = node.pre;
//后继结点
DoubleNode successor = node.next;
//无前驱节点,头节点就是下一个结点
if(pre == null) {
head = successor;
} else {
//有前驱节点
pre.next = successor;
node.pre = null;
}
//无后继结点,尾结点就是前一个结点
if(successor == null) {
tail = pre;
} else {
successor.pre = pre;
node.next = null;
}
}
10.根据索引index删除节点
public int removeByIndex(int index) {
if(index < 0 || index >= size){
return -1;
}
DoubleNode node = node(index);
int oldVal = node.data;
ulink(node);
return oldVal;
}
11.删除头结点
public void removeFir(){
ulink(node(0));
}
12.删除尾结点
public void removeTail(){
ulink(node(size - 1));
}
13.删除出现第一个值为val的结点
public void removeOnceVal(int val) {
//找到了循环就结束
for (DoubleNode x = head; x!= null; x = x.next) {
if (x.data == val){
ulink(x);
break;
}
}
}
14.删除链表中所有值为val的结点
public void removeAllVal(int val) {
for(DoubleNode x = head; head != null;) {
if(x.data == val){
//暂存下一个结点的地址
//因为ulink()方法会把x.next置空,找不到后继了
DoubleNode successor = x.next;
ulink(x);
x = successor;
} else {
x = x.next;
}
}
}
15.链表的toString方法
public String toString(){
String ret = "";
for (DoubleNode x = head; x != null; x= x.next){
ret += x.data;
ret += "->";
}
ret += "NULL";
return ret;
}