数据结构 - 排序与查找

This post is not yet available in English. Showing the original version.

June 2, 2025

Table of Contents
Table of Contents

数据结构-排序

排序分类

稳定性分类

序列中关键字值相等的节点经过某种排序方法进行排序之后,仍能保持它们在排序前的相对顺序,则称这种排序方法是稳定的;否则,称这种排序方法是不稳定的。

内存使用情况分类

据排序实现手段分类

难易程度分类

经典排序

插入排序

基本思想:将一个记录插入到已经排好序的序列中,形成一个新的、记录数增1的有序序列 。

我们一般使用直接插入排序。将A[i]插入有序序列,让前面所有的元素比它大/小,从而得到新有序序列。

时间复杂度:平均为O(n^2) 空间复杂度: O(1)

折半插入排序

二分法进入排序,即为折半插入排序,查找位置时间复杂度为O(logn),但是元素挪动依然为O(n),所以整体的时间复杂度为O(n^2)

希尔排序

基本原理:先对所有记录按增量(d)进行分组,组内进行插入排序;然后减少增量重复上述步骤,直至增量为1 。 画线可以连接在一起的视为一个组,比如示例,57,48,66为一个组,进行比较的时候57与48交换,交换后也要考虑57和66是否可以互相交换。 image.png

时间复杂度:依赖于增量序列,没有确切结论,可以优于 O(n2),例如某些情况下可达到 O(nlog2n) 或 O(n^3/2) 。


简单选择排序

基本思想首先选出最小的项,与第一个项交换;然后在剩余项中选出次小的项,与第二个项交换;以此类推,直到整个序列有序 。 image.png

堆排序

基本思想:将待排序序列构造成一个大顶堆(或小顶堆),此时堆顶元素即为最大(或最小)值。将其与末尾元素交换,然后将剩余n-1个元素重新调整为堆,重复此过程 。 image.png image.png

过程:包括建堆和调整堆两个主要步骤 。 先看建立堆过程吧。 image.png image.png image.png image.png

性能

交换排序

核心思路:对序列中的元素进行多次两两交换,从而使序列元素有序 。

冒泡排序

基本思想:依次比较相邻两个元素,如果反序则交换,重复操作直到整个序列有序。每趟排序会将当前未排序部分的最大(或最小)元素”冒泡”到最终位置 。 image.png

image.png

性能

快速排序

基本思想:通过递归分治方法,基于轴点 (pivot) 将待排序序列拆分成两个子序列(左边元素均小于等于轴点,右边元素均大于等于轴点),然后对两个子序列分别递归排序 。

image.png

快排比较复杂,这里给一个拆分示例,轴点寻找和插入位置寻找不看图不行。(这里使用的是左右双指针法):

👇 初始数组:

[10, 20, 15, 4, 1, 9, 6]

我们使用最简单直观的 Lomuto 分区法(用最后一个元素当轴点)。每一次递归我们都分区 + 继续快排子数组。


✅ 第一次快排(全数组) 数组[10, 20, 15, 4, 1, 9, 6]
pivot(轴点)= 6
i = -1

遍历 j=0 到 j=5:

结束后,i = 1,交换 arr[2] ↔ pivot (arr[6])
→ 最终:[4, 1, 6, 10, 20, 9, 15]

现在 6 已经在正确的位置了!


🎯 快排左边 [4, 1] pivot = 1
i = -1


🎯 快排右边 [10, 20, 9, 15] pivot = 15
i = 2(6 已经在前面了) 从 j=3 开始:


🎯 快排 [10, 9] pivot = 9


✅ 最终结果:

[1, 4, 6, 9, 10, 15, 20]

性能

归并排序

分配排序

不基于“比较-移动”的排序方式。

计数排序

桶排序

基数排序

索引排序


数据结构-查找

基本查找算法

顺序查找

一个一个查,O(n)

二分查找

中位 middle=(low+high)/2 low 和 high 要随时改成 middle+1 (去右边找)或者 middle-1 (去左边找) 时间复杂度 O(logn)


索引查找(分块查找)

如何构建索引?

其实时间复杂度也是线性,但是依赖于块数据的大小与块内查找方式。


二叉查找树 (BST)

基本定义

二叉查找树或者是一棵空树; 或者是具有如下特性的二叉树:

查找

如果要在二叉查找树寻找关键值 key ,基本原理和二分查找相同。如果给定值比当前节点小则去当前节点的左子树继续寻找,大则去右子树继续寻找。

时间复杂度为 O(h),h为树的高度。基本也就是进行递归调用。

对于其时间复杂度,性能最差可能会退化到线性查找。

二叉树插入算法

注意:插入位置是节点从根节点比较一步一步确定的,小于该节点移到左侧与左节点比较,反之同理,不是随便写的。 也是利用查找,确定好要进入的树节点,从而插入值。 “插入”操作在查找不成功时才进行。如果树中存在该值,BST 不允许有重复值。

二叉查找树的建立

将根节点设置为一个空集(变成可以设定为一个无限小),然后往下查找往下画即可。

二叉查找树的删除

删除可分三种情况讨论: (1)被删除的结点是叶子 ,不用管孩子,很简单

(2)被删除的结点只有左子树或者只有右子树 ,像链表一样需要删除节点的操作

(3)被删除的结点既有左子树,也有右子树。这个比较复杂,与堆的那个操作不太一样。 相当于在序列里将其往前移动一格,拼起来。

平衡二叉树(AVL树)

基本定义

AVL树或者是一棵空树,或者是具有下列性质的二叉查找树:

构造AVL树

看个例子。所谓平衡旋转,左旋和右旋,就是:

右旋 = 左子过重时,把左儿子提上来当新根,把原根放到右边;左儿子的右子(如果有)会变成原根的左子。

左旋 = 右子过重时,把右儿子提上来当新根,把原根放到左边;右儿子的左子(如果有)会变成原根的右子。

将BST转化为AVL

平衡化旋转

失衡调整旋转平衡处理

插入

AVL树的新结点的插入过程包括两个步骤:

插入有一个递归算法,太长了…

删除

image.png


散列查找 | 哈希查找

建立映射,unordered_map的伟大无需多言。 散列查找需要建立某种哈希函数,通过某种数学方法将数据转化为哈希地址,建立一个哈希表,映射建立。 由于哈希函数是一个压缩映射,因此在一般情况下容易产生冲突,避免冲突,要么就哈希地址更复杂,要么就哈希函数更复杂。

很难找到一个不产生冲突的散列函数。一般情况下,只能选择恰当的散列函数,使冲突尽可能少地产生。

散列函数 | 哈希函数

一般来说,一个好的散列函数应满足下列两个条件:

常见的哈希函数构造方法有:

直接地址存储法

image.png

数字分析法

image.png

平方取中法

image.png

平方取中法思想:以关键字的平方值的中间几位作为存储地址。 关键字的各位都在平方值的中间几位有所贡献,Hash 值中应该有各位影子。

折叠法

image.png

除留余数法

image.png

随机数法

注意随机一定要真随机,设置好 seed 随机种子。

image.png

散列表的绘制之后补充。

红黑树

红黑树的五条核心规则

  1. 每个节点要么是红色,要么是黑色
  2. 根节点永远是黑色
  3. 所有叶子节点(NIL/空节点)都是黑色的。
  4. 红色节点的子节点必须是黑色的。(即不能有两个连续的红色节点)
  5. 从任一节点到其所有后代叶子节点的路径上,黑色节点的数量都相同。

插入策略:新插入的节点总是红色的。然后通过“变色”和“旋转”来修复可能违反的规则。

修复违规的“小抄”

当你插入一个红色新节点 N,发现它的父节点 P 也是红色时(违反了规则4),你只需要看它叔叔节点 U 的颜色