什么是链表?

在了解完什么是数据结构之后,让我们一起来探索下数据结构中常见的一种—链表。

链表

链表是数据结构之一,其中的数据呈线性排列。在链表中,数据的添加和删除都较为方便,就是访问比较耗费时间。

什么是链表?
指针

如上图所示就是链表的概念图,Blue、Yellow、Red 这 3 个字符串作为数据被存储于链表中,也就是数据域,每个数据都有 1 个指针,即指针域,它指向下一个数据的内存地址,其中 Red 是最后 1 个数据,Red 的指针不指向任何位置,也就是为 NULL,指向 NULL 的指针通常被称为空指针。

什么是链表?
空指针

在链表中,数据一般都是分散存储于内存中的,无须存储在连续空间内。

什么是链表?
链表的存储空间是无序的

因为数据都是分散存储的,所以如果想要访问数据,只能从第 1 个数据开始,顺着指针的指向一一往下访问(这便是顺序访问)。比如,想要找到 Red 这一数据,就得从 Blue 开始访问,这之后,还要经过 Yellow,我们才能找到 Red。

什么是链表?
链表的顺序访问

如果想要添加数据,只需要改变添加位置前后的指针指向就可以,非常简单。比如,在 Blue 和 Yellow 之间添加 Green。

什么是链表?
链表的指针

首先将 Blue 的指针指向的位置变成 Green,然后再把 Green 的指针指向 Yellow,数据的添加就大功告成了。

什么是链表?
链表新增数据图示

数据的删除也一样,只要改变指针的指向就可以,比如删除 Yellow。

什么是链表?
链表删除数据

这时,只需要把 Green 指针指向的位置从 Yellow 变成 Red,删除就完成了。虽然 Yellow 本身还存储在内存中,但是不管从哪里都无法访问这个数据,所以也就没有特意去删除它的必要了。今后需要用到 Yellow 所在的存储空间时,只要用新数据覆盖掉就可以了。

那么对链表的操作所需的运行时间到底是多少呢?在这里,我们把链表中的数据量记成 n。访问数据时,我们需要从链表头部开始查找(线性查找),如果目标数据在链表最后的话,需要的时间就是 O(n)。

另外,添加数据只需要更改两个指针的指向,所以耗费的时间与 n 无关。如果已经到达了添加数据的位置,那么添加操作只需花费 O(1)的时间,删除数据同样也只需 O(1)的时间。

链表的实现

在对链表有了大概的认识以后,我们用 Java 去实现属于自己的链表:

public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
    }
}
class MyLinkedList {
    int size;
    /**
     * 哨兵节点作为伪头
     */
    ListNode head;
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    /**
     * 获取链表第 index 个节点的值。如果索引是无效的,返回-1
     * @param index
     * @return
     */
    public int get(int index) {
        // 若索引无效
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode curr = head;
        // 从伪头节点开始,向前走 index+1 步
        for (int i = 0; i < index + 1; ++i) {
            curr = curr.next;
        }
        return curr.val;
    }
    /**
     * 在头部插入节点
     *
     * @param val
     */
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }
    /**
     * 在尾部插入节点
     * @param val
     */
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    /**
     * 在链表中的第 index 个节点前添加值为 val 的一个节点。如果 index 等于链表的长度时,节点将被添加到链表的末尾。如果 index 大于链表长度,节点将无法插入
     * @param index
     * @param val
     */
    public void addAtIndex(int index, int val) {
        // 如果index大于长度,则不会插入该节点。
        if (index > size) {
            return;
        }
        // 如果 index 为负,则该节点将插入列表的开头。
        if (index < 0) {
            index = 0;
        }
        ++size;
        // 查找要添加的节点的前驱节点
        ListNode pred = head;
        for (int i = 0; i < index; ++i) {
            pred = pred.next;
        }
        // 要添加的节点
        ListNode toAdd = new ListNode(val);
        // 通过改变 next 来插入节点
        toAdd.next = pred.next;
        pred.next = toAdd;
    }
    /**
     * 如果 index 是有效的,删除链表中的第 index 个节点
     *
     * @param index
     */
    public void deleteAtIndex(int index) {
        // 如果 index 无效,则不执行任何操作
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        // 找到要删除节点的前驱节点
        ListNode pred = head;
        for (int i = 0; i < index; ++i) {
            pred = pred.next;
        }
        // 通过改变 next 来删除节点
        pred.next = pred.next.next;
    }
}

到这里,我相信大家应该对链表有了进一步的理解,大家可以用不同的语言去设计实现下。

链表扩展

以上讲述的链表是最基本的一种链表,除此之外,还存在几种扩展方便的链表。

虽然上文中提到的链表在尾部没有指针,但我们也可以在链表尾部使用指针,并且让它指向链表头部的数据,将链表变成环形,这便是循环链表,也叫环形链表。循环链表没有头和尾的概念,想要保存数量固定的最新数据时通常会使用这种链表。

什么是链表?
环形链表

另外,以上提到的链表里的每个数据都只有一个指针,但我们可以把指针设定为两个,并且让它们分别指向前后数据,这就是双向链表。使用这种链表,不仅可以从前往后,还可以从后往前遍历数据,十分方便。

但是,双向链表存在两个缺点:一是指针数的增加会导致存储空间需求增加;二是添加和删除数据时需要改变更多指针的指向。

什么是链表?
双向链表

总结

看完之后,相信大家都对链表和链表的基本操作有了一定的了解,还对循环链表和双向链表有了初步的认识,大家可以使用自己喜欢的语言去设计实现下单向链表,有能力的话可以把循环链表和双向链表也实现下。

说完链表,当然不能忘记经常和链表同时出现在面试官口中的—数组,将在接下来的文章对其进行展开介绍。

原文链接:https://mp.weixin.qq.com/s/ZuCcYvUtrcvJr19tDzrNnw

什么是链表?

: » 什么是链表?

原创文章,作者:254126420,如若转载,请注明出处:https://blog.ytso.com/252923.html

(0)
上一篇 2022年5月5日
下一篇 2022年5月5日

相关推荐

发表回复

登录后才能评论