排序(orting)相关算法(理论篇)毛利小九郎

排序(Sorting)是将一组数据序列按照关键字的非减或非增的顺序排列起来的操作。将待排序内容全部放在内存中的排序叫做内部排序,一次放不进来需要多次读取外存的叫外部排序,现在介绍内部排序。

内部排序有许多方法,常见的有:Straight Insertion Sort,Shell Sort,Quick Sort等。可以分为如下几类:

下面分别简要介绍其排序原理(无代码):

设想在打牌的时候,在牌堆里摸一张牌后,我们需要对新加入的牌和已有的牌进行比较,将新牌插入手中的牌中。插入类排序基本就是基于这样的思想,将乱序的部分看成牌堆,有序的部分看成手中的牌,然后依次从无序的中选择一个进行插入。主要有下面几种实现:

这是最简单的方法,首先,从第2个元素开始,把现在的元素左边的元素作为已经排序好的(后面的叙述中默认我们要求的排序是升序排列),如果这个元素比左边有序序列的最后一个,也就是该元素的前一个大,那么就不管它,继续向右移动,如果比有序的最后一个小,那么说明要进行插入操作,那么从有序序列的右边向左边依次比较,找到第一个比待插入元素小的元素,那么待插入元素就应该插在它的右边。依次对所有元素进行,就可以实现直接插入排序。

为了排序方便,我们常将数组的第一个元素设置成【哨兵】,即如果需要插入操作的话,首先将待插入元素放入哨兵,然后每比较一次如果比哨兵大,那么可以直接右移,因为最右边的已经在哨兵中暂存了。如此直到一个小鱼哨兵的位置,这时候我们把哨兵的元素放到它的右边就可以了。

折半插入排序是将直接插入的查找过程通过二分查找实现,利用了已经排序好的序列部分的有序的性质,可以减少比较次数,但是由于移动次数不变,因此时间复杂度和空间复杂度与直接插入一样。也是稳定排序。适合初始记录无序,n较大的情况。

希尔排序本质上是一种【分组插入排序】, 希尔排序维护一个增量序列【d1,…,dn】,每次从增量序列中递增地取一个值dk,然后以dk为间隔,在要排序的数列中取值,得到间隔为dk的一组值,然后进行插排,然后同样间隔右移一个单位,重复上述操作,等都重复完了以后,再取dk+1间隔,仍然以这样的一组为单位进行插排。因此直接插排可以看成是增量序列为【1】的特殊的Shell 排序。

可以这样理解,shell sort把数据分成了d1,d2,。。。,dn个组(间隔数就是组数,因为考虑到要移动 0 – dk-1 )然后分组插排,这样的好处就是通过大间隔的插排,使得数列已经基本有序,这样对于普通的插排可以提高效率。

增量序列的取法要满足:所有序列中的数字没有除了1以外的公因数。并且最后增量为1。

所谓交换类排序的思路是,遇到一个不符合前小后大的两个数,都进行一次交换,这样就可以把所有的都排整齐。

实际上,这种思路归结起来就是四个字:消除逆序。只要逆序全部消除,排序就完成了。最常见也是思路和实现最简单的是冒泡,bubble sort,有的地方也叫起泡排序。还有另一种特殊的复杂一点的交换方法,就是QuickSort,快排。下面分别说明:

实际上,这个排序方法就是依次进行相邻两个数的逆序消除,从无序部分的第一个到最后一个的依次遍历就是一趟,(确切的说,所谓一趟的定义是,使得有序序列中增加一个元素),因为每次这样都可以把最大的从左到右沉下去,而遍历多趟以后慢慢将小数冒出来,这就是冒泡排的名字的由来。设想如果从左到右,最大值每次都会和右边比较一下,所以它一定会一路把右边的值都推上去,从而自己沉在最下面,每次拿出一个最大值就把无序的长度减一,这样再把次大的拿出来,依次类推,即可全部排好。

冒泡是稳定的,由于两两比较,然后要比较的序列长度也是线性减少,所以明显为O(n^2),空间复杂度就是交换的那一临时变量,所以是O(1)。

QuickSort是一种较为特殊的交换排序,instead of 冒泡的相邻两两交换,快排可以一次性消除多个逆序。比如对于一个数列【4 2 3 1】,冒泡的话需要 42 43 41 才能交换到【2 3 1 4】,然后到【2 1 3 4】,再到【1 2 3 4】,而快排的想法是,如果我们直接把 1和4调换位置,那么可以只用一次,就消除了所有逆序。

快排是这样操作的,首先,选中一个数字,把它作为枢轴(pivot),将小鱼pivot的放到pivot的左边,大于的放到右边,这就是我们的目的。那么如何放呢?首先,我们维护两个指针,一个low,一个high,如果low小于high,也就是说还可以移动,那么当low指向的key小鱼pivot的话,就把low往高出移动,而同样的,如果high大于pivot,则向低处移动。这样很明显,如果都满足的话,它们会在pivot在有序数组中应在的位置相遇。那么如果不满足,即比如low指向的反而大于pivot,或者high指向的小鱼pivot,那么我们将这两个数交换,就可以消除这种逆序。

具体的实现方法是这样:首先,把最左边的元素作为pivot,然后把low放在最左边的pivot位置,high放到最右边,当然这个过程中还要维护一个pivotkey变量来暂存这个值,为交换腾出空间。这样先移动high,直到找到一个比pivotkey小的值,于是high停下来,把指向的内容赋予low,然后low开始右移,直到比pivotkey大的,low停下,把内容给high,然后high在继续。。。如此下去,知道low==high,此时的位置就是pivotkey的位置。为甚么捏?因为low移动的条件是,high指向的某个小鱼pivotkey的元素已经被交换到low此时的位置上了,所以low指向的位置小鱼pivotkey了,所以low才能走。也就是说,low移动到了某个位置,说明它前面的位置必然都小鱼pivotkey,high指针同理,所以这个相遇点就是pivot应在的位置,于是把pivotkey赋予这个位置。

实际上,每次我们都能以pivot为中心,左边小鱼pivotkey,右边大于。那么递归调用,把high用pivot,low用上次的low,以及low用pivot+1,high用上次的high。由此下去,即可得到全部的排列。

快排由于移动或者交换是非顺次交换的,所以不稳定,适合于初始无序,n较大,在平均情况下最快。

选择类排序的基本思路是:把要排序的数组的最小值选出来,放在最小的位置,然后从第二个开始,再选次小的,。。。以此下去就可以排好。

基本思路很简单,就是从左到右,依次选择后面无序序列中最小的,放在有序的后面,使得有序长度加1。具体实现是这样:首先把想要放到的位置记做i,也就是无序的第一个位置,然后在后面找到最小的返回其位置k,if not k == i,则需要把i和k对应的元素交换,两重循环下去就可以排好,外循环用来移动i,内循环找min的下标。

既然选择排序中含有选择min 的过程,因此可以就这个进行优化,如何选择最小值,不需要逐个遍历,我们可以利用之前选择最小值时得到的信息,来简化后面的选择。树形选择就是这个思想的一个实现。我们把要排序的n个数作为叶子,然后两两比较,将最小的放到上面做子树的根,这样就可以得到最小的。得到后,把最小的那个数在叶子上置为inf,从这个点网上,重新比较这一条路径,直到root,这时候就是次小,以此类推。

这个比较一次就是tree的深度,要n次比较,所以O(nlogn),但是需要额外的空间,叶子n个,所以总共node也是n的线性倍数。

堆排序利用了最大堆的调整过程,通过堆来实现在O(1)里对list进行选择排序。首先,建立最大堆,也叫大根堆,顾名思义就是根节点的key大于两个儿子的key,建立好以后,我们就得到了最大值,然后把最大值放在最后,也就是和堆的最后一个元素交换,并且不考虑这个元素,把剩下的n-1个元素重新调整为堆,依次下去,就可以得到一个排序。

堆排序的时间复杂度取决于筛选法调整堆的过程,筛选n-1次,每次都有logn的级别的操作,所以时间复杂度是O(nlogn),而空间复杂度显然是常数,就是交换根和最后一个元素的时候的中间变量。

堆排是不稳定的。

归并类排序是针对两个已经排好的序列,merge一下,重新整合成一个有序序列的过程。

利用递归,把merge sort 的过程一直下放到两两merge,然后逐层递归回来,就可以得到多个数的排序结果。

每趟归并不超过n个key,共需要logn趟,所以为O(nlogn)。归并需要中间变量,就是merge 的输出的存放的空间,所以空间为O(n)。

对于特殊情况的排序,可以突破排序的O(nlogn)的界限,达到O(n)。

先根据个位数分配,在收集,在根据十位数分配,再收集,。。。依次下去,就可以得到每个位置都是顺序的结果。利用了多关键词排序的方法。

美妙的假设被丑陋的事实扼杀,这是科学之大悲剧 —— 博物学家, 托马斯 亨利 赫胥黎

THE END
0.四重温数据结构之外部排序本文深入探讨了排序算法的核心概念,包括内部排序与外部排序,详细介绍了冒泡排序、快速排序、插入排序、选择排序、堆排序、归并排序、基数排序等经典排序算法的原理与实现,并附有代码实例。 排序在各大公司面试时是常考的类型,感觉排序没有什么特别要说明的,主要是在这些排序的叙述中需要理解“记录”、“关键字”、“一趟排序”的概念,然后大排序算法思想,在jvzquC41o0hmqp3euft/pny1nkqfa|ywf{5bt}neng5eg}fknu542@79::;
1.各种排序算法详解:从直接到外部排序,排序的基本思想 本文详细介绍了直接插入排序、折半插入排序、希尔排序、冒泡排序(包括快速排序)等常见的排序算法,以及选择排序、堆排序、归并排序和基数排序的特点和适用场景,最后提及了外部排序在处理大量数据时的应用。 插入排序。 直接插入排序。直接插入排序是一种最简单的排序方法,其基本操作是将一条记录插入到已经jvzquC41dnuh0lxfp0tfv8okplooalxfp1gsvrhng1jfvjnnu1746=948:=
2.之内排序外排序相关算法(插入排序锦标赛排序归并排序)文章浏览阅读1.1w次,点赞7次,收藏15次。本文探讨了C++中内排序算法的实现,包括插入排序、锦标赛排序和归并排序,并简单介绍了外排序的过程。对于内排序,详细讲解了每个算法的工作原理及其在C++中的应用。jvzquC41dnuh0lxfp0tfv8vsa672:>=8:1gsvrhng1jfvjnnu1>25B:885
3.《关于排序,你应该知道的》排序码(1)内部排序:内部排序是指排序期间数据元素全部存放在内存中的排序。(适合于不太大的元素序列) (内部排序的过程是一个逐步扩大记录的有序序列长度的过程。) (2)外部排序:外部排序指的是大文件的排序,即待排序的记录存储在外部存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,jvzquC41dnuh0lxfp0tfv8\wejgoiR4ctvodnn4fgvgjn|49:;9:8<=
4.七大经典排序算法排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。 排序的分类: 1) 内部排序:指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。 2) 外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。 jvzquC41fg|fnxugt0gmk‚zp0eun1jwvkerf1:7747=2
5.【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一简介: 【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理 一、排序的概念及其运用 1.1 排序的概念 排序是指使用一串记录,按照其中或某些关键字的大小,递增或递减的排序起来的操作(记录是指待排序的具体数据项)。 其中关于排序可以划分为: 外部排序:数据元素全部放在内存中的排序 内部排序:数据元素太多不能jvzquC41fg|fnxugt0gmk‚zp0eun1jwvkerf1:;394>1
6.排序根据排序时使用存储器的不同,可将排序分为本文深入探讨了排序算法的分类与原理,包括内部排序中的直接插入、折半插入、希尔排序、快速排序、选择排序、堆排序和归并排序,以及外部排序的概念。详细阐述了各种排序算法的时间复杂度、空间复杂度和稳定性,并举例说明了排序过程。这些排序方法在实际应用中各有优劣,适用于不同场景。 jvzquC41dnuh0lxfp0tfv8vsa6;79<7671gsvrhng1jfvjnnu171:A::96=
7.排序算法汇总(转载收藏)MachineLee文件初态反序时,每趟排序均要执行交换操作,中的移动次数取最大值(n-1) ; 直接选择排序的平均时间复杂度为O(n2) . 3. 直接选择排序是一个就地排序 4. 稳定性分析 直接选择排序是不稳定的. 2.2 堆排序 定义: 树形选择排序(锦标赛排序),1964年威洛姆斯(J.Willioms)提出了进一步改正的排序方法,即堆排序(Heap Sort). 堆 jvzquC41yy}/ewgnqiy/exr1iwuykjtygp5btlmkxg5329>124517865:6:177mvon
8.锦标赛排序(胜者树,记录胜者)文章浏览阅读2.2k次,点赞3次,收藏21次。本文深入探讨了锦标赛排序算法,一种高效的选择排序变种,通过构建完全二叉树并利用比较结果来减少后续查找最小元素的比较次数,实现了O(n*log2n)的时间复杂度。jvzquC41dnuh0lxfp0tfv8|gkzooa<954878;8ftvkimg8igvcomu8=826>199
9.数据结构与算法分析~笔记9内部排序数据结构内部排序知识点本文介绍了排序的基本概念,包括排序的定义、稳定性、正序逆序等,还区分了内部排序和外部排序、基于比较和不基于比较的排序。详细阐述了插入排序、交换排序、选择排序、归并排序和基数排序等内部排序方法的原理、步骤、复杂度分析及稳定性,并对各种内部排序方法进行了比较讨论。 jvzquC41dnuh0lxfp0tfv8|gkzooa=5392>788ftvkimg8igvcomu86599;57A;