排序算法8
❶ 常见排序算法归纳
排序算法一般分类:
比较两个相邻的元素,将值大的元素交换至右端。
依次比较两个相邻的数,将小数放到前面,大数放到后面
即在第一趟:首先比较第1个数和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此一直继续下去,直到比较最后两个数,将小数放前,大数放后。然后重复第一趟步骤,直到所有排序完成。
第一趟比较完成后,最后一个数一定是数组中最大的一个数,所以第二趟比较的时候最后一个数不参与比较。
第二趟完成后,倒数第二个数也一定是数组中第二大的数,所以第三趟比较的时候最后两个数不参与比较。
依次类推......
输出结果:
冒泡排序的优点: 每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。如上例:第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,没进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。
用时间复杂度来说:
从一个数组中随机选出一个数N,通过一趟排序将数组分割成三个部分,1、小于N的区域 2、等于N的区域 3、大于N的区域,然后再按照此方法对小于区的和大于区分别递归进行,从而达到整个数据变成有序数组。
如下图:
假设最开始的基准数据为数组的第一个元素23,则首先用一个临时变量去存储基准数据,即 tmp=23 ,然后分别从数组的两端扫描数组,设两个指示标志: low 指向起始位置, high 指向末尾。
首先从后半部分开始,如果 扫描到的值大于基准数据 就让 high-1 ,如果发现有元素比该基准数据的值小,比如上面的 18 <= tmp ,就让 high位置的值赋值给low位置 ,结果如下:
然后开始从前往后扫描,如果扫描到的值小于基准数据就让 low+1 ,如果发现有元素大于基准数据的值,比如上图 46 >= tmp ,就再将 low 位置的值赋值给 high 位置的值,指针移动并且数据交换后的结果如下:
然后再开始从前往后遍历,直到 low=high 结束循环,此时low或者high的下标就是 基准数据23在该数组中的正确索引位置 ,如下图所示:
这样一遍遍的走下来,可以很清楚的知道,快排的本质就是把比基准数据小的都放到基准数的左边,比基准数大的数都放到基准数的右边,这样就找到了该数据在数组中的正确位置。
然后采用递归的方式分别对前半部分和后半部分排序,最终结果就是自然有序的了。
输出结果:
最好情况下快排每次能恰好均分序列,那么时间复杂度就是O(nlogn),最坏情况下,快排每次划分都只能将序列分为一个元素和其它元素两部分,这时候的快排退化成冒泡排序,时间复杂度为O(n^2)。
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。
将一个数据插入到 已经排好序的有序数据 中
第一趟排序:
用数组的第二个数与第一个数( 看成是已有序的数据 )比较
第二趟排序:
用数组的第三个数与已是有序的数据 {2,3} (刚才在第一趟排的)比较
在第二步中:
...
后面依此类推
输出结果:
选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
举例:数组 int[] arr={5,2,8,4,9,1}
第一趟排序 : 原始数据: 5 2 8 4 9 1
最小数据1,把1放在首位,也就是1和5互换位置,
排序结果: 1 2 8 4 9 5
第二趟排序 :
第1以外的数据 {2 8 4 9 5} 进行比较,2最小,
排序结果: 1 2 8 4 9 5
第三趟排序 :
除 1、2 以外的数据 {8 4 9 5} 进行比较,4最小,8和4交换
排序结果: 1 2 4 8 9 5
第四趟排序 :
除第 1、2、4 以外的其他数据 {8 9 5} 进行比较,5最小,8和5交换
排序结果: 1 2 4 5 9 8
第五趟排序:
除第 1、2、4、5 以外的其他数据 {9 8} 进行比较,8最小,8和9交换
排序结果: 1 2 4 5 8 9
输出结果:
归并排序(merge sort)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
比如我们对 [8,4,5,7,1,3,6,2] 这个数组进行归并排序,我们首先利用分治思想的“分”将数组拆分。
输出结果:
❷ 几种排序算法的比较
一、八大排序算法的总体比较
4.3、堆的插入:
每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,然后将这个新数据插入到这个有序数据中
(1)用大根堆排序的基本思想
先将初始数组建成一个大根堆,此对为初始的无序区;
再将最大的元素和无序区的最后一个记录交换,由此得到新的无序区和有序区,且满足<=的值;
由于交换后新的根可能违反堆性质,故将当前无序区调整为堆。然后再次将其中最大的元素和该区间的最后一个记录交换,由此得到新的无序区和有序区,且仍满足关系的值<=的值,同样要将其调整为堆;
..........
直到无序区只有一个元素为止;
4.4:应用
寻找M个数中的前K个最小的数并保持有序;
时间复杂度:O(K)[创建K个元素最大堆的时间复杂度] +(M-K)*log(K)[对剩余M-K个数据进行比较并每次对最大堆进行从新最大堆化]
5.希尔排序
(1)基本思想
先将整个待排序元素序列分割成若干子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序(因为直接插入排序在元素基本有序的情况下,效率很高);
(2)适用场景
比较在希尔排序中是最主要的操作,而不是交换。用已知最好的步长序列的希尔排序比直接插入排序要快,甚至在小数组中比快速排序和堆排序还快,但在涉及大量数据时希尔排序还是不如快排;
6.归并排序
(1)基本思想
首先将初始序列的n个记录看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2的有序子序列,在此基础上,再对长度为2的有序子序列进行两两归并,得到若干个长度为4的有序子序列,以此类推,直到得到一个长度为n的有序序列为止;
(2)适用场景
若n较大,并且要求排序稳定,则可以选择归并排序;
7.简单选择排序
(1)基本思想
第一趟:从第一个记录开始,将后面n-1个记录进行比较,找到其中最小的记录和第一个记录进行交换;
第二趟:从第二个记录开始,将后面n-2个记录进行比较,找到其中最小的记录和第2个记录进行交换;
...........
第i趟:从第i个记录开始,将后面n-i个记录进行比较,找到其中最小的记录和第i个记录进行交换;
以此类推,经过n-1趟比较,将n-1个记录排到位,剩下一个最大记录直接排在最后;
❸ 八大经典排序算法原理及实现
该系列文章主要是记录下自己暑假这段时间的学习笔记,暑期也在实习,抽空学了很多,每个方面的知识我都会另起一篇博客去记录,每篇头部主要是另起博客的链接。
冒泡排序算法应该是大家第一个接触的算法,其原理都应该懂,但我还是想以自己的语言来叙述下其步奏:
按照计算时间复杂度的规则,去掉常数、去掉最高项系数,其复杂度为O(N^2)
冒泡排序及其复杂度分析
空间复杂度就是在交换元素时那个临时变量所占的内存
给定一个整数序列{6,1,2,3,4},每完成一次外层循环的结果为:
我们发现第一次外层循环之后就排序成功了,但是还是会继续循环下去,造成了不必要的时间复杂度,怎么优化?
冒泡排序都是相邻元素的比较,当相邻元素相等时并不会交换,因此冒泡排序算法是稳定性算法
插入排序是对冒泡排序的一种改进
插入排序的思想是数组是部分有序的,再将无序的部分插入有序的部分中去,如图:
(图片来自 这里 )
空间复杂度就是在交换元素时那个临时变量所占的内存
插入排序的优化,有两种方案:
文章后面会给出这两种排序算法
由于插入排序也是相邻元素的比较,遇到相等的相邻元素时不会发生交换,也不会造成相等元素之间的相对位置发生变化
其原理是从未排序的元素中选出最小值(最大值)放在已排序元素的后面
空间复杂度就是在交换元素时那个临时变量所占的内存
选择排序是不稳定的,比如 3 6 3 2 4,第一次外层循环中就会交换第一个元素3和第四个元素2,那么就会导致原序列的两个3的相对位置发生变化
希尔排序算是改良版的插入排序算法,所以也称为希尔插入排序算法
其原理是将序列分割成若干子序列(由相隔某个 增量 的元素组成的),分别进行直接插入排序;接着依次缩小增量继续进行排序,待整个序列基本有序时,再对全体元素进行插入排序,我们知道当序列基本有序时使用直接插入排序的效率很高。
上述描述只是其原理,真正的实现可以按下述步奏来:
希尔排序的效率取决于 增量值gap 的选取,这涉及到数学上尚未解决的难题,但是某些序列中复杂度可以为O(N 1.3),当然最好肯定是O(N),最坏是O(N 2)
空间复杂度就是在交换元素时那个临时变量所占的内存
希尔排序并不只是相邻元素的比较,有许多跳跃式的比较,难免会出现相同元素之间的相对位置发生变化,所以希尔排序是不稳定的
理解堆排序,就必须得先知道什么是堆?
二叉树的特点:
当父节点的值总是大于子结点时为 最大堆 ;反之为 最小堆 ,下图就为一个二叉堆
一般用数组来表示堆,下标为 i 的结点的父结点下标为(i-1)/2;其左右子结点分别为 (2 i + 1)、(2 i + 2)
怎么将给定的数组序列按照堆的性质,调整为堆?
这里以建立最小堆为示例,
很明显对于其叶子结点来说,已经是一个合法的子堆,所以做堆调整时,子节点没有必要进行,这里只需从结点为A[4] = 50的结点开始做堆调整,即从(n/2 - 1)位置处向上开始做堆调整:
由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN),二次操作时间相加还是O(N logN)。故堆排序的时间复杂度为O(N * logN)。
空间复杂度就是在交换元素时那个临时变量所占的内存
由于堆排序也是跨越式的交换数据,会导致相同元素之间的相对位置发生变化,则算法不稳定。比如 5 5 5 ,堆化数组后将堆顶元素5与堆尾元素5交换,使得第一个5和第三个5的相对位置发生变化
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
快速排序在应该是大家经常看到、听到的算法,但是真正默写出来是有难度的。希望大家看了下面 挖坑填数 方法后,能快速写出、快速排序。
其原理就这么几句话,但是现实起来并不是这么简单,我们采取流行的一种方式 挖坑填数分治法
对于序列: 72 6 57 88 60 42 83 73 48 85
数组变为: 48 6 57 88 60 42 83 73 88 85
再重复上面的步骤,先从后向前找,再从前向后找:
数组变为: 48 6 57 42 60 72 83 73 88 85
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了
空间复杂度,主要是递归造成的栈空间的使用:
快速排序的优化主要在于基准数的选取
快速排序也是跨越式比较及交换数据,易导致相同元素之间的相对位置发生变化,所以快速排序不稳定
前面也说了二分查找排序是改进的插入排序,不同之处在于,在有序区间查找新元素插入位置时,为了减少比较次数提高效率,采用二分查找算法进行插入位置的确定
具体步骤,设数组为a[0…n]:
二分查找插入位置,因为不是查找相等值,而是基于比较查插入合适的位置,所以必须查到最后一个元素才知道插入位置。
二分查找最坏时间复杂度:当2^X>=n时,查询结束,所以查询的次数就为x,而x等于log2n(以2为底,n的对数)。即O(log2n)
所以,二分查找排序比较次数为:x=log2n
二分查找插入排序耗时的操作有:比较 + 后移赋值。时间复杂度如下:
二分查找排序在交换数据时时进行移动,当遇到有相等值插入时也只会插入其后面,不会影响其相等元素之间的相对位置,所以是稳定的
白话经典算法排序
冒泡排序选择排序
快速排序复杂度分析
优化的插入排序
❹ JS常见排序算法
排序算法说明:
(1)对于评述算法优劣术语的说明
稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序 :所有排序操作都在内存中完成;
外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度 : 一个算法执行所耗费的时间。
空间复杂度 : 运行完一个程序所需内存的大小。
(2)排序算法图片总结:
1.冒泡排序:
解析:1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2.第一轮的时候最后一个元素应该是最大的一个。
3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
2.快速排序:
解析:快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。
3.插入排序:
解析:
(1) 从第一个元素开始,该元素可以认为已经被排序
(2) 取出下一个元素,在已经排序的元素序列中从后向前扫描
(3) 如果该元素(已排序)大于新元素,将该元素移到下一位置
(4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
(5)将新元素插入到下一位置中
(6) 重复步骤2
2.二分查找:
解析:二分查找,也为折半查找。首先要找到一个中间值,通过与中间值比较,大的放又,小的放在左边。再在两边中寻找中间值,持续以上操作,直到找到所在位置为止。
(1)递归方法
(2)非递归方法
4.选择排序:
解析:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
以此类推,直到所有元素均排序完毕。
5.希尔排序:
解析:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序
6.归并排序:
解析:归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
7.堆排序:
解析:堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是
小于(或者大于)它的父节点。
8.计数排序:
解析:计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
9.桶排序:
解析:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排
10.基数排序:
解析:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优
先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶 计数排序:每个桶只存储单一键值 桶排序:每个桶存储一定范围的数值
❺ 十大经典排序算法
排序算法是《数据结构与算法》中最基本的算法之一。
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:
点击以下图片查看大图:
关于时间复杂度
平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。
关于稳定性
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
n:数据规模 k:"桶"的个数 In-place:占用常数内存,不占用额外内存 Out-place:占用额外内存 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同包含以下内容:
1、冒泡排序 2、选择排序 3、插入排序 4、希尔排序 5、归并排序 6、快速排序 7、堆排序 8、计数排序 9、桶排序 10、基数排序排序算法包含的相关内容具体如下:
冒泡排序算法
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
选择排序算法
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n?) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。
插入排序算法
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
希尔排序算法
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
归并排序算法
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
快速排序算法
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
堆排序算法
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。
计数排序算法
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
桶排序算法
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
基数排序算法
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
❻ 什么是排序法
排序法是指根据被评估员工的工作绩效进行比较,从而确定每一员工的相对等级或名次。等级或名次可从优至劣或由劣到优排列。比较标准可根据员工绩效的某一方面(如:出勤率、事故率、优质品率)确定,一般情况下是根据员工的总体工作绩效进行综合比较。
❼ 排序算法概述
十大排序算法:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序、希尔排序、计数排序,基数排序,桶排序
稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。
算法的复杂度往往取决于数据的规模大小和数据本身分布性质。
时间复杂度 : 一个算法执行所耗费的时间。
空间复杂度 :对一个算法在运行过程中临时占用存储空间大小的量度。
常见复杂度由小到大 :O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n)
在各种不同算法中,若算法中语句执行次数(占用空间)为一个常数,则复杂度为O(1);
当一个算法的复杂度与以2为底的n的对数成正比时,可表示为O(log n);
当一个算法的复杂度与n成线性比例关系时,可表示为O (n),依次类推。
冒泡、选择、插入排序需要两个for循环,每次只关注一个元素,平均时间复杂度为
(一遍找元素O(n),一遍找位置O(n))
快速、归并、堆基于分治思想,log以2为底,平均时间复杂度往往和O(nlogn)(一遍找元素O(n),一遍找位置O(logn))相关
而希尔排序依赖于所取增量序列的性质,但是到目前为止还没有一个最好的增量序列 。例如希尔增量序列时间复杂度为O(n²),而Hibbard增量序列的希尔排序的时间复杂度为 , 有人在大量的实验后得出结论;当n在某个特定的范围后希尔排序的最小时间复杂度大约为n^1.3。
从平均时间来看,快速排序是效率最高的:
快速排序中平均时间复杂度O(nlog n),这个公式中隐含的常数因子很小,比归并排序的O(nlog n)中的要小很多,所以大多数情况下,快速排序总是优于合并排序的。
而堆排序的平均时间复杂度也是O(nlog n),但是堆排序存在着重建堆的过程,它把根节点移除后,把最后的叶子结点拿上来后需要重建堆,但是,拿上的值是要比它的两个叶子结点要差很多的,一般要比较很多次,才能回到合适的位置。堆排序就会有很多的时间耗在堆调整上。
虽然快速排序的最坏情况为排序规模(n)的平方关系,但是这种最坏情况取决于每次选择的基准, 对于这种情况,已经提出了很多优化的方法,比如三取样划分和Dual-Pivot快排。
同时,当排序规模较小时,划分的平衡性容易被打破,而且频繁的方法调用超过了O(nlog n)为
省出的时间,所以一般排序规模较小时,会改用插入排序或者其他排序算法。
一种简单的排序算法。它反复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。这个工作重复地进行直到没有元素再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为元素会经由交换慢慢“浮”到数列的顶端。
1.从数组头开始,比较相邻的元素。如果第一个比第二个大(小),就交换它们两个;
2.对每一对相邻元素作同样的工作,从开始第一对到尾部的最后一对,这样在最后的元素应该会是最大(小)的数;
3.重复步骤1~2,重复次数等于数组的长度,直到排序完成。
首先,找到数组中最大(小)的那个元素;
其次,将它和数组的第一个元素交换位置(如果第一个元素就是最大(小)元素那么它就和自己交换);
再次,在剩下的元素中找到最大(小)的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。
这种方法叫做选择排序,因为它在不断地选择剩余元素之中的最大(小)者。
对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
为了给要插入的元素腾出空间,我们需要将插入位置之后的已排序元素在都向后移动一位。
插入排序所需的时间取决于输入中元素的初始顺序。例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快得多。
总的来说,插入排序对于部分有序的数组十分高效,也很适合小规模数组。
一种基于插入排序的快速的排序算法。简单插入排序对于大规模乱序数组很慢,因为元素只能一点一点地从数组的一端移动到另一端。例如,如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要N-1 次移动。
希尔排序为了加快速度简单地改进了插入排序,也称为缩小增量排序,同时该算法是突破O(n^2)的第一批算法之一。
希尔排序是把待排序数组按一定数量的分组,对每组使用直接插入排序算法排序;然后缩小数量继续分组排序,随着数量逐渐减少,每组包含的元素越来越多,当数量减至 1 时,整个数组恰被分成一组,排序便完成了。这个不断缩小的数量,就构成了一个增量序列。
在先前较大的增量下每个子序列的规模都不大,用直接插入排序效率都较高,尽管在随后的增量递减分组中子序列越来越大,由于整个序列的有序性也越来越明显,则排序效率依然较高。
从理论上说,只要一个数组是递减的,并且最后一个值是1,都可以作为增量序列使用。有没有一个步长序列,使得排序过程中所需的比较和移动次数相对较少,并且无论待排序列记录数有多少,算法的时间复杂度都能渐近最佳呢?但是目前从数学上来说,无法证明某个序列是“最好的”。
常用的增量序列
希尔增量序列 :{N/2, (N / 2)/2, ..., 1},其中N为原始数组的长度,这是最常用的序列,但却不是最好的
Hibbard序列:{2^k-1, ..., 3,1}
Sedgewick序列:{... , 109 , 41 , 19 , 5,1} 表达式为
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。
为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序。
若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。
快速排序(Quicksort)是对冒泡排序的一种改进,也是采用分治法的一个典型的应用。
首先任意选取一个数据(比如数组的第一个数)作为关键数据,我们称为基准数(Pivot),然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序,也称为分区(partition)操作。
通过一趟快速排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数组变成有序序列。
为了提升性能,有时我们在分割后独立的两部分的个数小于某个数(比如15)的情况下,会采用其他排序算法,比如插入排序。
基准的选取:最优的情况是基准值刚好取在无序区数值的中位数,这样能够最大效率地让两边排序,同时最大地减少递归划分的次数,但是一般很难做到最优。基准的选取一般有三种方式,选取数组的第一个元素,选取数组的最后一个元素,以及选取第一个、最后一个以及中间的元素的中位数(如4 5 6 7, 第一个4, 最后一个7, 中间的为5, 这三个数的中位数为5, 所以选择5作为基准)。
Dual-Pivot快排:双基准快速排序算法,其实就是用两个基准数, 把整个数组分成三份来进行快速排序,在这种新的算法下面,比经典快排从实验来看节省了10%的时间。
许多应用程序都需要处理有序的元素,但不一定要求他们全部有序,或者不一定要一次就将他们排序,很多时候,我们每次只需要操作数据中的最大元素(最小元素),那么有一种基于二叉堆的数据结构可以提供支持。
所谓二叉堆,是一个完全二叉树的结构,同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。在一个二叉堆中,根节点总是最大(或者最小)节点。
堆排序算法就是抓住了这一特点,每次都取堆顶的元素,然后将剩余的元素重新调整为最大(最小)堆,依次类推,最终得到排序的序列。
推论1:对于位置为K的结点 左子结点=2 k+1 右子结点=2 (k+1)
验证:C:2 2 2+1=5 2 (2+1)=6
推论2:最后一个非叶节点的位置为 (N/2)-1,N为数组长度。
验证:数组长度为6,(6/2)-1=2
计数排序对一定范围内的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序局限性比较大,只限于对整数进行排序,而且待排序元素值分布较连续、跨度小的情况。
计数排序是一个排序时不比较元素大小的排序算法。
如果一个数组里所有元素都是整数,而且都在0-K以内。对于数组里每个元素来说,如果能知道数组里有多少项小于或等于该元素,就能准确地给出该元素在排序后的数组的位置。
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,利用某种函数的映射关系将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序)。
桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做排序即可。
常见的数据元素一般是由若干位组成的,比如字符串由若干字符组成,整数由若干位0~9数字组成。基数排序按照从右往左的顺序,依次将每一位都当做一次关键字,然后按照该关键字对数组排序,同时每一轮排序都基于上轮排序后的结果;当我们将所有的位排序后,整个数组就达到有序状态。基数排序不是基于比较的算法。
基数是什么意思?对于十进制整数,每一位都只可能是0~9中的某一个,总共10种可能。那10就是它的基,同理二进制数字的基为2;对于字符串,如果它使用的是8位的扩展ASCII字符集,那么它的基就是256。
基数排序 vs 计数排序 vs 桶排序
基数排序有两种方法:
MSD 从高位开始进行排序
LSD 从低位开始进行排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶
计数排序:每个桶只存储单一键值
桶排序:每个桶存储一定范围的数值
有时,待排序的文件很大,计算机内存不能容纳整个文件,这时候对文件就不能使用内部排序了(我们一般的排序都是在内存中做的,所以称之为内部排序,而外部排序是指待排序的内容不能在内存中一下子完成,它需要做内外存的内容交换),外部排序常采用的排序方法也是归并排序,这种归并方法由两个不同的阶段组成:
采用适当的内部排序方法对输入文件的每个片段进行排序,将排好序的片段(成为归并段)写到外部存储器中(通常由一个可用的磁盘作为临时缓冲区),这样临时缓冲区中的每个归并段的内容是有序的。
利用归并算法,归并第一阶段生成的归并段,直到只剩下一个归并段为止。
例如要对外存中4500个记录进行归并,而内存大小只能容纳750个记录,在第一阶段,我们可以每次读取750个记录进行排序,这样可以分六次读取,进行排序,可以得到六个有序的归并段
每个归并段的大小是750个记录,并将这些归并段全部写到临时缓冲区(由一个可用的磁盘充当)内了,这是第一步的排序结果。
完成第二步该怎么做呢?这时候归并算法就有用处了。
❽ 排序算法有多少种
排序(Sorting) 是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列。
排序就是把集合中的元素按照一定的次序排序在一起。一般来说有升序排列和降序排列2种排序,在算法中有8中基本排序:
(1)冒泡排序;
(2)选择排序;
(3)插入排序;
(4)希尔排序;
(5)归并排序;
(6)快速排序;
(7)基数排序;
(8)堆排序;
(9)计数排序;
(10)桶排序。
插入排序
插入排序算法是基于某序列已经有序排列的情况下,通过一次插入一个元素的方式按照原有排序方式增加元素。这种比较是从该有序序列的最末端开始执行,即要插入序列中的元素最先和有序序列中最大的元素比较,若其大于该最大元素,则可直接插入最大元素的后面即可,否则再向前一位比较查找直至找到应该插入的位置为止。插入排序的基本思想是,每次将1个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,寻找最适当的位置,直至全部记录插入完毕。执行过程中,若遇到和插入元素相等的位置,则将要插人的元素放在该相等元素的后面,因此插入该元素后并未改变原序列的前后顺序。我们认为插入排序也是一种稳定的排序方法。插入排序分直接插入排序、折半插入排序和希尔排序3类。
冒泡排序
冒泡排序算法是把较小的元素往前调或者把较大的元素往后调。这种方法主要是通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换,就能达到排序目的。冒泡排序的基本思想是,首先将第1个和第2个记录的关键字比较大小,如果是逆序的,就将这两个记录进行交换,再对第2个和第3个记录的关键字进行比较,依次类推,重复进行上述计算,直至完成第(n一1)个和第n个记录的关键字之间的比较,此后,再按照上述过程进行第2次、第3次排序,直至整个序列有序为止。排序过程中要特别注意的是,当相邻两个元素大小一致时,这一步操作就不需要交换位置,因此也说明冒泡排序是一种严格的稳定排序算法,它不改变序列中相同元素之间的相对位置关系。
选择排序
选择排序算法的基本思路是为每一个位置选择当前最小的元素。选择排序的基本思想是,基于直接选择排序和堆排序这两种基本的简单排序方法。首先从第1个位置开始对全部元素进行选择,选出全部元素中最小的给该位置,再对第2个位置进行选择,在剩余元素中选择最小的给该位置即可;以此类推,重复进行“最小元素”的选择,直至完成第(n-1)个位置的元素选择,则第n个位置就只剩唯一的最大元素,此时不需再进行选择。使用这种排序时,要注意其中一个不同于冒泡法的细节。举例说明:序列58539.我们知道第一遍选择第1个元素“5”会和元素“3”交换,那么原序列中的两个相同元素“5”之间的前后相对顺序就发生了改变。因此,我们说选择排序不是稳定的排序算法,它在计算过程中会破坏稳定性。
快速排序
快速排序的基本思想是:通过一趟排序算法把所需要排序的序列的元素分割成两大块,其中,一部分的元素都要小于或等于另外一部分的序列元素,然后仍根据该种方法对划分后的这两块序列的元素分别再次实行快速排序算法,排序实现的整个过程可以是递归的来进行调用,最终能够实现将所需排序的无序序列元素变为一个有序的序列。
归并排序
归并排序算法就是把序列递归划分成为一个个短序列,以其中只有1个元素的直接序列或者只有2个元素的序列作为短序列的递归出口,再将全部有序的短序列按照一定的规则进行排序为长序列。归并排序融合了分治策略,即将含有n个记录的初始序列中的每个记录均视为长度为1的子序列,再将这n个子序列两两合并得到n/2个长度为2(当凡为奇数时会出现长度为l的情况)的有序子序列;将上述步骤重复操作,直至得到1个长度为n的有序长序列。需要注意的是,在进行元素比较和交换时,若两个元素大小相等则不必刻意交换位置,因此该算法不会破坏序列的稳定性,即归并排序也是稳定的排序算法。