8大排序算法图文讲解
排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
我们这里说说八大排序就是内部排序。
当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
算法一:插入排序
插入排序示意图
插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
算法步骤:
1)将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2)从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
代码实现:
1 public class InsertSort { 2 3 public static void sort(int[] num){ 4 int i,j,min,temp; 5 for(i=0;i<num.length;i++){ 6 min=i;//将当前下标定义为最小值下标 7 for(j=i+1;j<num.length;j++){ 8 if (num[min]>num[j]) { 9 min=j;//如果有小于当前最小值的关键字,将此关键字的下标赋值给min 10 } 11 } 12 if (i!=min) {//若min不等于i,说明上面相互比较的为true,即有最小值,交换 13 temp=num[i]; 14 num[i]=num[min]; 15 num[min]=temp; 16 } 17 } 18 for (int k : num) { 19 System.out.println(k); 20 } 21 22 } 23 24 25 public static void main(String[] args) { 26 // TODO 自动生成的方法存根 27 int[] num={5,2,4,6,8,9,7,1,3,0}; 28 sort(num); 29 } 30 }
算法二:希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
- 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
算法步骤:
1)选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2)按增量序列个数k,对序列进行k 趟排序;
3)每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
代码实现:
1 public class ShellSort { 2 //希尔排序 3 public static void sort(int[] a) { 4 // 希尔排序 5 int d = a.length; 6 while (true) { 7 d = d / 2; 8 for (int x = 0; x < d; x++) { 9 for (int i = x + d; i < a.length; i = i + d) { 10 int temp = a[i]; 11 int j; 12 for (j = i - d; j >= 0 && a[j] > temp; j = j - d) { 13 a[j + d] = a[j]; 14 } 15 a[j + d] = temp; 16 } 17 } 18 if (d == 1) { 19 break; 20 } 21 } 22 23 for (int k : a) { 24 System.out.println(k); 25 } 26 } 27 28 public static void main(String[] args) { 29 // TODO Auto-generated method stub 30 int[] a = { 5, 2, 4, 6, 8, 9, 7, 1, 3, 0 }; 31 // int[]a={49,38,65,97,76,13,27,49,78,34,12,64,1}; 32 sort(a); 33 } 34 35 }
算法三:选择排序
选择排序示意图
选择排序(Selection sort)也是一种简单直观的排序算法。
算法步骤:
1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3)重复第二步,直到所有元素均排序完毕。
代码实现:
1 /** 2 * 选择一个min做基准和其他的数据相互比较,如果比较的数大则把当前的数的赋值给min 3 * 以此类推 4 * @author Administrator 5 * 6 */ 7 public class SelectSort { 8 //简单选择排序,选择一个min做基准和其他的数据相互比较 9 public static void sort(int[] num){ 10 int i,j,min,temp; 11 for(i=0;i<num.length;i++){ 12 min=i;//将当前下标定义为最小值下标 13 for(j=i+1;j<num.length;j++){ 14 if (num[min]>num[j]) { 15 min=j;//如果有小于当前最小值的关键字,将此关键字的下标赋值给min 16 } 17 } 18 if (i!=min) {//若min不等于i,说明上面相互比较的为true,即有最小值,交换 19 temp=num[i]; 20 num[i]=num[min]; 21 num[min]=temp; 22 } 23 } 24 for (int k : num) { 25 System.out.println(k); 26 } 27 28 } 29 30 public static void main(String args[]){ 31 int[] num={5,2,4,6,8,9,7,1,3,0}; 32 sort(num); 33 34 } 35 }
算法四:冒泡排序
冒泡排序示意图
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
算法步骤:
1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3)针对所有的元素重复以上的步骤,除了最后一个。
4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
代码实现:
1 /** 2 * 相邻数据两两比较,大的排上面,小的排下面 第一次可排出最小的值 3 * 第二次排出第二小的值 4 * 第三次排出第三小的值 5 * 以此类推排出顺序 6 * @author Administrator 7 * 8 */ 9 public class BubbleSort { 10 11 //初级版 12 public static void sort1(int[] num){ 13 int i,j,temp; 14 for(i=0;i<num.length;i++){ 15 for(j=i+1;j<num.length;j++){ 16 if (num[i]>num[j]) { 17 temp=num[i]; 18 num[i]=num[j]; 19 num[j]=temp; 20 } 21 } 22 } 23 for (int k : num) { 24 System.out.println(k); 25 } 26 27 } 28 //中级版 29 public static void sort2(int[] num){ 30 int i,j,temp; 31 for(i=0;i<num.length;i++){ 32 for(j=num.length-1;j>i;j--){ 33 if (num[j-1]>num[j]) { 34 temp=num[j-1]; 35 num[j-1]=num[j]; 36 num[j]=temp; 37 } 38 } 39 } 40 for (int k : num) { 41 System.out.println(k); 42 } 43 44 } 45 //终极版 46 public static void sort3(int[] num){ 47 int i,j,temp; 48 boolean flag=true; 49 for(i=0;i<num.length&&flag;i++){ 50 flag=false; 51 for(j=num.length-1;j>i;j--){ 52 if (num[j-1]>num[j]) { 53 temp=num[j-1]; 54 num[j-1]=num[j]; 55 num[j]=temp; 56 flag=true; 57 } 58 } 59 } 60 for (int k : num) { 61 System.out.println(k); 62 } 63 64 } 65 66 67 public static void main(String args[]){ 68 int[] num={5,2,4,6,8,9,7,1,3,0}; 69 // sort1(num); 70 // sort2(num); 71 sort3(num); 72 } 73 74 }
算法五:归并排序
归并排序示意图
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
分而治之
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
代码实现
package sortdemo; import java.util.Arrays; /** * Created by chengxiao on 2016/12/8. */ public class MergeSort { public static void main(String []args){ int []arr = {9,8,7,6,5,4,3,2,1}; sort(arr); System.out.println(Arrays.toString(arr)); } public static void sort(int []arr){ int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间 sort(arr,0,arr.length-1,temp); } private static void sort(int[] arr,int left,int right,int []temp){ if(left<right){ int mid = (left+right)/2; sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序 sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序 merge(arr,left,mid,right,temp);//将两个有序子数组合并操作 } } private static void merge(int[] arr,int left,int mid,int right,int[] temp){ int i = left;//左序列指针 int j = mid+1;//右序列指针 int t = 0;//临时数组指针 while (i<=mid && j<=right){ if(arr[i]<=arr[j]){ temp[t++] = arr[i++]; }else { temp[t++] = arr[j++]; } } while(i<=mid){//将左边剩余元素填充进temp中 temp[t++] = arr[i++]; } while(j<=right){//将右序列剩余元素填充进temp中 temp[t++] = arr[j++]; } t = 0; //将temp中的元素全部拷贝到原数组中 while(left <= right){ arr[left++] = temp[t++]; } } }
执行结果
[1, 2, 3, 4, 5, 6, 7, 8, 9]
最后
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
算法六:快速排序
方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。
1 public class QuickSort { 2 3 public static void quickSort(int arr[], int _left, int _right) { 4 int left = _left; 5 int right = _right; 6 int temp = 0; 7 if (left <= right) { // 待排序的元素至少有两个的情况 8 temp = arr[left]; // 待排序的第一个元素作为基准元素 9 while (left != right) { // 从左右两边交替扫描,直到left = right 10 11 while (right > left && arr[right] >= temp) 12 right--; // 从右往左扫描,找到第一个比基准元素小的元素 13 arr[left] = arr[right]; // 找到这种元素arr[right]后与arr[left]交换 14 15 while (left < right && arr[left] <= temp) 16 left++; // 从左往右扫描,找到第一个比基准元素大的元素 17 arr[right] = arr[left]; // 找到这种元素arr[left]后,与arr[right]交换 18 19 } 20 arr[right] = temp; // 基准元素归位 21 quickSort(arr, _left, left - 1); // 对基准元素左边的元素进行递归排序 22 quickSort(arr, right + 1, _right); // 对基准元素右边的进行递归排序 23 } 24 } 25 26 public static void main(String[] args) { 27 int array[] = { 10, 5, 3, 1, 7, 2, 8 }; 28 System.out.println("排序之前:"); 29 for (int element : array) { 30 System.out.print(element + " "); 31 } 32 33 quickSort(array, 0, array.length - 1); 34 35 System.out.println("\n排序之后:"); 36 for (int element : array) { 37 System.out.print(element + " "); 38 } 39 40 } 41 42 }