常见排序算法 迷南。 2022-06-05 05:51 194阅读 0赞 ## 常见排序: ## ![这里写图片描述][SouthEast] **1、直接插入排序** 在给定数组中,拿出一个数M,与前面的数相比较,如果M大于前面的数,位置不变,如果小于前面的数,就依次比较,将大于M的数依次往后挪,找到M的正确排序的位置。 ![这里写图片描述][SouthEast 1] **2、希尔排序** 希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序 > 基本思想: > 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。 1、预排序—接近于有序 2、直接插入排序 -------------------- 按间距为gap = 3,进行分组,将每一组进行插入排序,然后总体进行插入排序,gap大小由数组大小决定。平均O(N^1.3) O(N^2) ![这里写图片描述][SouthEast 2] 数组越大,gap越大,数组越小,gap越小. 相比于直接插入排序,顺序情况下不如直接插入,逆序情况下效果更好。 **3、选择排序** > 基本思想: > 在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。 简单选择排序,每趟循环只能确定一个元素排序后的定位。可以改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行\[n/2\]趟循环即可。 > 选出begin和end,找出最小的(最大的)放在最左边(最右边),begin(end)++(–),还需要最大数下标max和最小数下标min **4、堆排序** 堆排序是一种树形选择排序,是对直接选择排序的有效改进。O(N\*lgN) > 初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。 因此,实现堆排序需解决两个问题: 1. 如何将n 个待排序的数建成堆; 2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。 首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。 调整小顶堆的方法: 1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶(最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。 2)将根结点与左、右子树中较小元素的进行交换。 3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2). 4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2). 5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。 称这个自根结点到叶子结点的调整过程为筛选。 再讨论对n 个元素初始建堆的过程。 建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。 1)n 个结点的完全二叉树,则最后一个结点是第\[n/2\]个结点的子树。 2)筛选从第\[n/2\]个结点为根的子树开始,该子树成为堆。 3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。 **5、冒泡排序** 每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。时间复杂度为:O(N^2) ,最好O(N) > 基本思想: > 在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。 冒泡排序代码: void BubbleSort(int *a, size_t n) { for(size_t end =n; end > 0 ; --end) { bool exchange = false; for(size_t i = 0; i < n; i++) { if(a[i-1] > a[i]) { swap(a[i-1],a[i]); exchange = true; } } if(exchange == false) { break; } } } **6、快速排序** > 基本思想: > 1)选择一个基准元素key,通常选择第一个元素或者最后一个元素 > 2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比key值小。另一部分记录的元素值比key值大。 > 3)此时key在其排好序后的正确位置 > 4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。 ![这里写图片描述][SouthEast 3] 类似于一棵树,高度与元素个数N有关,log2N 开始查找时,begin从左往右找大,end从右往左找小,将两个数值交换,相遇停止查找,停止之后再进行左右每个小部分的查找。时间复杂度为:O(N\*lgN) 但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“**三数取中法**”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。 相关面试题:![这里写图片描述][SouthEast 4] **7、归并排序** > 基本思想: > 归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。 归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要lgN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N\*lgN)。 归并排序实例:![这里写图片描述][SouthEast 5] 归并排序比较占用内存,但却是几个高效排序算法(快速排序、希尔排序、堆排序)中唯一稳定的排序方法。适合在磁盘上数据进行排序。 **8、计数排序** > 基本思想 > 假设数序列中小于元素a的个数为n,则直接把a放到第n+1个位置上。当存在几个相同的元素时要做适当的调整,因为不能把所有的元素放到同一个位置上。计数排序假设输入的元素都是0到k之间的整数。典型的应用是对年龄排序。 不能比较大小, **9、基数排序** > 算法思想: > (以整形为例),将整形10进制按每位拆分,然后从低位到高位依次比较各个位。主要分为两个过程: > (1)分配,先从个位开始,根据位值(0-9)分别放到0~9号桶中(比如64,个位为4,则放入4号桶中); > (2)收集,再将放置在0~9号桶中的数据按顺序放到数组中; > (3)重复(1)(2)过程,从个位到最高位(比如32位无符号整形最大数4294967296,最高位为第10位)。 基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。 **总结** ![这里写图片描述][SouthEast 6] 各种排序方法比较 简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。 影响排序效果的因素 因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素: ①待排序的记录数目n; ②记录的大小(规模); ③关键字的结构及其初始状态; ④对稳定性的要求; ⑤语言工具的条件; ⑥存储结构; ⑦时间和辅助空间复杂度等。 不同条件下,排序方法的选择 (1)若n较小(如n≤50),可采用直接插入或直接选择排序。 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。 (2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜; (3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或 归并排序。 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短; 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。 若要求排序稳定,则可选用归并排序。但从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。 ————————排序稳定性的分析,有助于理解掌握—————————— 首先,排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。 其次,说一下稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就 是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换 的次数可能会少一些(个人感觉,没有证实)。 回到主题,现在分析一下常见的排序算法的稳定性,每个都给出简单的理由。 (1)冒泡排序 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 (2)选择排序 选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。 (3)插入排序 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳 定的。 (4)快速排序 快速排序有两个方向,左边的i下标一直往右走,当a\[i\] <= a\[center\_index\],其中center\_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a\[j\] > a\[center\_index\]。如果i和j都走不动了,i <= j, 交换a\[i\]和a\[j\],重复上面的过程,直到i>j。 交换a\[j\]和a\[center\_index\],完成一趟快速排序。在中枢元素和a\[j\]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a\[j\]交换的时刻。 (5)归并排序 归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。 (6)基数排序 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。 (7)希尔排序(shell) 希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。 (8)堆排序 我们知道堆的结构是节点i的孩子为2\*i和2\*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。 [SouthEast]: /images/20220605/7c69c54286464918a94793e8cb42f3ea.png [SouthEast 1]: /images/20220605/95a6953c736049c3981c06fc1ad118a5.png [SouthEast 2]: /images/20220605/612b516b58614e2fa18763da76ffbebb.png [SouthEast 3]: /images/20220605/3b725eff086545f69573555e912f5c53.png [SouthEast 4]: /images/20220605/ce5addb213ad401093dccc65001ae77e.png [SouthEast 5]: /images/20220605/607a64e55991469b896bef7ae0e41667.png [SouthEast 6]: /images/20220605/c25ad5f1e9ef47c6bef314055e116db2.png
相关 [排序算法] 常见的排序算法 前言 1.冒泡排序 2.选择排序 3.插入排序 4希尔排序 5.归并排序 6.快速排序 前言 在实际需求中,我们可能要 雨点打透心脏的1/2处/ 2023年09月29日 14:29/ 0 赞/ 184 阅读
相关 算法(一)常见排序 算法-常见排序 这里写目录标题 算法-常见排序 一. 常见排序列表 二. 参考下马老师的快速排序记忆法 三. 排序 àì夳堔傛蜴生んèń/ 2022年11月14日 13:14/ 0 赞/ 103 阅读
相关 常见排序算法总结 排序算法经过了很长时间的演变,产生了很多种不同的方法。对于初学者来说,对它们进行整理便于理解记忆显得很重要。每种算法都有它特定的使用场合,很难通用。因此,我们很有必要对所有常 我会带着你远行/ 2022年08月23日 14:58/ 0 赞/ 143 阅读
相关 常见排序算法 注:点进链接查看算法具体实现!!! -------------------- 插入排序 [直接插入排序][Link 1] [希尔排序][Link 2] - 怼烎@/ 2022年06月16日 09:10/ 0 赞/ 155 阅读
相关 常见排序算法 常见排序: ![这里写图片描述][SouthEast] 1、直接插入排序 在给定数组中,拿出一个数M,与前面的数相比较,如果M大于前面的数,位置不变,如果小于前面的 迷南。/ 2022年06月05日 05:51/ 0 赞/ 195 阅读
相关 常见排序算法 常见排序算法 排序问题作为算法基础的重要组成部分,代码实现方式多种多样,但其原理是基本一致的。常见的排序算法有: ①. 冒泡排序 ②. 选择排序 ③. 插入排序 偏执的太偏执、/ 2022年05月16日 07:28/ 0 赞/ 224 阅读
相关 常见排序算法 今天实在不想刷笔试题就把常见的排序手敲了一遍 -------------------- 1.选择排序 class ChoiceSort<T extends 约定不等于承诺〃/ 2022年01月15日 13:35/ 0 赞/ 225 阅读
相关 常见排序算法 稳定排序:假设在待排序的文件中,存在两个或两个以上的记录具有相同的关键字,在用某种排序法排序后,若这些相同关键字的元素的相对次序仍然不变,则这种排序方法是稳定的。(即原本a在b 墨蓝/ 2022年01月06日 03:01/ 0 赞/ 243 阅读
还没有评论,来说两句吧...