diff --git a/Data-Structrue/10-sort.md b/Data-Structrue/10-sort.md index 52be94f..2947504 100644 --- a/Data-Structrue/10-sort.md +++ b/Data-Structrue/10-sort.md @@ -11,7 +11,7 @@ ## 插入排序 -插入排序就是将选定的目标值插入到对应的位置。 +分为非排序和已排序序列。插入排序就是将选定的目标值插入到对应的位置。 ### 直接插入排序 @@ -83,6 +83,8 @@ ### 冒泡排序 +重点就是相邻两两比较。 + #### 冒泡排序的过程 从后往前或从前往后两两比较相邻元素的值,若逆序则交换这两个值,如果相等也不交换,直到序列比较完。这个过程是一趟冒泡排序,第$i$趟后第$i$个元素会已经排序完成。每一趟都会让关键字最小或最大的一个元素到未排序队列的第一个或最后一个。一共需要$n-1$趟排序。 @@ -103,15 +105,59 @@ 冒泡排序可以用于链表。 +#### 冒泡优化 + +对于每行冒泡进行优化:如果发现排序前几轮就已经实现了排序成功,那么后面的排序岂不是都浪费了时间进行比较?可以在第一轮循环中设置一个布尔值为false,如果在这一轮发生排序交换就设置为true,如果一轮结束后发现这个值还是false,说明这一轮没有进行交换,表示已经排序成功,就直接所有退出循环。 + +对于每列冒泡进行优化:默认每一轮冒泡是从$[length-i]$结束,如一共5个元素排序,需要4轮排序,第二轮冒泡排序应该从0开始,到3结束,因为最后一个元素4已经在第一轮排序成功。但是如果在第二轮发现2,3已经排序成功了不需要交换,那么默认排序方法第三轮还是要从0到2进行排序,还要比较一次1和2位置的数据,这就造成了浪费,那么如何解决?记录每一轮发生比较的元素的最大索引值,下一轮比较到这个索引值直接结束,不需要继续比较后面的元素。如果最大索引值为0则直接退出。这就进一步优化了上面一种策略。 + +```c +void Bubble(int[] a) { + int n = a.length - 1; + while (true) + { + //表示最后一次交换元素位置 + int last = 0; + for (int i = 0; i < n; i++) + { + if (a[i] > a[i + 1]) { + swap(a, i, i + 1); + last = i; + } + } + n = last; + if (n == 0) + { + return; + } + } +} +``` + ### 快速排序 快速排序在内部排序中的表现确实是最好的。排序过程类似于构建二叉排序树。基于分治法。 #### 快速排序的过程 -取待排序序列中的某个元素$pivot$作为基准(一般取第一个元素),通过一趟排序,将待排元素分为左右两个子序列,左子序列元素的关键字均小于或等于基准元素的关键字,右子序列的关键字则大于基准元素的关键字,称进行了一趟快速排序(一次划分),然后分别对两个子序列继续进行排序,直至整个序列有序。 +取待排序序列中的某个元素$pivot$作为基准(一般取第一个元素),通过一趟排序,将待排元素分为左右两个子序列,左子序列元素的关键字均小于或等于基准元素的关键字,右子序列的关键字则大于基准元素的关键字,称进行了一趟快速排序(一次划分),这个$pivot$已经成功排序。然后分别对两个子序列继续进行排序,直至整个序列有序。 -1. 先选择个值做标杆,把标杆放入$pivot$,比标杆小的放左边,大的放右边。 +#### 单边循环快排 + +即$lomuto$洛穆托分区方案。 + +1. 选择最右边的元素值做标杆,把标杆值放入$pivot$变量中。 +2. 初始时,令$low$和$high$都指向最左边的元素。其中$low$用于被动向右移动,维护小于标杆值的元素的边界,即每次交换的目标索引,一旦交换$low$就向右移动一个;$high$用于主动向右移动,寻找比标杆值小的元素,一旦找到就与$low$指向元素进行交换。 +3. 然后$high$开始移动,判断$high$指向的元素值是否小于$pivot$值,如果不小于就继续向右移动。 +4. 当遇到比标杆小的值,$high$指向的值就和$low$指向的值进行交换,交换成功后$low$右移一个,$high$继续右移查找。 +5. $high$继续移动,最后$high=pivot$时将基准点元素值与$low$指向值进行交换,该轮排序结束。此时$low$指向的位置就是$pivot$值所应该在的位置。 +6. 返回基准点元素所在索引,从而确定排序上下边界,递归继续执行排序。 + +#### 双边循环快排 + +分为普通分区方案和$hoare$霍尔分区方案。逻辑基本上一样,只是边界选择方式不同。 + +1. 先选择个值做标杆,把标杆值放入$pivot$变量中。 2. 初始时,令$high$指向序列最右边的值,$low$指向序列最左边的值。 3. 然后从$high$开始,当遇到比标杆大的值时$high--$。 4. 当遇到比标杆小的值,就取出这个值放入当前$low$所指的位置。 @@ -120,7 +166,7 @@ 7. 若$low$所指向的值比标杆大,则放入当前$high$所指向的位置。 8. 然后$low$不动,$high--$开始移动。回到步骤三开始执行。 9. 当$low=high$时表示$low$和$high$之前的元素都比基准小,$low$和$high$之后的元素都比基准大,完成了一次划分。然后把基准元素放入$low$和$high$指向的位置。 -10. 不断交替使用$low$和$high$指针进行对比。对左右子序列进行同样的递归操作即可,从步骤三开始。若左右两个子序列的元素数量等于一,则无需再划分。 +10. 不断交替使用$low$和$high$指针进行对比。对左右子序列进行同样的递归操作即可,从步骤三开始。若左右两个子序列的元素数量小等于一,则无需再划分。 即对序列进行比较,有头尾两个指针,尾指针开始比较向前移动,若指向值比对比值小则要交换,交替让头指针开始移动,否则不改变指针则尾指针继续向前;同理头指针向后移动,若指向值比对比值大则交换,交替让尾指针移动,否则不改变指针则头指针继续向后。最后头尾指针指向一个位置,将对比值插入到当前值,此时一趟完成。 @@ -146,7 +192,7 @@ ## 选择排序 -选择排序就是每一趟在待排序元素中选取关键字最小或最大的元素加入有序子序列。 +分为已排序和未排序序列。选择排序就是每一趟在待排序元素中选取关键字最小或最大的元素加入有序子序列。 ### 简单选择排序 @@ -164,6 +210,35 @@ 简单选择排序也可以适用于链表。 +#### 直接插入排序与简单选择排序 + +插入排序和选择排序都是分为未排序和已排序两个部分,那么其中有什么区别? + +如18、23、19、9、23*、15进行排序。 + +18 23 19 9 23* 15 + +插入排序: + +```txt +18 23 19 9 23* 15 +18 19 23 9 23* 15 +9 18 19 23 23* 15 +9 18 19 23 23* 15 +9 15 18 19 23 23* +``` + +选择排序: + +```txt +9 23 19 18 23* 15 +9 15 19 18 23* 23 +9 15 18 19 23* 23 +9 15 18 19 23* 23 +9 15 18 19 23* 23 +9 15 18 19 23* 23 +``` + ### 堆排序 #### 堆的定义 diff --git a/Data-Structrue/9-search-ex.md b/Data-Structrue/9-search-ex.md index 2aada21..79c8a3f 100644 --- a/Data-Structrue/9-search-ex.md +++ b/Data-Structrue/9-search-ex.md @@ -18,6 +18,8 @@ $D.$无法确定 ### 折半查找 +#### 比较次数 + **例题** 已知一个有序表$(13,18,24,35,47,50,62,83,90,115,134)$,当二分查找值为$90$的元素时,查找成功的比较次数为()。 $A.1$ @@ -30,6 +32,12 @@ $D.6$ 解:$B$。开始时$low$指向$13$,$high$指向$134$,$mid$指向$50$,比较第一次$90>50$,所以将$low$指向$62$,$high$指向$134$,$mid$指向$90$,第二次比较找到$90$。 +#### 最多比较次数 + +计算查找次数,特别是最多查找次数,就找二叉树的层数,对总数以2取对数向上取整,即为最多查找次数。 + +#### 平均查找长度 + **例题** 具有$12$个关键字的有序表中,对每个关键字的查找概率相同,折半查找算法查找成功的平均查找长度为(),折半查找查找失败的平均查找长度为()。 $A.37/12$ diff --git a/Data-Structrue/9-search.md b/Data-Structrue/9-search.md index 31c0050..7865e47 100644 --- a/Data-Structrue/9-search.md +++ b/Data-Structrue/9-search.md @@ -48,7 +48,18 @@ $ASL$查找成功为$\sum\limits_{i=1}^nP_i(n-i+1)$,$P_i$为第$i$个元素出 #### 折半查找的过程 -查找时,首先计算出$mid$判断是否相等,若查找值小于$mid$的值,则将$high$赋值为$mid-1$的值,若查找值大于$mid$,则将$low$赋值为$mid+1$的值,重新计算$mid$。这样就可以不断二分区间来查找,从而加快迭代。当查找最后$low>high$则查找失败。 +1. 定义左边界$low$,默认为0,右边界$high$,默认为$length-1$,循环执行折半查找(2,3两步)。 +2. 计算出$mid=\lfloor(low+high)\div2\rfloor$。 +3. 判断中间索引值$data\lbrack mid\rbrack$是否与搜索值$target$相等。 + + 若$data\lbrack mid\rbrack=target$,返回中间索引。 + + 若$data\lbrack mid\rbracktarget$,则将$low=mid+1$的值。 +4. 当查找最后$low>high$则查找失败。 + +在对$mid$进行取值时,如果数据量太大,查找到右侧时计算$mid$进行两数相加$low+high$可能会数值溢出。那么如何解决? + ++ 变幻公式:$(low+high)\div2\rightarrow low\div2+high\div2$或$\rightarrow low-(low\div2-high\div2)\rightarrow low+(high-low)\div2$。 ++ 无符号右移运算:$mid=(low+hight) >>> 1$。直接讲除以2变为右移运算,速度更快,且舍去了小数位不需要进行取整运算。 $ASL$查找成功为$\dfrac{1+2+3+\cdots+n}{n}=\dfrac{n+1}{2}$,$ASL$查找失败为$n+1$,时间复杂度为$O(n)$。