Loading docs/basic/file-operation.md +1 −1 Original line number Diff line number Diff line Loading @@ -83,7 +83,7 @@ ofstream fout("data.out"); // data.out 就是输出文件的文件名,和可执行文件在同一目录下 ``` 关闭标准输入\\输出流 关闭标准输入/输出流 ```cpp fin.close(); Loading docs/basic/sort.md +50 −2 Original line number Diff line number Diff line Loading @@ -4,7 +4,9 @@ 稳定性是指相等的元素经过排序之后相对顺序是否发生了改变。 我们常用的归并排序是稳定排序,而快速排序不是稳定排序。 基数排序、计数排序(桶排序)、插入排序、冒泡排序、归并排序是稳定排序。 选择排序、堆排序、快速排序不是稳定排序。 ## 时间复杂度 Loading @@ -18,6 +20,8 @@ 当然也有不是 $O(n\log n)$ 的,桶排序的时间复杂度是 $O(n)$ ,但是它是在「用空间换时间」,它的空间复杂度是 $O($ 所排序的最大数 $)$ 当待排序的关键码序列基本有序时,插入排序最快。 ## 冒泡排序 冒泡排序是一种稳定的排序方法。 Loading @@ -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)地来将一个数组排序。 Loading Loading @@ -165,7 +198,7 @@ std::sort(a, a + n); ## 计数排序 计数排序可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。 计数排序也称桶排序,可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。 !!! warning "注" 注意区分**基数排序** Loading @@ -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 的排序演示动画 Loading docs/ds/avl.md +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 类似,将结点与后继交换后再删除。 删除会导致树高以及平衡因子变化,这时需要沿着被删除结点到根的路径来调整这种变化。 docs/ds/hash.md +8 −0 Original line number Diff line number Diff line Loading @@ -16,8 +16,16 @@ ### 拉链法 拉链发也称开散列法(open hashing)。 拉链法是在每个存放数据的地方开一个链表,如果有多个 key 索引到同一个地方,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其 key 与查询的 key 是否一致。如果索引的范围是 1~M,哈希表的大小为 N,那么一次插入/查询需要进行期望 $O(\frac{N}{M})$ 次比较。 ### 闭散列法 闭散列方法吧所有记录直接存储在散列表中,如果发生冲突则根据某种方式继续进行探查。 比如线性探查法:如果在 `d` 处发生冲突,就依次检查 `d + 1` , `d+2` …… ## 实现 ### 拉链法 Loading docs/ds/persistent-balanced.md +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
docs/basic/file-operation.md +1 −1 Original line number Diff line number Diff line Loading @@ -83,7 +83,7 @@ ofstream fout("data.out"); // data.out 就是输出文件的文件名,和可执行文件在同一目录下 ``` 关闭标准输入\\输出流 关闭标准输入/输出流 ```cpp fin.close(); Loading
docs/basic/sort.md +50 −2 Original line number Diff line number Diff line Loading @@ -4,7 +4,9 @@ 稳定性是指相等的元素经过排序之后相对顺序是否发生了改变。 我们常用的归并排序是稳定排序,而快速排序不是稳定排序。 基数排序、计数排序(桶排序)、插入排序、冒泡排序、归并排序是稳定排序。 选择排序、堆排序、快速排序不是稳定排序。 ## 时间复杂度 Loading @@ -18,6 +20,8 @@ 当然也有不是 $O(n\log n)$ 的,桶排序的时间复杂度是 $O(n)$ ,但是它是在「用空间换时间」,它的空间复杂度是 $O($ 所排序的最大数 $)$ 当待排序的关键码序列基本有序时,插入排序最快。 ## 冒泡排序 冒泡排序是一种稳定的排序方法。 Loading @@ -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)地来将一个数组排序。 Loading Loading @@ -165,7 +198,7 @@ std::sort(a, a + n); ## 计数排序 计数排序可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。 计数排序也称桶排序,可以在 $O(n)$ 的时间内排序,但是它要求所有的数都出现在一定的范围内。 !!! warning "注" 注意区分**基数排序** Loading @@ -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 的排序演示动画 Loading
docs/ds/avl.md +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 类似,将结点与后继交换后再删除。 删除会导致树高以及平衡因子变化,这时需要沿着被删除结点到根的路径来调整这种变化。
docs/ds/hash.md +8 −0 Original line number Diff line number Diff line Loading @@ -16,8 +16,16 @@ ### 拉链法 拉链发也称开散列法(open hashing)。 拉链法是在每个存放数据的地方开一个链表,如果有多个 key 索引到同一个地方,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其 key 与查询的 key 是否一致。如果索引的范围是 1~M,哈希表的大小为 N,那么一次插入/查询需要进行期望 $O(\frac{N}{M})$ 次比较。 ### 闭散列法 闭散列方法吧所有记录直接存储在散列表中,如果发生冲突则根据某种方式继续进行探查。 比如线性探查法:如果在 `d` 处发生冲突,就依次检查 `d + 1` , `d+2` …… ## 实现 ### 拉链法 Loading
docs/ds/persistent-balanced.md +1 −1 Original line number Diff line number Diff line Loading @@ -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