• 算法基础:单链表图解及模板总结


    ⭐写在前面的话:本系列文章旨在复习算法刷题中常用的基础算法与数据结构,配以详细的图例解释,总结相应的代码模板,同时结合例题以达到最佳的学习效果。本专栏面向算法零基础但有一定的C++基础的学习者。若C++基础不牢固,可参考:10min快速回顾C++语法,进行语法复习。

    静态链表

    如果说用结构体+指针的方式实现链表和栈的话,每次需要new一个新节点,非常慢。笔试题链表大小在10万级别,因此笔试题一般不会采用这种动态链表的方式。通常采用数组模拟链表的方式,这种方式更快。

    struct Node
    {
    	int val;
    	Node *next;
    };
    new Node();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    链表与邻接表

    邻接表

    邻接表的head[i]存储的是节点i所有的临边。实际上就是一个单链表。

    用数组模拟单链表

    单链表里常用的是邻接表。邻接表主要用来存储图和数。

    双链表常用来优化某些问题。

    链表的结构通常如下所示:

    image-20221017182739361

    每一个节点中通常存储两个数,分别是节点值(val)和指向下一个节点的指针(next)。通常如下:

    • val :e[N]
    • next :ne[N]

    两者通过索引下标相联系。

    image-20221017183223725

    #include
    using namespace std;
    
    const int N = 100010;
    
    // head 表示头节点的下标
    // e[i] 表示节点i的值
    // ne[i] 表示节点i的next指针是多少
    // idx存储当前用到了哪个点
    int head, e[N], ne[N], idx;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    初始化操作
    //初始化
    void init()
    {
        head = -1;idx = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    插入头节点

    基本思路如下:

    image-20221017211027847

    //将x插到头结点
    void add_to_head(int x)
    {
        // 把插入的值x存在idx中
        e[idx] = x;
        // 让当前的idx的next指针与头节点指向一致
        ne[idx] = head;
        // head指向新节点idx
        head = idx;
        idx ++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里也可以将代码压缩一下,更加简洁

    //将x插到头结点
    void add_to_head(int x)
    {
        e[idx] = x, ne[idx] = head, head = idx, idx ++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    插入下标为k的后面
    //将x插入下标为k的后面
    void add(int k, int x)
    {
        e[idx] = x, ne[idx] = ne[k], ne[k] = idx, idx ++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当然也可以进一步化简,将ne[k] = idx, idx ++;化简为ne[k] = idx++

    删除操作

    将下标为k的点后面的点删除:只需要指针跳过该点即可。

    image-20221017211610701

    void remove(int k)
    {
        // k的指向等于它下一个的节点的指向
        ne[k] = ne[ne[k]];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    例题:单链表

    实现一个单链表,链表初始为空,支持三种操作:

    1. 向链表头插入一个数;
    2. 删除第 k 个插入的数后面的数;
    3. 在第 k 个插入的数后插入一个数。

    现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。

    注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。

    输入格式

    第一行包含整数 M,表示操作次数。

    接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

    1. H x,表示向链表头插入一个数 x。
    2. D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
    3. I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。

    输出格式

    共一行,将整个链表从头到尾输出。

    数据范围

    1≤M≤100000
    所有操作保证合法。

    输入样例

    10
    H 9
    I 1 1
    D 1
    D 0
    H 6
    I 3 6
    I 4 5
    I 4 5
    I 3 4
    D 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出样例

    6 4 6 5
    
    • 1
    code

    此题需要注意的是,题目要求在第k个节点上进行操作,而第k个节点的下标是k-1。(还是要多注意题目的表述)

    #include
    using namespace std;
    
    const int M = 100010;
    
    int head, idx, e[M], ne[M];
    
    void init()
    {
        head = -1;
        idx = 0;
    }
    
    void add_to_head(int x)
    {
        e[idx] = x, ne[idx] = head, head = idx ++ ;
    }
    
    void add(int k, int x)
    {
        e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
    }
    
    void remove(int k)
    {
        ne[k] = ne[ne[k]];
    }
    
    int main()
    {
        int m;
        cin >> m;
        
        init();
        
        while(m --)
        {
            int k, x;
            char op;
            
            cin >> op;
            if(op == 'H')
            {
                cin >> x;
                add_to_head(x);
            }
            else if(op == 'D')
            {
                cin >> k;
                // 要特别注意删除节点头的情况
                if(!k)head = ne[head];
                remove(k - 1);
            }
            else
            {
                cin >> k >> x;
                add(k - 1, x);
            }
        }
        for(int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    单链表模板总结

    // head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
    int head, e[N], ne[N], idx;
    
    // 初始化
    void init()
    {
        head = -1;
        idx = 0;
    }
    
    // 在链表头插入一个数a
    void insert(int a)
    {
        e[idx] = a, ne[idx] = head, head = idx ++ ;
    }
    
    // 将头结点删除,需要保证头结点存在
    void remove()
    {
        head = ne[head];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    APISpace 手机号码归属地API 方便好用
    深入解析:数据库连接池的必要性与优化策略
    【每日三十六记 —— BGP知识点汇总大全】(第一弹)
    k8s--基础--03--api
    聊聊编程是什么
    面试官:group by 有哪些注意事项?
    50天50个前端小项目(纯html+css+js)第八天(形成波浪动画结合登录表单)
    独立产品灵感周刊 DecoHack #030 - iOS16正式发布
    Redis线程模型、单线程快的原因
    2022双十一有哪些数码好物值得入手?双11实用性强的数码装备推荐
  • 原文地址:https://blog.csdn.net/m0_52316372/article/details/128012059