时间复杂度:
O
(
n
2
)
O(n^2)
O(n2), 无论有序无序, 都要走
n
−
1
n-1
n−1 趟, 总共需要对比次数
1
+
⋯
+
(
n
−
1
)
=
n
(
n
−
1
)
2
1+\cdots+(n-1)=\frac{n(n-1)}{2}
1+⋯+(n−1)=2n(n−1)
空间复杂度:
O
(
1
)
O(1)
O(1)
稳定性: 不稳定
Notes: 既适用于顺序表. 也适用于链表
b. 堆排序
1. 什么是大根堆和小根堆
大根堆和小根堆都是一棵 “完全二叉树”, 其中:
大根堆: 若满足
L
(
i
)
≥
L
(
2
i
)
且
L
(
i
)
≥
L
(
2
i
+
1
(
1
≤
i
≤
n
/
2
)
L(i) \geq L(2i)且L(i) \geq L(2i+1\quad (1\leq i\leq n/2)
L(i)≥L(2i)且L(i)≥L(2i+1(1≤i≤n/2), 说人话就是, 每一个 分支节点 都要大于其 左右孩子 节点
小根堆: 满足
L
(
i
)
≤
L
(
2
i
)
且
L
(
i
)
≤
L
(
2
i
+
1
(
1
≤
i
≤
n
/
2
)
L(i) \leq L(2i)且L(i) \leq L(2i+1\quad (1\leq i\leq n/2)
L(i)≤L(2i)且L(i)≤L(2i+1(1≤i≤n/2), 每一个 分支节点 都要小于其 左右孩子 节点
2. 怎么建立大根堆
算法思想:
先将初始序列按照 树的"层次遍历" 建立一棵二叉树
依次扫描 “非终端结点”, 即在序列中编号
i
≤
⌊
n
/
2
⌋
i \leq \lfloor n/2 \rfloor
i≤⌊n/2⌋ 的结点
时间复杂度: 建"堆"时间:
O
(
n
)
O(n)
O(n) "堆"排序时间:
O
(
n
log
2
n
)
O(n\log_2n)
O(nlog2n) 总的时间复杂度:
O
(
n
)
+
O
(
n
log
2
n
)
=
O
(
n
log
2
n
)
O(n)+O(n\log_2n)=O(n\log_2n)
O(n)+O(nlog2n)=O(nlog2n)
内部归并时间: 将磁盘里的
n
n
n 个有序的块, 进行归并排序, 最终得到这
n
n
n 个块里的
4
n
4n
4n 个数字(假设一个块有4个数字)有序
优化1: 多路归并
理论: 采用多路归并可以减少归并的 “趟数”, 从而减少在磁盘来回读写的次数; 对
r
r
r 个初始归并段, 做
k
k
k 路归并, 则归并树可以用
k
叉树
k叉树
k叉树 来表示; 磁盘来回读写 I/O 的次数 = 树高
h
−
1
h-1
h−1 =
⌈
log
k
r
⌉
\lceil \log_kr \rceil
⌈logkr⌉,
k
k
k 越大,
r
r
r 越小, 读写次数就越小
代价1: 内存开销增加
代价2:
k
k
k路归并, 每挑选一个关键字就要比较
k
−
1
k-1
k−1 次, 内部归并所需时间增加
优化2: 减少初始归并段数量 上面提到, 来回读写 I/O 次数 =
h
−
1
h-1
h−1 =
⌈
log
k
r
⌉
\lceil \log_kr\rceil
⌈logkr⌉,
r
r
r 代表了初始归并段数量; 如果在 “内部排序” 阶段, 增加内存的 “输入缓冲区” 则可以生成更大的 “初始归并段”, 从而减少 “初始归并段数量”
b. 败者树
算法思想: 为了解决上述 “优化1中的代价1” 的问题, 增加
k
k
k 路合并回导致内部排序时间比较次数
k
−
1
k-1
k−1 增大, 所以通过建立 “败者树” 来解决. “败者树” 是一棵 “完全二叉树”, 将
k
k
k 个初始归并段当作叶子节点, 每次传入归并段中的一个关键字, 然后两两 “PK”, 进行决斗, 最终得出这一轮的 “优胜者” 即最小/最大 的那个数字. 然后从优胜者的那个归并段重新补充一个数字进叶子结点, 然后让它依次从下到上依次 “PK”, 值得注意的是, 实际上, 这里第二轮的"PK" 不需要达到 “
k
−
1
k-1
k−1” 次那么多.