通俗易懂的链表

3bf77c09dda256816e16ddb774a63c62.gif

关注下方公众号,分享硬核知识

作者 | 小K

出品 | 公众号:小K算法 (ID:xiaok365)

01

数组

数组是最简单的数据结构,存放一组相同类型的数据,可以通过下标快速进行读写操作。

fb1135a51c8f59eb30c7ca66c1a9dceb.png

它在内存中也是一段连续的地址。

64c32316c456d8127701d6fc3968e763.png

如果告诉你数组的首地址,对地址递增,就可以遍历完数组的所有元素。

59f3e88acc07e27c4d2e9659099f36b5.png

但如果要删除元素,比如删除中间的一个元素,首先得找到这个元素。

0572c9043aa503f0a034bd09acfd3da5.png

然后用下一个元素覆盖掉当前元素,同理后面的所有元素都需要前移一位,时间复杂度为O(n),当数据量很大时,效率就非常低。

fb8a571697dfe32e9e79dcf85ce01462.png

那有没有办法改进呢?

02

链表

针对上面的问题,于是出现了链表。首先链表也是存在于内存中的数据结构,和数组不同的是,它不是一段连续的地址。

54fc8c8147964ad61c3df876f68a47ca.png

为了能够遍历每个元素,所以需要将所有的元素串联起来,这就是链表的定义。

4f006c3487223d7b471c43df3bee7e7e.png

所以每一个链表元素需要存储两个最重要的信息,一个是数据,另一个就是下一个元素的地址。

8b3c58ea11bba9c9745cafdbb729d4f2.png

03

链表定义

每一个结点,存储数据和下一元素的地址。为了方便操作,一般还需要定义一个头指针和尾指针,分别指向链表的头和尾。

0f4a74a2ff6a068cbc94aeecce5860c7.png

代码如下:

struct LinkNode {
    int data;
    LinkNode *next;
};
LinkNode *head, *tail;

04

插入结点

插入一般分两种,从头或尾插入新结点。


从头插入:先新建一个结点,将新结点指向头结点,再将头指针指向新结点。

4991399da3dc1556b573d99cd95c8743.png

代码如下:

void insertFromHead(int x) {
    LinkNode *node = new LinkNode{x};
    if (head == nullptr) {
        head = node;
        tail = node;
    } else {
        node->next = head;
        head = node;
    }
}

从尾插入:先新建一个结点,将尾结点指向新结点,再将尾指针指向新结点。

20df36ac3313e05a5796349d021896dd.png

代码如下:

void insertFromTail(int x) {
    LinkNode *node = new LinkNode{x};
    if (head == nullptr) {
        head = node;
        tail = node;
    } else {
        tail->next = node;
        tail = node;
    }
}

05

删除结点

先找到要删除的结点,将上一结点的next指向当前结点的next,再释放当前结点。

5c41a0a06c461aa65a7bc29d6d7e5725.png

代码如下:

void deleteNode(LinkNode **head, int x) {
    LinkNode *pre;
    // 如果头结点是要删除的结点
    pre = *head;
    if ((*head)->data == x) {
        (*head) = (*head)->next;
        delete pre;
        return;
    }
    // 寻找要删除的结点
    while (pre->next != nullptr && pre->next->data != x) {
        pre = pre->next;
    }
    // 如果找到,则删除结点
    LinkNode *current;
    if (pre->next != nullptr) {
        current = pre->next;
        pre->next = pre->next->next;
        delete current;
    }
}

06

遍历结点

从头结点开始,依次读取直到结点为空。

c50bc17818627ad4c9e296570c67437c.png

代码如下:

void printLink(LinkNode *head) {
    while (head != nullptr) {
        cout << head->data << endl;
        head = head->next;
    }
}

07

总结

数组读写都是O(1),适合元素个数固定的场景。链表对于插入和删除操作都是O(1),但访问却是O(n),所以更适合频繁增减元素的场景。
数组和链表都各有优缺点,互补。那有没有更完美的数据结构呢,既有数组的快速访问效率,又有链表的快速增减效率?那肯定是有的,有需求就有市场,用数组加链表组合起来,这不就是满大街都在用的hashmap了吗,欲知hashmap详情,即听下文分解。

本文原创作者:小K,一个思维独特的写手。
文章首发平台:微信公众号【小K算法】。

如果喜欢小K的文章,请点个关注,分享给更多的人,小K将持续更新,谢谢啦!

5b1a3c6233a77b73474aa82fba97fbf5.gif

关注下方公众号,分享硬核知识

f654c73dea57beee4cb68f36c4fed68e.gif

关注我,涨知识

ff274debc885211dea0f5ea8ebdec432.gif

原创不易,感谢分享

转发,点赞,在看

往期精彩回顾

花了300多买的鞋柜,没想到...

组装了人生第一台电脑

鸡兔同笼

4b2fa25665b90659098754aba08b8aa8.gif

分享给更多朋友,转发,点赞,在看

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值