Unverified Commit ad95f595 authored by abc1763613206's avatar abc1763613206 Committed by GitHub
Browse files

Merge pull request #907 from Ir1d/master

feat: 排序算法、avl 树、最小树形图
parents aba5cdbb 3480fee9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ ofstream fout("data.out");
// data.out 就是输出文件的文件名,和可执行文件在同一目录下
```

关闭标准输入\\输出流
关闭标准输入/输出流

```cpp
fin.close();
+50 −2
Original line number Diff line number Diff line
@@ -4,7 +4,9 @@

稳定性是指相等的元素经过排序之后相对顺序是否发生了改变。

我们常用的归并排序是稳定排序,而快速排序不是稳定排序。
基数排序、计数排序(桶排序)、插入排序、冒泡排序、归并排序是稳定排序。

选择排序、堆排序、快速排序不是稳定排序。

## 时间复杂度

@@ -18,6 +20,8 @@

当然也有不是 $O(n\log n)$ 的,桶排序的时间复杂度是 $O(n)$ ,但是它是在「用空间换时间」,它的空间复杂度是 $O($ 所排序的最大数 $)$ 

当待排序的关键码序列基本有序时,插入排序最快。

## 冒泡排序

冒泡排序是一种稳定的排序方法。
@@ -44,6 +48,35 @@ void bubble_sort() {

在序列完全有序时,该算法只需遍历一遍数组,不用执行任何交换操作,时间复杂度为 $O(n)$ 。在最坏情况下,冒泡排序要执行 $(n-1) \times n/2$ 次交换操作,时间复杂度为 $O(n^2)$ 。在平均情况下,冒泡排序的时间复杂度也是 $O(n^2)$ 。

## 插入排序

插入排序依次处理待排序的记录,每个记录和之前处理过的序列中的记录进行比较,然后插入到其中适当的位置。

时间复杂度是 $O(n^2)$ 。

## Shell 排序

Shell 排序是以它的发明者命名的,也称为缩小增量排序法。Shell 排序对不相邻的记录进行比较和移动:
1\. 将待排序序列分为若干子序列(每个子序列的元素在原始数组中间距相同)
2\. 对这些子序列进行插入排序
3\. 减小每个子序列中元素之间的间距,重复上述过程直至间距减少为 1

Shell 排序的复杂度和间距序列的选取(就是间距如何减小到 1)有关,比如“间距每次除以 3”的 Shell 排序的复杂度是 $O(n^{3/2})$ 。

## 选择排序

每次找出第 i 小的记录,将这个记录与数组的第 i 个位置的记录交换。

时间复杂度为 $O(n^2)$ 

## 堆排序

对所有记录建[](/ds/heap/)

每次取出堆顶元素,就可以依次得到排好序的序列。

时间复杂度为 $O(n\log n)$ 

## 归并排序

归并排序是[分治](/basic/divide-and-conquer)地来将一个数组排序。
@@ -165,7 +198,7 @@ std::sort(a, a + n);

## 计数排序

计数排序可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。
计数排序也称桶排序,可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。

!!! warning "注"
    注意区分**基数排序**
@@ -174,6 +207,21 @@ std::sort(a, a + n);

一般考虑的是某一范围内的整数,但是计数排序也可以和[离散化](/misc/discrete)一起使用,来对浮点数、大整数进行计数排序。

## 基数排序

基數排序是将待排序码拆分成多个部分分别来比较。

按照排序码的先后顺序,分为两种:

1.  高位优先(MSD)
    先对高位排序,分成若干子序列,对每个子序列根据较低位排序……是一个分、分、……、分、收的过程。
2.  低位优先(LSD)
    从低位开始,对于排好的序列用次低位排序……(每次排序的都是全体元素)是一个分、收、分、收……分、收的过程

低位优先速度较快,便于处理,更常用。

基数排序对关键码值进行 $O(\log_r n)$ 次运算,因此处理 n 个不同的关键码时,基数排序的时间代价为 $O(n \log n)$ 。

### 参考

<http://atool.org/sort.php>ATool 的排序演示动画  
+18 −0
Original line number Diff line number Diff line
AVL 树,是一种平衡的二叉搜索树

## 性质

1.  空二叉树是一个 AVL 树
2.  如果 T 是一棵 AVL 树,那么其左右子树也是 AVL 树,并且 $|h(ls) - h(rs) \leq 1|$ ,h 是其左右子树的高度
3.  树高为 $O(\log n)$ 

平衡因子:右子树高度 - 左子树高度

## 插入结点

与 BST(二叉搜索树)中类似,先进行一次失败的查找来确定插入的位置,插入节点后根据平衡因子来决定是否需要调整。

## 删除结点

删除和 BST 类似,将结点与后继交换后再删除。

删除会导致树高以及平衡因子变化,这时需要沿着被删除结点到根的路径来调整这种变化。
+8 −0
Original line number Diff line number Diff line
@@ -16,8 +16,16 @@

### 拉链法

拉链发也称开散列法(open hashing)。

拉链法是在每个存放数据的地方开一个链表,如果有多个 key 索引到同一个地方,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其 key 与查询的 key 是否一致。如果索引的范围是 1~M,哈希表的大小为 N,那么一次插入/查询需要进行期望 $O(\frac{N}{M})$ 次比较。

### 闭散列法

闭散列方法吧所有记录直接存储在散列表中,如果发生冲突则根据某种方式继续进行探查。

比如线性探查法:如果在 `d` 处发生冲突,就依次检查 `d + 1``d+2` ……

## 实现

### 拉链法
+1 −1
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ Split-Merge Treap

如果把 Treap 变为非旋转的,我们发现可以通过可持久化**Merge****Split**操作就可以完成可持久化。

「一切可支持操作都可以通过**Merge\*\***Split\***\*Newnode\*\***Build**完成」,而**Build**操作只用于建造无需理会,**Newnode\*\*(新建节点)就是用来可持久化的工具。
「一切可支持操作都可以通过**Merge Split Newnode Build**完成」,而**Build**操作只用于建造无需理会,**Newnode**(新建节点)就是用来可持久化的工具。

我们来观察一下**Merge****Split**,我们会发现它们都是由上而下的操作!

Loading