技术心得记录:常见的比较排序

简介: 技术心得记录:常见的比较排序

一、冒泡排序(Bubble Sort)


【原理】


  比较两个相邻的元素,将值大的元素交换至右端。


【思路】


  依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。


  第一趟比较完成后,最后一个数一定是数组中最大的一个数,所以第二趟比较的时候最后一个数不参与比较;


  第二趟比较完成后,倒数第二个数也一定是数组中第二大的数,所以第三趟比较的时候最后两个数不参与比较;


  依次类推,每一趟比较次数-1;


  ……


【举例】——要排序数组:int【】 arr={6,3,8,2,9,1};


  第一趟排序:


    第一次排序:6和3比较,6大于3,交换位置: 3 6 8 2 9 1


    第二次排序:6和8比较,6小于8,不交换位置:3 6 8 2 9 1


    第三次排序:8和2比较,8大于2,交换位置: 3 6 2 8 9 1


    第四次排序:8和9比较,8小于9,不交换位置:3 6 2 8 9 1


    第五次排序:9和1比较:9大于1,交换位置: 3 6 2 8 1 9


    第一趟总共进行了5次比较, 排序结果: 3 6 2 8 1 9


  ---------------------------------------------------------------------


  第二趟排序:


    第一次排序:3和6比较,3小于6,不交换位置:3 6 2 8 1 9


    第二次排序:6和2比较,6大于2,交换位置: 3 2 6 8 1 9


    第三次排序:6和8比较,6大于8,不交换位置:3 2 6 8 1 9


    第四次排序:8和1比较,8大于1,交换位置: 3 2 6 1 8 9


    第二趟总共进行了4次比较, 排序结果: 3 2 6 1 8 9


  ---------------------------------------------------------------------


  第三趟排序:


    第一次排序:3和2比较,3大于2,交换位置: 2 3 6 1 8 9


    第二次排序:3和6比较,3小于6,不交换位置:2 3 6 1 8 9


    第三次排序:6和1比较,6大于1,交换位置: 2 3 1 6 8 9


    第二趟总共进行了3次比较, 排序结果: 2 3 1 6 8 9


  ---------------------------------------------------------------------


  第四趟排序:


    第一次排序:2和3比较,2小于3,不交换位置:2 3 1 6 8 9


    第二次排序:3和1比较,3大于1,交换位置: 2 1 3 6 8 9


    第二趟总共进行了2次比较, 排序结果: 2 1 3 6 8 9


  ---------------------------------------------------------------------


  第五趟排序:


    第一次排序:2和1比较,2大于1,交换位置: 1 2 3 6 8 9


    第二趟总共进行了1次比较, 排序结果: 1 2 3 6 8 9


  ---------------------------------------------------------------------


  最终结果:1 2 3 6 8 9


  ---------------------------------------------------------------------


  由此可见:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数,即


for(int i=1;i

for(int j=1;j

//交换位置


}


  冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。如上例:第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,每进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。


  用时间复杂度来说:


  1.如果我们的数据正序,只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)。


  2.如果很不幸我们的数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:


  冒泡排序的最坏时间复杂度为:O(n2) 。


  综上所述:冒泡排序总的平均时间复杂度为:O(n2) 。


【代码实现】


public class BubbleSort {


public static void main(String【】 args) {


int【】 arr = {6, 3, 8, 2, 9, 1};


System.out.println("排序前数组:");


for (int num : arr) {


System.out.println(num + " ");


}


for (int i = 0; i < arr.length - 1; i++) {//外层循环控制排序趟数


for (int j = 0; j < arr.length - 1 - i; j++) {//内层循环控制每一趟排序多少次


if (arr【j】 > arr【j + 1】) {


swap(arr, j, j + 1);


}


}


}


System.out.println("------------");


System.out.println("排序后数组:");


for (int num : arr) {


System.out.println(num + " ");


}


}


public static void swap(int【】 arr, int i, int j) {


int tmp = arr【i】;


arr【i】 = arr【j】;


arr【j】 = tmp;


}


}


二、选择排序(SelectionSort)


【原理】


   每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。


【基本思想】(简单选择排序)


  给定数组:int【】 arr={里面n个数据};第1趟排序,在待排序数据arr【1】~arr【n】中选出最小的数据,将它与arrr【1】交换;第2趟,在待排序数据arr【2】~arr【n】中选出最小的数据,将它与arr【2】交换;以此类推,第i趟在待排序数据arr【i】~arr【n】中选出最小的数据,将它与arr【i】交换,直到全部排序完成。


【举例】——数组 int【】 arr={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


  -------------------------------------------------------


  注:每一趟排序获得最小数的方法:for循环进行比较,定义一个第三个变量temp,首先前两个数比较,把较小的数放在temp中,然后用temp再去跟剩下的数据比较,如果出现比temp小的数据,就用它代替temp中原有的数据。


【代码实现】


public class SelectionSort {


public static void main(String【】 args) {


int【】 arr = {5, 2, 8, 4, 9, 1};


System.out.println("交换之前:");


for (int num : arr) {


System.out.print(num + " ");


}


// 做第i趟排序


for (int i = 0; i < arr.length - 1; i++) {


int minIndex = i;


// 选最小的记录


for (int j = i + 1; j < arr.length; j++) {


//记下目前找到的最小值所在的位置


minIndex = arr【j】 < arr【minIndex】 ? j : minIndex;


}


swap(arr, i, minIndex);


}


System.out.println();


System.out.println("交换后:");


for (int num : arr) {


System.out.print(num + " ");


}


}


public static void swap(int【】 arr, int i, int j) {


int tmp = arr【i】;


arr【i】 = arr【j】;


arr【j】 = tmp;


}


}


  选择排序的时间复杂度:简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有n个元素,则比较次数永远都是n (n - 1) / 2。而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0。当序列反序时,移动次数最多,为3n (n - 1) / 2。


  所以,综上,简单排序的时间复杂度为 O(n2)。


三、插入排序(Insertion sort)


  插入排序对于少量元素的排序是很高效的,而且这个排序的手法在每个人生活中也是有的哦。你可能没有意识到,当你打牌的时候,就是用的插入排序。


【概念】


  从桌上的牌堆摸牌,牌堆内是杂乱无序的,但是我们摸上牌的时候,却会边摸边排序,借用一张算法导论的图。


  每次我们从牌堆摸起一张牌,然后将这张牌插入我们左手捏的手牌里面,在插入手牌之前,我们会自动计算将牌插入什么位置,然后将牌插入到这个计算后的位置,虽然这个计算转瞬而过,但我们还是尝试分析一下这个过程:


我决定摸起牌后,最小的牌放在左边,摸完后,牌面是从左到右依次增大


摸起第1张牌,直接捏在手里,现在还不用排序


摸起第2张牌,查看牌面大小,如果第二张牌比第一张牌大,就放在右边


摸起第3张牌,从右至左开始计算,先看右边的牌,如果摸的牌比最右边的小,那再从右至左看下一张,如果仍然小,继续顺延,直到找到正确位置(循环)


摸完所有的牌,结束


  所以我们摸完牌,牌就已经排完序了。讲起来有点拗口,但是你在打牌的时候绝对不会觉得这种排序算法会让你头疼。这就是传说中的插入排序。


  想象一下,假如我们认为左手拿的牌和桌面的牌堆就是同一数组,当我们摸完牌以后,我们就完成了对这个数组的排序。


【示例】


  上图就是插入排序的过程,我们把它想象成摸牌的过程。


  格子上方的数字:表示格子的序号,图(a)中,1号格子内的数字是5,2号格子是2,3号格子是4,以此类推


  灰色格子:我们手上已经摸到的牌


  黑色格子:我们刚刚摸起来的牌


  白色格子:桌面上牌堆的牌


  1、图(a),我们先摸起来一张5,然后摸起来第二张2,发现2比5小,于是将5放到2号格子,2放到1号格子(简单的人话:将2插到5前面)


  2、图(b),摸起来一张4,比较4和2号格子内的数字5,4比5小,于是将5放到3号格子,再比较4和1号格子内的2,4大于2,4小于5,于是这就找到了正确的位置。(说人话:就是摸了张4点,将4和5交换位置)


  3、图(c)、图(d)、图(e)和图(f),全部依次类推,相信打牌的你能够看懂。


  看到这里,我相信应该没人看不懂什么是插入排序了,那么插入排序的代码长什么模样:


【代码实现】


public class InsertionSort {


public static void main(String【】 args) {


int【】 arr = {1, 2, 3, 7, 5, 2, 3, 3, 1};


System.out.println("排序前:");


for (int num : arr) {


System.out.print(num + " ");


}


//插入排序


for (int i = 1; i < arr.length; i++) {


for (int j = i - 1; j >= 0 && arr【j】 > arr【j + 1】; j--) {


swap(arr, j, j + 1);


}


}


System.out.println();


System.out.println("排序后:");


for (int num : arr) {


System.out.print(num + " ");


}


}


public static void swap(int【】 arr, int i, int j) {


int tmp = arr【i】;


arr【i】 = arr【j】;


arr【j】 = tmp;


}


}


【时间复杂度】


最好情况下,数组已经是有序的,每插入一个元素,只需要考查前一个元素,因此最好情况下,插入排序的时间复杂度为O(N)。


在最坏情况下,数组完全逆序,插入第2个元素时要考察前1个元素,插入第3个元素时,要考虑前2个元素,……,插入第N个元素,要考虑前 N - 1 个元素。因此,最坏情况下的比较次数是 1 + 2 + 3 + ... + (N - 1),等差数列求和,结果为 N2 / 2,所以最坏情况下的复杂度为 O(N2)。


  当数据状况不同,产生的算法流程不同的时候,一律按最差的估计,所以插入排序是O(N2)的算法。


四、归并排序(MERGE-SORT)


【基本思想】


  归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。


  可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。


【合并相邻有序子序列】


再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将【4,5,7,8】和【1,2,3,6】两个已经有序的子序列,合并为最终序列【1,2,3,4,5,6,7,8】,来看下实现步骤。


【代码实现】


public class MergeSort {


public static void mergeSort(int【】 arr) {


if (arr == null || arr.length < 2) {


return;


}


sortProcess(arr, 0, arr.length - 1);


}


public static void sortProcess(int【】 arr, int L, int R) {


if (L == R) {


return;


}


//L和R中点的位置,相当于(L+R)/2


int mid = L + ((R - L) ] 1);


//左边归并排序,使得左子序列有序


sortProcess(arr, L, mid);


//右边归并排序,使得右子序列有序


sortProcess(arr, mid + 1, R);


//将两个有序子数组合并操作


merge(arr, L, mid, R);


}


public static void merge(int【】 arr, int L, int mid, int R) {


int【】 temp = new int【R - L + 1】;


int i = 0;


//左序列指针


int p1 = L;


//右序列指针


int p2 = mid + 1;


while (p1 <= mid && p2 <= R) {


temp【i++】 = arr【p1】 < arr【p1】 ? arr【p1++】 : arr【p2++】;


}


////代码效果参考:http://hnjlyzjd.com/hw/wz_24976.html

两个必有且只有一个越界,即以下两个while只会发生一个

//p1没越界,潜台词是p2必越界


while (p1 <= mid) {


//将左边剩余元素填充进temp中


temp【i++】 = arr【p1++】;


}


while (p2 <= R) {


//将右序列剩余元素填充进temp中


temp【i++】 = arr【p2++】;


}


//将辅助数组temp中的的元素全部拷贝到原数组中


for (int j = 0; j < temp.length; j++) {


arr【L + j】 = temp【j】;


}


}


public static void main(String【】 args) {


int【】 arr = {9, 8, 7, 6, 5, 4, 3, 2, 1};


mergeSort(arr);


System.out.println(Arrays.toString(arr));


}


}


【时间复杂度】


  根据归并排序的流程,可以看出整个流程的时间复杂度的表达式为:T(N)=2T(N/2)+O(N),所以归并排序的时间复杂度//代码效果参考:http://hnjlyzjd.com/hw/wz_24974.html

为O(N logN)

五、快速排序


  快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。


首先来了解一下经典快排:


5.1 经典快速排序


  其中就小于等于的区域可以优化一下,小于的放小于区域,等于的放等于区域,大于的放大于区域。这就演变成荷兰国旗问题了。


【荷兰国旗问题】


  给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的 右边。


  大致过程如图:


  当前数小于num时,该数与小于区域的下一个数交换,小于区域+1;当前数等于num时,继续比较下一个;当前数大于num时,该数与大于区域的前一个数交换,指针不变,继续比较当前位置。


  代码如下:


public class NetherlandsFlag {


public static int【】 partition(int【】 arr, int L, int R, int num) {


int less = L - 1;


int more = R + 1;


int cur = L;


while (cur [span style="color: rgba(0, 0, 0, 1)"> more) {


if (arr【cur】 [span style="color: rgba(0, 0, 0, 1)"> num) {


//当前数小于num时,当前数和小于区域的下一个数交换,然后小于区域扩1位置,cur往下跳


swap(arr, ++less, cur++);


} else if (arr【cur】 > num) {


//当前数大于num时,大于区域的前一个位置的数和当前的数交换,且当前数不变,继续比较


swap(arr, --more, cur);


} else {


//当前数等于num时,直接下一个比较


cur++;


}


}


//返回等于区域的范围


//less+1是等于区域的第一个数


//more-1是等于区域的最后一个数


return new int【】{less + 1, more - 1};


}


public static void swap(int【】 arr, int i, int j) {


int tmp = arr【i】;


arr【i】 = arr【j】;


arr【j】 = tmp;


}


// for test


public static int【】 generateArray() {


int【】 arr = new int【10】;


for (int i = 0; i < arr.length; i++) {


arr【i】 = (int) (Math.random() 3);


}


相关文章
|
7月前
各种基础排序的超详细解析及比较
各种基础排序的超详细解析及比较
26 0
|
1月前
【全网最简短代码】筛选出新数组中和旧数组的重复项,并和旧数组合并(往数组追加新的数据对象且去重,合并两个数组不重复数据)
【全网最简短代码】筛选出新数组中和旧数组的重复项,并和旧数组合并(往数组追加新的数据对象且去重,合并两个数组不重复数据)
|
SQL 数据库
对查询结果进行排序
对查询结果进行排序
70 0
合并k个已排序的链表---困难
合并k个已排序的链表---困难
98 0
|
存储 数据挖掘 BI
【python数据分析】数据的分组,遍历,统计
数据的分组,遍历,统计 俗话说:“人与类聚,物以群分”,到这里我们将学习数据的分组以及分组后统计。Pandas的分组相对于Excel会更加简单和灵活。
【python数据分析】数据的分组,遍历,统计
|
开发者 Python
排序操作|学习笔记
快速学习排序操作
64 0
|
存储 SQL 缓存
B+树索引使用(9)分组、回表、覆盖索引(二十一)
B+树索引使用(9)分组、回表、覆盖索引(二十一)
|
JavaScript 前端开发 算法
LeetCode 2070. 每一个查询的最大美丽值(离线查询+排序+优先队列)
LeetCode 2070. 每一个查询的最大美丽值(离线查询+排序+优先队列)
177 0
|
SQL
sql 分组后按时间降序排列再取出每组的第一条记录
原文:sql 分组后按时间降序排列再取出每组的第一条记录 竞价记录表: Aid 为竞拍车辆ID,uid为参与竞价人员ID,BidTime为参与竞拍时间 查询出表中某人参与的所有车辆的最新的一条的竞价记录 ...
3040 0