博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析8种常用排序
阅读量:5347 次
发布时间:2019-06-15

本文共 6372 字,大约阅读时间需要 21 分钟。

1、引言

  排序作为一种常用算法,时常活跃在我们的工作学习中,为了进一步加深对常用算法的理解,故做此博文与大家共享...

2、常用排序算法的种类

  冒泡排序,简单选择排序,插入排序,希尔排序,快速排序,归并排序,堆排序,基数排序

3、常用排序算法的一些特征

  (1)排序的稳定性(什么是稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,

且在之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的)

  •    稳定排序:冒泡排序、插入排序、归并排序、基数排序
  •      非稳定排序:选择排序、快速排序、希尔排序、堆排序

  (2)排序的维度(即占用内存的大小及运行所用时间的复杂度)

4、各种排序 

  ①快速排序

为了帮助大家理解下面引用《啊哈!算法》一书中的经典实例进行说明(《啊哈!算法》很好的一本书将枯燥的算法趣味化,建议大家看看)

  假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数。为了方便,我们一般取第一个数作为基准书。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列。
      3  1  2 5  4  6  9 7  10  8
 

方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。

094811yilrz1tkzkvlrriz.png

 

      首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

 

095430axy0qkhxxkktkktk.png
095437kdandfxhbtokk2qh.png

      现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。

       6  1  2  5  9 3  4  7  10  8
095448k1kevwlz41373e7k.png
095458ejza15wscjv7iw5c.png

 

       到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。
       6  1  2 5  4  3  9  7 10  8
 
       第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。
3  1 2  5  4  6  9 7  10  8
095506uz7e1uuukcblhkxv.png
095514cag5fumuqqg5jnsw.png
095530e0jf6p0y6aaaw2ir.png
       到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。这样就进行了一轮排序,类似于二分法,下面将以6为二分点,将数据分为3、1、2、5、4和9、7、10、8两部分,重复上面的步骤进行递归,直到排序完成。

 

/*** 快速排序,从小到大排*/public void quickSort(int arr[],int low,int high){  if(low>high){    return;//左边数大于右边数,程序结束  }   int key = arr[low];//将第一个数设为关键字  int i = low;  int j = high;  //左边找到的数与右边找到的数不相等时就一直找  while(i!=j){  //先从右边开始找,不能从左边开始下面会解释  while(i
=key){    j--;  }  //再找左边的  while(i
<=key){    i++;  }  //交换低位和高位上找到的数  if(i

 

有必要解释下为什么一定要从右边开始找,其实原因很简单,如果先从左边开始的话,不能保证与基准数交换的数会是一个小于基准数的值,可以自己debug看看,实践见真章!   



  

②冒泡排序

  冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行 直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。由于对于相等的相邻两个数,无需进行交换,换句话说原来相等的两个数左右顺序不会发生改变,故该算法是一种稳定算法。又因为排序要经过多次循环反复对相邻元素进行排序,所以该算法的效率是比较低的,时间复杂度达到O(n2)。

  冒泡排序算法的运作如下:

  1、 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2、 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

  3、 针对所有的元素重复以上的步骤,除了最后一个。

  4、 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

  用个小实例图解冒泡过程:

/*** 冒泡排序,从小到大排*/public void bubbleSort(int[] arr){  for(int i=0;i
arr[j+1]){        int temp = arr[j];        arr[j] = arr[j+1];        arr[j+1] = temp;      }    }  }  System.out.println(Arrays.toString(arr));}@Testpublic void testBubbleSort(){  bubbleSort(new int[]{6,1,2,7,9,3,4,5,10,8});}

 

 



 

③插入排序

  插入排序的基本思想:每步将一个待排序的记录,按其关键字的大小插入前面已经排好序的文件中适当的位置上,直到全部插入完为止。

  i、直接插入排序

    直接插入的排序的基本思想:把待排序的记录按其关键字的大小逐个插入到一个已经排序好的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

  用个小实例图表解释直接插入排序的过程:(参考资料:《算法与数据结构》 清华大学出版社 陈媛版)

注意:用方括号括起来的部分表示已经排序好的部分
i=1 [6] 1 2 7 9 3 4 5 10 8
i=2 [1,6] / 2 7 9 3 4 5 10 8
i=3 [1,2,6] / / 7 9 3 4 5 10 8
i=4 [1,2,6,7] / / / 9 3 4 5 10 8
i=5 [1,2,6,7,9] / / / / 3 4 5 10 8
i=6 [1,2,3,6,7,9] / / / / / 4 5 10 8
i=7 [1,2,3,4,6,7,9] / / / / / / 5 10 8
i=8 [1,2,3,4,5,6,7,9] / / / / / / / 10 8
i=9 [1,2,3,4,5,6,7,9,10] / / / / / / / / 8
i=10 [1,2,3,4,5,6,7,9,10] / / / / / / / / /
/***代码示例,直接插入排序,从小到大 */public void immediateInsertSort(int[] arr){  for(int i=1;i
=0&&temp

 

  ii、折半插入排序

  折半排序的基本思想:

   1)计算 0 ~ i-1 的中间点,用 i 索引处的元素与中间值进行比较,如果 i 索引处的元素大,说明要插入的这个元素应该在中间值和刚加入i索引之间,反之,就是在刚开始的位置 到中间值的位置,这样很简单的完成了折半;

     2)在相应的半个范围里面找插入的位置时,不断的用(1)步骤缩小范围,不停的折半,范围依次缩小为 1/2  1/4  1/8 .......快速的确定出第 i  个元素要插在什么地方;

       3)确定位置之后,将整个序列后移,并将元素插入到相应位置。

1 /** 2      * 折半插入排序,从小到大 3      */ 4     public void halfInsertSort(int[] arr){ 5         for(int i=1;i
low){20 arr[j] = arr[j-1];//如果插入的数比比对数小,将比对数右移一位21 j--;22 }23 arr[low] = insertNode;24 }25 System.out.println(Arrays.toString(arr));26 }27 28 @Test 29 public void testHalfInsertSort(){30 int[] arr = new int[]{6,1,2,7,9,3,4,5,10,8};31 halfInsertSort(arr);32 }

 

iii、希尔排序

  希尔排序的基本思想:先将整个待排序的数分成若干组,分别对每个小组进行直接插入排序,然后再缩减步长继续进行排序,待整个序列基本有序时(插入排序对基本有序的元素排序效率极高),对全部元素进行一次直接插入排序,至此排序完成,希尔排序效率非常高,其效率高于快速排序

  下面仍以6,1,2,7,9,3,4,5,10,8进行排序,分析希尔排序的排序过程

  第一次:步长gap=10/2,分组为(6,3)(1,4)(2,5)(7,10)(9,8),排序后结果(3,6)(1,4)(2,5)(7,10)(8,9)

第二次:步长gap=5/2,分组为(3,2,8,4,10)(1,7,6,5,9),排序后结果(2,3,4,8,10)(1,5,6,7,9)

 

第三次:步长gap=2/2,分组为(2,1,3,5,4,6,8,7,10,9),排序后结果(1,2,3,4,5,6,7,8,9,10)

1 /** 2      * 希尔排序,小到大 3      */ 4     public void shellSort(int[] arr){ 5         int n = arr.length; 6         for(int gap=n/2;gap>0;gap/=2){
//由希尔排序原理写出循环条件 7 for(int i=gap;i
=0&&insertNode

 

 



 

④选择排序 选择排序的基本思想:一个长度为N的无序数组中,在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换,直到第N-1趟遍历剩下的2个数据,找出其中最小的数值与第N-1个元素交换,至此选择排序完成。
1 /** 2      * 选择排序,小到大 3      */ 4     public void selectSort(int[] arr){ 5         for(int i=0;i
arr[j]){
//后面的数比前面的数小,则将较小值的索引保存在index中 9 index = j;10 }11 }12 //当前待排序的数与最小数交换,完成本次排序13 int temp = arr[index];14 arr[index] = arr[i];15 arr[i] = temp;16 }17 System.out.println(Arrays.toString(arr));18 }19 20 @Test21 public void testSelectSort(){22 int[] arr = {6,1,2,7,9,3,4,5,10,8};23 selectSort(arr);24 }

 



 

⑤归并排序

  归并排序:归并排序是一种稳定的排序方式,即归并排序不交换相同的元素,归并排序主要要做两件事 

  (1)“分解”:将序列每次折半划分

  (2)“合并”:将划分后的序列段两两合并后排序

    但要注意的分解和归并是同时进行的,并不是全部分解完了,再去合并,归并排序是一种高效的排序算法,可以毫不夸张的说在比较排序中,归并排序的效率是最高的,jdk的Arrays.sort()方法就是采用的归并排序,有兴趣的朋友可以参看jdk源码。

   下面以java代码为例,实现归并排序

  

1 /** 2      * 合并排好序的数组 3      * @param arr 待排序的数组 4      * @param first 数组的第一个数 5      * @param mid 数组的中间数 6      * @param last 数组的最后一位数 7      * @param temp 临时数组,用于临时存放排好序的序列 8      */ 9     public void mergeSort(int[] arr, int first, int mid, int last, int temp[]){10         int i = first, j = mid + 1, m = mid, n = last, k = 0;        11      while(i<=m&&j<=n){12             if(arr[i]<=arr[j]){13                 temp[k++] = arr[i++];14             }else{15                 temp[k++] = arr[j++];16             }17         }18         while(i<=mid){19             temp[k++] = arr[i++];20         }21         while(j

 



 

 

 

转载于:https://www.cnblogs.com/gongli123/p/6927073.html

你可能感兴趣的文章
动态内存申请函数选择(realloc、malloc 、alloca、 calloc)
查看>>
获取元素属性get_attribute
查看>>
视觉设计师的进化
查看>>
Python/jquery
查看>>
WPF之Binding
查看>>
【BZOJ】【2132】圈地计划
查看>>
HTML图片映射实用
查看>>
DP题目 sicily 1687 Permutation
查看>>
转载:无线测试
查看>>
Hadoop框架之HDFS的shell操作
查看>>
mybatis 学习四 (上)resutlMap
查看>>
ES6学习之数组的扩展
查看>>
Lua 语言基本语法
查看>>
ARM 的Thumb状态测试
查看>>
windows下读取utf-8文件
查看>>
apache 启动不了的排查方法
查看>>
Java有没有goto?
查看>>
(转)makefile 的用法
查看>>
Linux云自动化运维第十六课
查看>>
1.秋招复习简单整理之红黑树性质
查看>>