数据结构-链表

简介: 链表结构链表结构五花八门,今天我重点给你介绍三种最常见的链表结构,它们分别是:单链表、双向链表和循环链表。我们首先来看最简单、最常用的单链表。

链表结构



链表结构五花八门,今天我重点给你介绍三种最常见的链表结构,它们分别是:单链表、双向链表和循环链表。我们首先来看最简单、最常用的单链表。


单链表



image.png


我们习惯性地把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。


针对链表的插入和删除操作,我们只需要考虑相邻结点的指针改变,所以对应的时间复杂度是 O(1)。链表随机访问的性能没有数组好,需要 O(n) 的时间复杂度。


循环链表



循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样首尾相连,所以叫作“循环”链表。


image.png


从我画的图中可以看出来,双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。那相比单链表,双向链表适合解决哪种问题呢?从结构上来看,双向链表可以支持 O(1) 时间复杂度的情况下找到前驱结点,正是这样的特点,也使双向链表在某些情况下的插入、删除等操作都要比单链表简单、高效。


头结点即为第一个节点


尾节点指向空地址


带哨兵的节点有利于简化代码,推荐使用


双向链表



循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样首尾相连,所以叫作“循环”链表。


image.png

双向循环链表



了解了循环链表和双向链表,如果把这两种链表整合在一起就是一个新的版本:双向循环链表。


image.png


双向链表的示例【待添加】


必练操作



接口定义

package com.s1.array;
public interface IList {
    void print();
    // 往尾部添加
    void add(Integer data);
    void addFirst(Integer data);
    void addLast(Integer data);
    // 指定位置添加元素(有效范围内)
    void add(int position, Integer data);
    // 往尾部删除
    Object remove();
    // 删除首元素
    Object removeFirst();
    // 删除尾元素
    Object removeLast();
    // 删除指定下标的元素
    Object remove(int index);
    // 删除指定数据的元素
    Object remove(Object obj);  
    // 改1 : 找到下标,然后更改
    boolean updateByPosition(int position, Integer value);
    // 改2 : 找到原数据,然后更改
    boolean updateByData(Integer oldData, Integer newData);
    void clear();   
}


  • 实现单链表【可选 循环链表、双向链表】,支持增删操作


  • 单链表反转链


  • 表中环的检测


  • 两个有序的链表合并


  • 删除链表倒数第 n 个结点


  • 求链表的中间结点


思考题:基于链表的 LRU 算法



LRU 思路一


  1. 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。


  1. 如果此数据没有在缓存链表中,又可以分为两种情况:
    如果此时缓存未满,则将此结点直接插入到链表的头部;
    如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。


或者 思路二


/**
     * 如果不存在 
     *   队列未满则插入 tail
     *   队列已满移除head并从插入 tail         
     * 如果存在 则从中取出并从插入 tail
     */


package com.s2.link;
public class LRULinkedList extends LinkedList {
    private static final int DEFAULT_LENGTH = 10;
    private final int length;
    private int used = 0;
    public LRULinkedList() {
        this(DEFAULT_LENGTH);
    }
    public LRULinkedList(int length) {
        this.length = length;
    }
    protected boolean isFull () {
        return this.used == this.length;
    }
    @Override
    public void add(Integer data) {
        /**
         * 如果不存在 
         *   队列未满则插入 tail
         *   队列已满移除head并从插入 tail         
         * 如果存在 则从中取出并从插入 tail
         */ 
        Object removeNode = this.remove(data);
        if (removeNode == null && this.isFull()) {
            this.removeFirst();     
        }
        this.addLast(data);
    }
    @Override
    public Object remove(Object obj) {
        Object removeObject = super.remove(obj);
        if (removeObject != null) {
            this.used--;
        }       
        return removeObject;
    }
    @Override
    public Object removeFirst() {
        Object removeObject = super.removeFirst();
        if (removeObject != null) {
            this.used--;
        }       
        return removeObject;
    }
    @Override
    public void addLast(Integer data) {
        super.addLast(data);
        used++;
    }
}


思考题:判断是否为回文串



找到中间节点。根据奇偶个数。


如果是奇数个则中分开。


如果是偶数个,则认为中点有两个,继续分开。


然后分别拿到两端的 head 指针就行循环,如果遇到节点的数据不一致则认定不是回文串。若循环结束则认为该串是回文串。


代码片段

// 判断是否为回文 
    public boolean palindrome() {
        // 根据快慢指针找到中间节点, 但是不知道总结点个数是奇还是偶数
        if (this.headNode == null) {
            return false;
        }
        if (this.headNode.next == null) {
            return true;
        }
        // 大于两个节点
        Node slow = this.headNode;
        Node fast = this.headNode;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        //  slow  fast
        //  1     2
        //  1     2   3
        System.out.println("slow " + slow);
        System.out.println("fast " + fast);
        Node leftNode;
        Node rightNode;
        // 总奇数个, 一个重点
        if (fast.next == null) {
            rightNode = slow.next;
            this.inverseLinkList(slow);
            leftNode = slow.next;
        } 
        //  总偶数个数,两个中点 
        else {
            rightNode = slow.next;
            this.inverseLinkList(slow);
            leftNode = slow;
        }           
        return this.TFResult(leftNode, rightNode);
    }


我的代码


https://gitee.com/kaiLee/struct/tree/master/src/main/java/com/s2


参考



06 | 链表(上):如何实现LRU缓存淘汰算法?


https://time.geekbang.org/column/article/41013


07 | 链表(下):如何轻松写出正确的链表代码?


https://time.geekbang.org/column/article/41149


algo: 数据结构和算法必知必会的50个代码实现


https://gitee.com/TheAlgorithms/algo




目录
相关文章
|
12月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
212 4
|
9月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
312 30
|
9月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
414 25
|
10月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
459 5
|
11月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
12月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
346 5
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
1085 4
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
124 7