排序算法总结
一、写在前面
排序简而言之就是将一组序列通过算法按照一定顺序排列,排序是一种算法,在程序中能够经常使用到。
排序算法有多种,每种算法各有优点,各有缺陷,面对不同的排序环境选择不同的排序算法是我们学习的目的。本文就常用排序算法进行详细总结,包括选择排序(直接选择排序和堆排序)、交换排序(冒泡排序、快速排序)、插入排序(直接插入排序、折半插入排序、Shell排序)、归并排序、桶排序、计数排序、基数排序共计11种排序算法,欢迎探讨。
二、选择排序
1、直接选择排序
直接选择排序逻辑十分简单,对整个无序序列进行遍历,找到最大值或最小值,与序列第一个元素交换,这样,第一个元素就是有序的,后面的序列就是无序的。按照上面的步骤又对无序部分进行遍历,找到最大,值或最小值,与序列第二个元素交换,这样,第一个元素和第二个元素就是有序的,剩下的部分是无序的。以此类推,共进行n-1回遍历,就能使整个序列有序。
如数列:4、3、5、6、5、0、9。进行升序排序:
第一次遍历找到无序部分最小值0,0和4交换,得到新数列:0、3、5、6、5、4、9。
第二次遍历找到无序部分最小值3,3的位置不变:0、3、5、6、5、4、9。
第二次遍历找到无序部分最小值4,4和5交换:0、3、4、6、5、5、9。
以此类推......
第n-1次也就是第6次得到升序数列:0、3、4、5、5、6、9。
直接选择排序特点是算法简单,但时间复杂度为O(n^2),适合序列较少的排序,属于不稳定排序算法。(算法的稳定性用于描述待排序列中相同关键字在排序之后,相对位置是否发生了变化。比如数列4、3、5、6、5、0、9这个数列中存在相同的关键字5,他们具有前后的相对位置,红色的5在前,蓝色的5在后,如果排序之后两个5的相对位置不变,即红5在蓝5之前,那么就说这个排序算法是稳定的,否则为不稳定的)。
下面是实现代码(Java):
1 package test; 2 3 import java.util.Arrays; 4 5 /** 6 * 直接选择排序 7 */ 8 import org.junit.Test; 9 10 public class StraightSelectionSort { 11 12 @Test 13 public void test(){ 14 int[] data = {4,1,5,6,5,0,9}; 15 16 System.out.println(Arrays.toString(straightSelectionSort(data))); 17 } 18 19 /* 20 * 直接选择排序 21 */ 22 public int[] straightSelectionSort(int[] data){ 23 for(int i=0;i<data.length-1;i++){ 24 int minIndex = i; 25 for(int j=i+1;j<data.length;j++){ 26 if(data[i]>data[j]) 27 minIndex = j;//记录最小值角标 28 } 29 //最小值排到前面 30 if(minIndex != i){ 31 int temp = data[i]; 32 data[i] = data[minIndex]; 33 data[minIndex] = temp; 34 } 35 } 36 return data; 37 } 38 }
2、堆排序
堆排序是利用“堆”这种数据结构进行排序的算法。所以首先我们要知道什么是堆?
堆是满足以下性质的完全二叉树:对于非叶子结点(即有孩子的结点),如果父结点的值大于或等于其左右子结点,称为大顶堆;如果父节点的值小于或等于其左右子节点的值,称为小顶堆。
根据堆的特性我们知道根节点为最值,类似直接排序的逻辑,已经知道了最值然后就可以交换位置了,以下是堆排序的实现步骤:
一、初始化为大顶堆或小顶堆。方法是从最后一个非叶子结点开始,从下往上,从右往左进行堆结构处理。
二、根结点与无序部分末尾位置就行交换。
三、重新构造大顶堆或小顶堆,方法是从根结点开始,从上往下、从左往右进行堆结构处理,重复二、三步骤知道只剩根结点即可。
三、交换排序
1、冒泡排序
冒泡排序是序列从头到尾,相邻的元素进行两两比较,按照给定的顺序,顺序一致继续下一组相邻元素比较,顺序不一致,交换位置。这样每从头到尾走一趟就有一个遍历序列的最小值或最大值到达尾部,形似“冒泡”。接着对无序部分进行第二趟冒泡排序,直到只剩最后一个元素即可实现整体排序。
实现代码如下(Java版):
/** * 冒泡排序 * * @param arr */ public void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { boolean flag = true;//设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已然完成。 for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { swap(arr,j,j+1); flag = false; } } if (flag) { break; } } }
2、快速排序
快速排序的排序逻辑:随便选择序列中的一个数作为基准数,一般选择第一个元素即可。然后以基准数为准将元素分为两部分,一边大于等于基准数,另一边小于等于基准数。这样就得到两个序列,递归实现两部分即可。快速排序的原理很简单,与冒泡排序相比,采用了“跳跃式”的交换方式,所以时间复杂度比冒泡排序要低。
详解可参考:坐在马桶上看算法:快速排序
四、插入排序
1、直接插入排序
直接插入排序是将序列分为有序列和待排无序列,其实大多排序算法都如此,待排序列与有序列进行比较并插入,但插入排序麻烦在“插入”这个过程,不同于交换位置,是要改变元素的位置。如图所示:
2、折半插入排序
折半插入是对直接插入的改进,直接插入在将元素插入有序序列时,是从头到尾的比较,然后插入,对于较长的序列,这样的方法显然效率较低,因此插入时从中间位置开始比较,这样效率要高很多。
3、Shell排序
Shell排序又称缩小增量排序,是希尔于1959年提出来的,固此而得名。希尔排序也是对直接插入排序的优化,希尔排序是将整个序列按照一定增量分成若干组,再对这几组分分别进行直接插入排序,然后减少增量(一般是缩小一倍),然后重复以上操作,知道增量为1,即最后再整个序列作为一组进行直插。
参考图来自:https://www.cnblogs.com/chengxiao/p/6104371.html
实现代码(Java版):
1 public static void main(String [] args) 2 { 3 int[]a={49,38,65,97,76,13,27,49,78,34,12,64,1}; 4 System.out.println("排序之前:"); 5 for(int i=0;i<a.length;i++) 6 { 7 System.out.print(a[i]+" "); 8 } 9 //希尔排序 10 int d=a.length; 11 while(true) 12 { 13 d=d/2; 14 for(int x=0;x<d;x++) 15 { 16 for(int i=x+d;i<a.length;i=i+d) 17 { 18 int temp=a[i]; 19 int j; 20 for(j=i-d;j>=0&&a[j]>temp;j=j-d) 21 { 22 a[j+d]=a[j]; 23 } 24 a[j+d]=temp; 25 } 26 } 27 if(d==1) 28 { 29 break; 30 } 31 } 32 System.out.println(); 33 System.out.println("排序之后:"); 34 for(int i=0;i<a.length;i++) 35 { 36 System.out.print(a[i]+" "); 37 } 38 }
五、归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
详解参考:图解算法之归并排序
代码实现(Java版)
1 package sortdemo; 2 3 import java.util.Arrays; 4 5 /** 6 * Created by chengxiao on 2016/12/8. 7 */ 8 public class MergeSort { 9 public static void main(String []args){ 10 int []arr = {9,8,7,6,5,4,3,2,1}; 11 sort(arr); 12 System.out.println(Arrays.toString(arr)); 13 } 14 public static void sort(int []arr){ 15 int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间 16 sort(arr,0,arr.length-1,temp); 17 } 18 private static void sort(int[] arr,int left,int right,int []temp){ 19 if(left<right){ 20 int mid = (left+right)/2; 21 sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序 22 sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序 23 merge(arr,left,mid,right,temp);//将两个有序子数组合并操作 24 } 25 } 26 private static void merge(int[] arr,int left,int mid,int right,int[] temp){ 27 int i = left;//左序列指针 28 int j = mid+1;//右序列指针 29 int t = 0;//临时数组指针 30 while (i<=mid && j<=right){ 31 if(arr[i]<=arr[j]){ 32 temp[t++] = arr[i++]; 33 }else { 34 temp[t++] = arr[j++]; 35 } 36 } 37 while(i<=mid){//将左边剩余元素填充进temp中 38 temp[t++] = arr[i++]; 39 } 40 while(j<=right){//将右序列剩余元素填充进temp中 41 temp[t++] = arr[j++]; 42 } 43 t = 0; 44 //将temp中的元素全部拷贝到原数组中 45 while(left <= right){ 46 arr[left++] = temp[t++]; 47 } 48 } 49 }
六、桶排序
桶排序是先设置一定顺序的“桶”,这里的“桶”代表一定的函数关系或说区间范围,我们对无序序列进行整体遍历并将满足“桶”函数关系的元素“放入”桶中,然后分别对各个“桶”进行排序,最后按照“桶”的顺序依次输出即可实现整体排序。
七、计数排序
计数排序是没有交换的排序方法,效率很高但空间复杂度也很大,计数排序适合数字重复较多的无序序列。计数排序原理很简单,利用的就是查表法或说查哈希表法,比如有一组数字:1,2,1,4,1,1,9。我们知道数字范围在1~9之间,那么我们就设置一个1-9的哈希表:
八、基数排序
基数排序也无需元素间相互比较和相互交换位置,类似计数排序和桶排序,我们需要对元素进行“分类”。基数排序的关键就是使用基数字0-9进行数字排序,需要实现分配与收集的过程。分配:我们都知道任何数字都是0-9组成的,所以我们先设置10个“桶”,分别代表0-9十个数字,我们从个位开始对元素的进行分配。然后收集,第一遍能保证序列中的个位数变为有序。然后再对收集的数列按照十位数字进行分配,这次能保证序列十位数和个位数有序。依次类推,进行分配和收集的重复操作,直至最高位,然后实现整体排序。
其实基数排序可以从低位开始也可以从高位开始,但最高位优先法显然较麻烦。以下是基数排序的Java实现:
1 public class RadixSort 2 { 3 public static void sort(int[] number, int d) //d表示最大的数有多少位 4 { 5 intk = 0; 6 intn = 1; 7 intm = 1; //控制键值排序依据在哪一位 8 int[][]temp = newint[10][number.length]; //数组的第一维表示可能的余数0-9 9 int[]order = newint[10]; //数组orderp[i]用来表示该位是i的数的个数 10 while(m <= d) 11 { 12 for(inti = 0; i < number.length; i++) 13 { 14 intlsd = ((number[i] / n) % 10); 15 temp[lsd][order[lsd]] = number[i]; 16 order[lsd]++; 17 } 18 for(inti = 0; i < 10; i++) 19 { 20 if(order[i] != 0) 21 for(intj = 0; j < order[i]; j++) 22 { 23 number[k] = temp[i][j]; 24 k++; 25 } 26 order[i] = 0; 27 } 28 n *= 10; 29 k = 0; 30 m++; 31 } 32 } 33 public static void main(String[] args) 34 { 35 int[]data = 36 {73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 100}; 37 RadixSort.sort(data, 3); 38 for(inti = 0; i < data.length; i++) 39 { 40 System.out.print(data[i] + ""); 41 } 42 }
九、各排序算法总结