Java数据结构与算法05——多图读懂双向链表

4 篇文章 0 订阅

标签(空格分隔): Java 数据结构 算法

作者 : Maxchen

版本 : V1.0.0

日期 : 2020/4/27


什么是双向链表?为什么会使用它?

双向链表就是具备两个方向的指向,无非就是每个结点成了两个指针。

举个例子: 假设一个文本编辑用单链表来存储文本. 每一行用一个String对象存储在链表的一个节点中. 当编辑器用户向下移动光标时, 链表直接操作到下一个节点即可. 但是当用于将光标向上移动呢? 这个时候为了回到上一个节点, 我们可能需要从first开始, 依次走到想要的节点上。

双向链表既可以从头遍历到尾,又可以从尾遍历到头,解决了单链表存储文本光标向上移动时必须从first开始的问题。

一个完整的双向链表包含head头节点,中间的node,以及末尾指向空的节点。

image.png-204.5kB

双向链表的几个常规操作

1、创建链表

未命名文件.png-3.8kB

有两个指针域分别指向prev前一个结点和next后一个结点,还有一部分用来保存结点数据,初始化结点时需要将两个指针都指向空。

node.prev == null;
node.next == null;

2、遍历链表

双向链表.gif-29.8kB

从头节点开始一直往后取链表数据,直到取出的链表数据为空为止。也可以从末尾开始取数据,直到空的头节点。

temp = node.next;
while (true) {
	// 判断是否到链表最后,链表最后为空
	if (temp == null) {
		break;
	}
	// 如果不为空就一直往后取数
	temp = temp.next;
}

3、新增链表

双向链表.gif-59.1kB

从head节点开始一直遍历双向链表,直到遍历到链表末尾。在链表末尾重新指向新增的数据。

// 遍历到双向链表末尾
while (true) {
	if (temp.next == null) {
		break;
	}
	temp = temp.next;
}
// 在末尾重新定义指向,指向新的链表数据newNode
temp.next = newNode;
newNode.prev = temp;

4、修改链表

双向链表.gif-40kB

遍历查询链表,直到找到需要修改的链表(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、删除链表

双向链表.gif-48.2kB

遍历查询链表,直到找到需要删除的链表(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、往每个节点新增数据,之后再遍历出来;
image.png-78.6kB
image.png-31.7kB
3、根据编号找到需要修改的节点,指向保持不变,修改它的数据;
image.png-24.4kB
image.png-31.3kB
4、根据编号找到需要删除的节点,指向重新设置。
image.png-15kB
image.png-22kB

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 + "]";
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值