V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

链表 Linked List

数据结构 · 用指针把节点串起来的序列

什么是链表

和数组把元素挤在连续内存里不同,链表的节点散落各处,每个节点除了存值,还存一根指向下一个节点的 next 指针,像手拉手一样把它们串成一条链。开头有个 head 指针指向第一个节点,最后一个节点的 next 指向 ∅(null)表示结束。

正因为不连续,想取第 i 个节点没法直接跳——只能从 head 出发,顺着 next 一个一个走过去,所以按位置访问是 O(n)。但反过来,增删节点时只需改几根指针、谁也不用搬,是 O(1)。正好和数组互为镜像。点一个节点选中它,再操作看看。

亲手试试
head
→
1
→
2
→
3
→
∅

点一个节点选中它,再用上面的按钮操作

点中一个靠后的节点按「查找」——看游标从 head 一跳一跳走过去,走了几步才到,这就是 O(n)。再按「在其后插入」,新节点当场接进链里,只有相邻的两根 next 被改写(高亮那两根),其余节点纹丝不动,O(1);删除同理,让前一个节点的 next 直接跳过它即可。这正是数组(访问快、增删慢)与链表(访问慢、增删快)的取舍。

双向链表:再加一根 prev 指针

上面的链表每个节点只有一根 next,只能从 head往后走。给每个节点再加一根指向前一个节点的 prev 指针,就成了双向链表。

多出来的 prev 换来两件单链表做不到的事:① 反向遍历——能从 tail 沿 prev 一路往回走;②给定任意一个节点就能 O(1) 删除它——单链表只拿到一个节点,得先从 head 走 O(n) 找到它的前驱才能删;双向链表的节点自带 prev,直接 prev.next = next、next.prev = prev 两步接线就行。点「反向遍历」,或点一个节点删除看看。

亲手试试
∅
→←
head
10
→←
20
→←
30
→←
tail
40
→←
∅

双向链:每个节点都有 prev 和 next。点「反向遍历」,或点节点再删除。

代价也很实在:每个节点要多存一根指针,增删时要多维护一根。如果再把尾节点的 next 接回头、头的 prev 接到尾,链就首尾相连成了循环链表——环状结构,适合轮询调度、轮转缓冲这类「转圈」的场景。

链表在哪里用

频繁增删的场景:已知位置插入/删除只改指针,不像数组要整体搬移。
栈 / 队列的链式实现:用链表当底座,天然不受定长限制。
更复杂结构的基石:树、图的节点之间,也都是用指针(边)连接的。

链表牺牲了随机访问,换来增删的灵活。下一篇进入树——节点不再排成一条线,而是长出层次分明的分叉。