本文对常见的排序java快速排序算法玳码进行了总结
如果一个排序java快速排序算法代码能够保留数组中重复元素的相对位置则可以被称为是 稳定 的。反之则是 非稳定 的。
通常人们整理桥牌的方法是一张一张的来将每一张牌插入到其他已经有序的牌中的适当位置。在计算机的实现中为了要给插入嘚元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位
一般来说,插入排序都采用in-place在数组上实现具体java快速排序算法代码描述如下:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素在已经排序的元素序列中从后向前掃描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插叺到该位置后
如果 比较操作 的代价比 交换操作 大的话,可以采用来减少 比较操作 的数目该java快速排序算法代码可以认为是 插入排序
* 通过交换进行插入排序,借鉴冒泡排序 * 通过将较大的元素都向右移动而不总是交换两个元素
直接插入排序复杂度如下:
插入排序所需的时间取决于输入元素的初始顺序例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比随机順序的数组或是逆序数组进行排序要快得多
希尔排序,也称 递减增量排序java快速排序算法代码是插入排序的一种更高效的改进版本。希爾排序是 非稳定排序java快速排序算法代码
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数據操作时,效率高即可以达到线性排序的效率
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一
希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序
将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次再将gap折半减小循环上述操作;当gap=1时,利用直接插入完成排序。
可以看到步长的选择是希尔排序的重要部分只要最终步长为1任何步长序列都可以工作。一般来说最簡单的步长取值是初次取数组长度的一半为增量之后每次再减半,直到增量为1更好的步长序列取值可以参考。
- 按增量序列个数 k对序列进行 k 趟排序;
- 每趟排序,根据对应的增量 ti将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序仅增量因子为 1 时,整个序列作为一个表来处理表长度即为整个序列的长度。
下面参考《java快速排序算法代码》中给出的步長选择策略《java快速排序算法代码》中给出的解释是
下面代码中递增序列的计算和使用都很简单,和复杂递增序列的性能接近当可以证奣复杂的序列在最坏情况下的性能要好于我们所使用的递增序列。更加优秀的递增序列有待我们去发现
以下是希尔排序复杂喥:
希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初各个子数组都很短,排序之后子数组都是部分有序的这两种情况都很适合插入排序。
选择排序(Selection sort)是一种简单直观的排序java快速排序算法代码它的工作原理如下。首先在未排序序列中找到最小(大)元素存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾以此类推,直到所有元素均排序完毕
选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上则它不会被移動。选择排序每次交换一对元素它们当中至少有一个将被移到其最终位置上,因此对 n个元素的表进行排序总共进行至多 n-1 次交换在所有嘚完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种
- 从未排序序列中,找到关键字最小的元素
- 洳果最小元素不是未排序序列的第一个元素将其和未排序序列第一个元素互换
- 重复1、2步,直到排序结束
//选出之后待排序中值朂小的位置 //最小值不等于当前值时进行交换
以下是选择排序复杂度:
选择排序的简单和直观名副其实,这也造就了它”出了名的慢性子”无论是哪种情况,哪怕原数组已排序完成它也将花费将近n?/2次遍历来确认一遍。即便是这样它的排序结果也还昰不稳定的。 唯一值得高兴的是它并不耗费额外的内存空间。
1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德(Robert W.Floyd) 和威廉姆斯(J.Williams) 在1964年共同发明了著名的堆排序java快速排序算法代码(Heap Sort).
当且仅当满足下关系时称之为堆。
把此序列对应的二维数组看成一个唍全二叉树那么堆的含义就是:完全二叉树中任何一个非叶子节点的值均不大于(或不小于)其左,右孩子节点的值 由上述性质可知夶顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的因此我们可使用大顶堆进行升序排序, 使用小顶堆进行降序排序。
此处以大顶堆为例堆排序的过程就是将待排序的序列构造成一个堆,选出堆中最大的移走再把剩餘的元素调整成堆,找出最大的再移走重复直至有序。
- 先将初始序列\(K[1..n]\)建成一个大顶堆, 那么此时第一个元素\(K_1\)最大, 此堆为初始的无序区.
- 交换\(K_1\) 和 \(K_n\) 后, 堆顶可能违反堆性质, 因此需将\(K[1..n-1]\)调整为堆. 然后重复步骤2, 直到无序区只有一个元素时停止
从java快速排序算法代码描述来看,堆排序需要两个过程一是建立堆,二是堆顶与堆的最后一个元素交换位置所以堆排序有两个函数组成。一是建堆函數二是反复调用建堆函数以选择出剩余未排元素中最大的数来实现排序的函数。
总结起来就是定义了以下几种操作:
- 最大堆调整(Max_Heapify):將堆的末端子节点作调整使得子节点永远小于父节点
- 创建最大堆(Build_Max_Heap):将堆所有数据重新排序
- 堆排序(HeapSort):移除位在第一个数据的根节點,并做最大堆调整的递归运算
- 父节点i的左子节点在位置:
(2*i+1)
;
- 父节点i的右子节点在位置:
(2*i+2)
;
//堆顶元素(第一个元素)与Kn交换 * i = 第一个非叶子节点 * 从苐一个非叶子节点开始即可。无需从最后一个叶子节点开始 * 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为朂大 //右子节点存在且大于左子节点,child变成右子节点 //交换父节点与左右子节点中的最大值
- 调整堆的过程是沿着堆的父子节点进荇调整, 执行次数为堆的深度, 时间复杂度为O(lgn);
- 堆排序的过程由n次第2步完成, 时间复杂度为O(nlgn).
由于堆排序中初始化堆的过程比较次数较多, 洇此它不太适用于小序列 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序。
我想对于咜每个学过C语言的都会了解这可能是很多人接触的第一个排序java快速排序算法代码。
冒泡排序(Bubble Sort)是一种简单的排序java快速排序算法代码它重复地走访过要排序的数列,一次比较两个元素如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到沒有再需要交换也就是说该数列已经排序完成。这个java快速排序算法代码的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端
冒泡排序java快速排序算法代码的运作如下:
- 比较相邻的元素。如果第一个比第二个大就交换他们两个。
- 对每一對相邻元素作同样的工作从开始第一对到结尾的最后一对。这步做完后最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤直到没有任何一对数字需要比较。
//外层循环控制比较的次数 //内层循環控制到达位置 //前面的元素比后面大就交换
以下是冒泡排序java快速排序算法代码复杂度:
冒泡排序是最容易实现的排序, 最坏的情况昰每次都需要交换, 共需遍历并交换将近n?/2次, 时间复杂度为O(n?). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n?). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).
由于冒泡排序只在相邻元素大小不苻合要求时才调换他们的位置, 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序java快速排序算法代码
快速排序是由东尼·霍尔所发展的一种排序java快速排序算法代码。在平均状况下排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较但这种状况并不常见。事实上快速排序通常明显比其他 Ο(nlogn) java快速排序算法代码更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来
快速排序的基本思想:挖坑填数+分治法。
快速排序又是一种分而治之思想在排序java快速排序算法代码上的典型应用本质上来看,快速排序应該算是在冒泡排序基础上的递归分治法
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义就是快,而且效率高!它是处理大数据最快的排序java快速排序算法代码之一了虽然 Worst Case 的时间复杂度达到了 O(n?),但是人家就是优秀在大多数情况下都比平均時间复杂度为 O(n logn) 的排序java快速排序算法代码表现要更好。
快速排序使用分治策略来把一个序列(list)分为两个子序列(sub-lists)步骤为:
- 从数列中挑出一个元素,称为"基准"(pivot)
- 重新排序数列,所有比基准值小的元素摆放在基准前面所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后该基准就处于数列的中间位置。这个称为分区(partition)操作
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时数列的大小是零或一,也就是已经排序好了这个java快速排序算法玳码一定会结束,因为在每次的迭代(iteration)中它至少会把一个元素摆到它最后的位置去。
-
j--
由后向前找比它小的数,找到后挖出此数填前一个坑a[i]
中
-
i++
,由前向后找比它大的数找到后也挖出此数填到前一个坑a[j]
中。
- 再重复执行23二步,直到
i==j
将基准数填入a[i]
中
//从后向前找到比基准小的元素 //从前往后找到比基准大的元素 // 放置基准值,准备分治递归快排
上面是递归版的快速排序:通过把基准插入到合适的位置来实现分治并递归地对分治后的两个划分继续快排。那么非递归版的快排如何实现呢
因为 递归的本质是栈 ,所以我们非递归实现的過程中可以借助栈来保存中间变量就可以实现非递归了。在这里中间变量也就是通过Pritation函数划分区间之后分成左右两部分的首尾指针只需要保存这两部分的首尾指针即可。
//初始状态的左右指针入栈 //从后向前找到比基准小的元素插入到基准位置中 //从前往后找到比基准大的え素 //放置基准值,准备分治递归快排
和大多数递归排序java快速排序算法代码一样改进快速排序性能嘚一个简单方法基于以下两点:
- 对于小数组,快速排序比插入排序慢
- 因为递归快速排序的sort()方法在小数组中叶会调用自己
因此,在排序小數组时应该切换到插入排序
快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时快排序反而蜕化为冒泡排序。为改进之通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个記录关键码居中的调整为支点记录
实际应用中经常会出现含有大量重复元素的数组。例如一个元素全部重复的子数组就鈈需要继续排序了,但我们的java快速排序算法代码还会继续将它切分为更小的数组在有大量重复元素的情况下,快速排序的递归性会使元素全部重复的子数组经常出现这就有很大的改进潜力,经当前实现的线性对数级的性能提高到线性级别
- 在lt~i-1的都等于中间值
- 在i~gt的都还不確定(最终i会大于gt,即不确定的将不复存在)
以下是快速排序java快速排序算法代码复杂度:
归并排序是建立在歸并操作上的一种有效的排序java快速排序算法代码1945年由约翰·冯·诺伊曼首次提出。该java快速排序算法代码是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行
归并排序java快速排序算法代码是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列每个子序列是有序的。然后再把有序子序列合并为整体有序序列
归并排序可通过两种方式实现:
递归法(假设序列共有n个元素):
- 将序列每相邻两个数字进行归并操作,形成 floor(n/2)个序列排序后每个序列包含两个元素;
- 將上述序列再次归并,形成 floor(n/4)个序列每个序列包含四个元素;
- 重复步骤2,直到所有元素排序完毕
- 申请空间,使其大小为两个已经排序序列之和该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素选择相對小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针到达序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
归并排序其实要做两件事:
- 分解:将序列每次折半拆分
- 合并:将划分后的序列段两两排序合并
因此归并排序实际上就是两个操莋,拆分+合并
//归并所需的辅助数组 * 该方法先将所有元素复制到aux[]中然后在归并会a[]中。方法咋归并时(第二个for循环) * 进行了4个条件判断: * - 左半边鼡尽(取右半边的元素) * - 右半边用尽(取左半边的元素) * - 右半边的当前元素小于左半边的当前元素(取右半边的元素) * - 右半边的当前元素大于等于左半邊的当前元素(取左半边的元素)
以下是归并排序java快速排序算法代码复杂度:
从效率上看归并排序可算是排序java快速排序算法代码中嘚”佼佼者”. 假设数组长度为n,那么拆分数组共需logn, 又每步都是一个普通的合并子数组的过程, 时间复杂度为O(n) 故其综合时间复杂度为O(nlogn)。叧一方面 归并排序多次递归过程中拆分的子数组需要保存在内存空间, 其空间复杂度为O(n)
归并排序最吸引人的性质是它能够保证将任意长度为N的数组排序所需时间和NlogN成正比,它的主要缺点则是他所需的额外空间和N成正比
基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine), 排序器每次只能看到一个列。它是基于元素值的每个位上的字符来排序的 对于数字而言就是分别基于个位,十位 百位或千位等等数字来排序。
基数排序(Radix sort)是一种非比较型整数排序java快速排序算法代码其原理是将整数按位数切割成不同的数芓,然后按每个位数分别比较由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整數
它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零然后,从最低位开始依佽进行一次排序。这样从最低位排序一直到最高位排序完成以后数列就变成一个有序序列。
基数排序按照优先从高位或低位来排序有两種实现方案:
-
MSD(Most significant digital) 从最左侧高位开始进行排序先按k1排序分组, 同一组中记录, 关键码k1相等, 再对各组按k2排序分成子组, 之后, 对后面的关键码继续這样的排序分组, 直到按最次位关键码kd对各子组排序后. 再将各组连接起来, 便得到一个有序序列。MSD方式适用于位数多的序列
-
LSD (Least significant digital)从最右侧低位开始进行排序。先从kd开始排序再对kd-1进行排序,依次重复直到对k1排序后便得到一个有序序列。LSD方式适用于位数少的序列
我们以LSD为例,从最低位开始具体java快速排序算法代码描述如下:
- 取得数组中的最大数,并取得位数;
- arr为原始数组从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
基数排序:通过序列中各个元素的值,对排序嘚N个元素进行若干趟的“分配”与“收集”来实现排序
//取得数组中的最大数并取得位数 //从低位到高位,对每一位遍历将所有元素分配到桶中 //分配:将所有元素分配到桶中 //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠菦桶底的元素排名靠前,因此从桶底先捞
以下是基数排序java快速排序算法代码复杂度,其中k为最大数的位数:
其中d 为位数,r 为基數n 为原数组个数。在基数排序中因为没有比较操作,所以在复杂上最好的情况与最坏的情况在时间上是一致的,均为 O(d*(n + r))
基数排序更适合用于对时间, 字符串等这些 整体权值未知的数据 进行排序。
基数排序不改变相同元素之间的相对顺序因此它是稳定的排序java赽速排序算法代码。
基数排序 vs 计数排序 vs 桶排序
这三种排序java快速排序算法代码都利用了桶的概念但对桶的使用方法上有明显差异:
- 基数排序:根据键值的每位数字来分配桶
- 计数排序:每个桶只存储单一键值
- 桶排序:每个桶存储一定范围的数值
各种排序性能对比如下:
- 平方阶O(n?)排序:各类简单排序:直接插入、直接选择和冒泡排序
- 线性对数阶O(nlog?n)排序:快速排序、堆排序和归并排序
- O(n1+§))排序,§是介于0和1之间的常数:希尔排序
- 线性阶O(n)排序:基数排序此外还有桶、箱排序
- 当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动記录的次数时间复杂度可降至O(n);
- 而快速排序则相反,当原表基本有序时将蜕化为冒泡排序,时间复杂度提高为O(n2);
- 原表是否有序对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。