链表的表示 | 算法必看系列三

简介: 由于链表的特点(查询或删除元素都要从头结点开始),所以我们只要在链表中定义头结点即可,另外如果要频繁用到链表的长度,还可以额外定义一个变量来表示。

上一节:什么是链表?

链表的表示

链表的表示

由于链表的特点(查询或删除元素都要从头结点开始),所以我们只要在链表中定义头结点即可,另外如果要频繁用到链表的长度,还可以额外定义一个变量来表示。

需要注意的是这个头结点的定义是有讲究的,一般来说头结点有两种定义形式,一种是直接以某个元素结点为头结点,如下
image.png
一种是以一个虚拟的节点作为头结点,即我们常说的哨兵,如下
image.png
定义这个哨兵有啥好处呢,假设我们不定义这个哨兵,来看看链表及添加元素的基本操作怎么定义的

/**
* 链表中的结点,data代表节点的值,next是指向下一个节点的引用
 */
class Node {
    int data;// 结点的数组域,值
    Node next = null;// 节点的引用,指向下一个节点
    public Node(int data) {
        this.data = data;
    }
}

/**
* 链表
 */
public class LinkedList {
    int length = 0; // 链表长度,非必须,可不加
    Node head = null; // 头结点

    public void addNode(int val) {
        if (head == null) {
            head = new Node(val);
        } else {
            Node tmp = head;
            while (tmp.next != null) {
                tmp.next = tmp;
            }
            tmp.next = new Node(val);
        }
    }
}

发现问题了吗,注意看下面代码
image.png
有两个问题:

1.每插入一个元素都要对头结点进行判空比较,如果一个链表有很多元素需要插入,就需要进行很多次的判空处理,不是那么高效
2.头结点与其他结点插入逻辑不统一(一个需要判空后再插入,一个不需要判空直接插入),从程序逻辑性来说不是那么合理(因为结点与结点是平级,添加逻辑理应相同)
如果定义了哨兵结点,以上两个问题都可解决,来看下使用哨兵结点的链表定义

public class LinkedList {
    int length = 0; // 链表长度,非必须,可不加
    Node head = new Node(0); // 哨兵结点
    public void addNode(int val) {
        Node tmp = head;
        while (tmp.next != null) {
            tmp.next = tmp;
        }
        tmp.next = new Node(val);
    }
}

可以看到,定义了哨兵结点的链表逻辑上清楚了很多,不用每次插入元素都对头结点进行判空,也统一了每一个结点的添加逻辑。

所以之后的习题讲解中我们使用的链表都是使用定义了哨兵结点的形式。

做了这么多前期的准备工作,终于要开始我们的正餐了:链表解题常用套路–翻转!

链表常见解题套路

热身赛

既然我们要用链表解题,那我们首先就构造一个链表吧

题目:给定数组 1,2,3,4 构造如下链表 head–>4—->3—->2—->1

看清楚了,是逆序构造链表!顺序构造我们都知道怎么构造,对每个元素持续调用上文代码定义的 addNode 方法即可(即尾插法),与尾插法对应的,是头插法,即把每一个元素插到头节点后面即可,这样就能做到逆序构造链表,如图示(以插入1,2 为例)
image.png
头插法比较简单,直接上代码,直接按上图的步骤来完成逻辑,如下

public class LinkedList {
    int length = 0; // 链表长度,非必须,可不加
    Node head = new Node(0); // 哨兵节点
    
     // 头插法
    public void headInsert(int val) {
        // 1.构造新结点
        Node newNode = new Node(val);
        // 2.新结点指向头结点之后的结点
        newNode.next = head.next;
        // 3.头结点指向新结点
        head.next = newNode;
    }

    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        int[] arr = {1,2,3,4};
        // 头插法构造链表
        for (int i = 0; i < arr.length; i++) {
            linkedList.headInsert(arr[i]);
        }
        // 打印链表,将打印 4-->3-->2-->1
        linkedList.printList();
    }
}

小试牛刀

现在我们加大一下难度,来看下曾经的 Google 面试题:给定单向链表的头指针和一个节点指针,定义一个函数在 O(1) 内删除这个节点。
image.png
如图示:给定值为 2 的结点,如何把这个结点给删了。
我们知道,如果给定一个结点要删除它的后继结点是很简单的,只要把这个结点的指针指向后继结点的后继结点即可
image.png
如图示:给定结点 2,删除它的后继结点 3, 把结点 2 的 next 指针指向 3 的后继结点 4 即可。

但给定结点 2,该怎么删除结点 2 本身呢,注意题目没有规定说不能改变结点中的值,所以有一种很巧妙的方法,狸猫换太子!我们先通过结点 2 找到结点 3,再把节点 3 的值赋给结点 2,此时结点 2 的值变成了 3,这时候问题就转化成了上图这种比较简单的需求,即根据结点 2 把结点 3 移除即可,看图
image.png
不过需要注意的是这种解题技巧只适用于被删除的指定结点是中间结点的情况,如果指定结点是尾结点,还是要老老实实地找到尾结点的前继结点,再把尾结点删除,代码如下

/**
 * 删除指定的结点
 * @param deletedNode
 */
public void removeSelectedNode(Node deletedNode) {
    // 如果此结点是尾结点我们还是要从头遍历到尾结点的前继结点,再将尾结点删除
    if (deletedNode.next == null) {
        Node tmp = head;
        while (tmp.next != deletedNode) {
            tmp = tmp.next;
        }
        // 找到尾结点的前继结点,把尾结点删除
        tmp.next = null;
    } else {
        Node nextNode = deletedNode.next;
        // 将删除结点的后继结点的值赋给被删除结点
        deletedNode.data = nextNode.data;
        // 将 nextNode 结点删除
        deletedNode.next = nextNode.next;
        nextNode.next = null;
    }
}

了解更多算法内容,请持续关注:阿里云开发者社区算法编程官方技术圈 !

下一节:链表翻转

来源 | 五分钟学算法
作者 | 程序员小吴

相关文章
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
296 1
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
200 0
|
算法
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
196 0
|
算法
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
183 0
|
存储 算法
【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表
【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表
346 0
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
443 30
|
11月前
|
存储 监控 算法
员工电脑监控系统中的 C# 链表算法剖析-如何监控员工的电脑
当代企业管理体系中,员工电脑监控已成为一个具有重要研究价值与实践意义的关键议题。随着数字化办公模式的广泛普及,企业亟需确保员工对公司资源的合理利用,维护网络安全环境,并提升整体工作效率。有效的电脑监控手段对于企业实现这些目标具有不可忽视的作用,而这一过程离不开精妙的数据结构与算法作为技术支撑。本文旨在深入探究链表(Linked List)这一经典数据结构在员工电脑监控场景中的具体应用,并通过 C# 编程语言给出详尽的代码实现与解析。
218 5
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
617 25
|
10月前
|
存储 算法 物联网
解析局域网内控制电脑机制:基于 Go 语言链表算法的隐秘通信技术探究
数字化办公与物联网蓬勃发展的时代背景下,局域网内计算机控制已成为提升工作效率、达成设备协同管理的重要途径。无论是企业远程办公时的设备统一调度,还是智能家居系统中多设备间的联动控制,高效的数据传输与管理机制均构成实现局域网内计算机控制功能的核心要素。本文将深入探究 Go 语言中的链表数据结构,剖析其在局域网内计算机控制过程中,如何达成数据的有序存储与高效传输,并通过完整的 Go 语言代码示例展示其应用流程。
204 0
|
12月前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
241 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨