标签(空格分隔): Java
数据结构
算法
作者 : Maxchen
版本 : V1.0.0
日期 : 2020/4/27
什么是双向链表?为什么会使用它?
双向链表就是具备两个方向的指向,无非就是每个结点成了两个指针。
举个例子: 假设一个文本编辑用单链表来存储文本. 每一行用一个String对象存储在链表的一个节点中. 当编辑器用户向下移动光标时, 链表直接操作到下一个节点即可. 但是当用于将光标向上移动呢? 这个时候为了回到上一个节点, 我们可能需要从first开始, 依次走到想要的节点上。
双向链表既可以从头遍历到尾,又可以从尾遍历到头,解决了单链表存储文本光标向上移动时必须从first开始的问题。
一个完整的双向链表包含head头节点,中间的node,以及末尾指向空的节点。
双向链表的几个常规操作
1、创建链表
有两个指针域分别指向prev
前一个结点和next
后一个结点,还有一部分用来保存结点数据,初始化结点时需要将两个指针都指向空。
node.prev == null;
node.next == null;
2、遍历链表
从头节点开始一直往后取链表数据,直到取出的链表数据为空为止。也可以从末尾开始取数据,直到空的头节点。
temp = node.next;
while (true) {
// 判断是否到链表最后,链表最后为空
if (temp == null) {
break;
}
// 如果不为空就一直往后取数
temp = temp.next;
}
3、新增链表
从head节点开始一直遍历双向链表,直到遍历到链表末尾。在链表末尾重新指向新增的数据。
// 遍历到双向链表末尾
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
// 在末尾重新定义指向,指向新的链表数据newNode
temp.next = newNode;
newNode.prev = temp;
4、修改链表
遍历查询链表,直到找到需要修改的链表(flag为true),在链表指向不变的基础上修改数据。
while (true) {
if (temp == null) {
break; // 已经遍历完链表
}
if (temp.no == newNode.no) {
// 找到需要修改的链表,flag为true代表需要修改
flag = true;
break;
}
temp = temp.next;
}
// 根据flag判断是否找到要修改的节点,找到之后进行修改
if (flag) {
temp.name = newNode.name;
temp.nickname = newNode.nickname;
} else { // 没有找到
System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newNode.no);
}
5、删除链表
遍历查询链表,直到找到需要删除的链表(flag为true),前一个节点next指向跳过删除的节点,后一个节点prev指向跳过删除的节点。
while (true) {
if (temp == null) { // 已经到链表的最后
break;
}
if (temp.no == no) {
// 找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next; // temp后移,遍历
}
// 判断flag
if (flag) { // 找到
// 可以删除
// temp.next = temp.next.next;[单向链表]
temp.pre.next = temp.next;
// 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
if (temp.next != null) {
temp.next.prev = temp.prev;
}
} else {
System.out.printf("要删除的 %d 节点不存在\n", no);
}
双向链表的增删改查测试
1、测试结果
1、我们创建一个双向链表,并在此基础上新增节点;
2、往每个节点新增数据,之后再遍历出来;
3、根据编号找到需要修改的节点,指向保持不变,修改它的数据;
4、根据编号找到需要删除的节点,指向重新设置。
2、测试代码
package com.maxchen.demo.linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
// 测试
System.out.println("双向链表的测试");
// 先创建节点
ComputerNode2 computer1 = new ComputerNode2(1, "联想", "美帝良心想");
ComputerNode2 computer2 = new ComputerNode2(2, "惠普", "铁板熊掌普");
ComputerNode2 computer3 = new ComputerNode2(3, "华硕", "奸若磐石硕");
ComputerNode2 computer4 = new ComputerNode2(4, "戴尔", "人傻钱多戴");
// 创建一个双向链表
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
// 新增
doubleLinkedList.add(computer1);
doubleLinkedList.add(computer2);
doubleLinkedList.add(computer3);
doubleLinkedList.add(computer4);
// 遍历
doubleLinkedList.list();
// 修改
ComputerNode2 newComputerNode2 = new ComputerNode2(4, "宏碁", "偷工减料碁");
doubleLinkedList.update(newComputerNode2);
System.out.println("修改后的链表情况");
doubleLinkedList.list();
// 删除
doubleLinkedList.del(3);
System.out.println("删除后的链表情况");
doubleLinkedList.list();
}
}
// 创建一个双向链表的类
class DoubleLinkedList {
// 先初始化一个头节点, 头节点不要动, 不存放具体的数据
private ComputerNode2 head = new ComputerNode2(0, "", "");
// 返回头节点
public ComputerNode2 getHead() {
return head;
}
// 遍历双向链表的方法
// 显示链表[遍历]
public void list() {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 因为头节点,不能动,因此我们需要一个辅助变量来遍历
ComputerNode2 temp = head.next;
while (true) {
// 判断是否到链表最后
if (temp == null) {
break;
}
// 输出节点的信息
System.out.println(temp);
// 将temp后移, 一定小心
temp = temp.next;
}
}
// 添加一个节点到双向链表的最后.
public void add(ComputerNode2 computerNode) {
// 因为head节点不能动,因此我们需要一个辅助遍历 temp
ComputerNode2 temp = head;
// 遍历链表,找到最后
while (true) {
// 找到链表的最后
if (temp.next == null) {//
break;
}
// 如果没有找到最后, 将将temp后移
temp = temp.next;
}
// 当退出while循环时,temp就指向了链表的最后
// 形成一个双向链表
temp.next = computerNode;
computerNode.prev = temp;
}
// 修改一个节点的内容, 可以看到双向链表的节点内容修改和单向链表一样
// 只是 节点类型改成 ComputerNode2
public void update(ComputerNode2 newComputerNode2) {
// 判断是否空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
// 找到需要修改的节点, 根据no编号
// 定义一个辅助变量
ComputerNode2 temp = head.next;
boolean flag = false; // 表示是否找到该节点
while (true) {
if (temp == null) {
break; // 已经遍历完链表
}
if (temp.no == newComputerNode2.no) {
// 找到
flag = true;
break;
}
temp = temp.next;
}
// 根据flag 判断是否找到要修改的节点
if (flag) {
temp.name = newComputerNode2.name;
temp.nickname = newComputerNode2.nickname;
} else { // 没有找到
System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newComputerNode2.no);
}
}
// 从双向链表中删除一个节点,
// 说明
// 1 对于双向链表,我们可以直接找到要删除的这个节点
// 2 找到后,自我删除即可
public void del(int no) {
// 判断当前链表是否为空
if (head.next == null) {// 空链表
System.out.println("链表为空,无法删除");
return;
}
ComputerNode2 temp = head.next; // 辅助变量(指针)
boolean flag = false; // 标志是否找到待删除节点的
while (true) {
if (temp == null) { // 已经到链表的最后
break;
}
if (temp.no == no) {
// 找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next; // temp后移,遍历
}
// 判断flag
if (flag) { // 找到
// 可以删除
// temp.next = temp.next.next;[单向链表]
temp.prev.next = temp.next;
// 这里我们的代码有问题?
// 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
if (temp.next != null) {
temp.next.prev = temp.prev;
}
} else {
System.out.printf("要删除的 %d 节点不存在\n", no);
}
}
}
class ComputerNode2 {
public int no; //序号
public String name; //品牌名称
public String nickname; //品牌外号
public ComputerNode2 next; //指向下一个节点
public ComputerNode2 prev; //指向下一个节点
//构造器
public ComputerNode2(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//为了显示方法,我们重新toString
@Override
public String toString() {
return "ComputerNode2 [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}