排序算法

写在前面:因为自己编程挺弱的,可能写的代码有瑕疵,并且有些没有优化(比如在快排时,选择基准值时就直接选择最前面的值),但是代码都能运行通过,欢迎交流与指出!

排序算法介绍

排序是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。

排序算法的稳定性:假设在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法的稳定的,否则称为不稳定的。

待排序的记录数量不同,使得排序过程中涉及的存储器不同,可将排序算法分为:内部排序(计算机随机存储器中进行排序),外部排序(待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中需要对外存进行访问的排序过程)

本文只讨论内部排序。

内部排序介绍

按排序过程中依据的不同原则对内部排序进行分类,大致可分为插入排序(直接插入排序、希尔排序)、交换排序(冒泡排序、快速排序)、选择排序(简单选择排序、堆排序)、归并排序和计数排序等五类。

下表给出了常见比较排序算法的性能:

常见比较排序算法的性能
排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(n2) O(n) O(n2) O(1) 稳定
简单选择排序 O(n2) O(n2) O(n2) O(1) 不稳定
直接插入排序 O(n2) O(n) O(n2) O(1) 稳定
希尔排序 O(nlogn)~O(n2) O(n1.3) O(n2) O(1) 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
快速排序 O(nlogn) O(nlogn) O(n2) O(logn)~O(n) 不稳定

冒泡排序

 1 public static void bubbleSort(int[] num){
 2         if (num==null)
 3             return;
 4 
 5         for (int i=0; i<num.length-1; i++){ //遍历的趟数 n-1,每遍历一次就有一个数在规定的位置上
 6             boolean flag = true;  //判断是否已经排好序了,如果排好了,直接退出循环,优化部分
 7 
 8             // 从后向前开始冒泡。因为前i个元素已经为排好序的,所以判断范围为i到n-1,即比较到i+1和i为止,j到i+1。
 9             for (int j=num.length-1; j>i; j--){
10                 if (num[j] < num[j-1]){
11                     int temp = num[j];
12                     num[j] = num[j-1];
13                     num[j-1] = temp;
14                     flag = false; //如果有交换,说明并没有拍好序,继续下一趟排序。
15                 }
16             }
17 
18             if (flag)
19                 break;
20         }
21     }

简单选择排序

选择排序的基本思想:每一趟在n-i+1(i=1,2,...,n-1)个记录中选出关键字最小的记录作为有序序列中的第i个记录。

 1 public static void selectionSort(int[] num){
 2         if (num==null)
 3             return;
 4 
 5         //每次选择一个未排序数组中最小的放在开始处,一共需要n-1次
 6         for (int i=0; i<num.length-1; i++){
 7             int min = i; //将每次遍历开始的索引保存为min,之后min保存当前遍历中数值最小的索引。
 8             for (int j=i+1; j<num.length; j++){
 9                 if (num[j] < num[min]){
10                     min = j;
11                 }
12             }
13 
14             //如果min不等于i,将最小的数值调换到i位置上
15             if (i != min){
16                 int temp = num[i];
17                 num[i] = num[min];
18                 num[min] = temp;
19             }
20         }
21     }

直接插入排序

基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。

 1 public static void insertSort(int[] num){
 2         if (num==null)
 3             return;
 4 
 5         //因为给定的数组一般都是从下标为0开始的,如果将其作为哨兵,需要将其先整体往后挪,排完序后再挪回来
 6         //所以声明一个常数空间来存储元素,但是这样无法防止数组越界,需要自己判断
 7         for (int i=1; i<num.length; i++){
 8             if (num[i] < num[i-1]){ //可以不用这行判断,见希尔排序的写法
 9                 int temp = num[i];  //复制为哨兵
10                 int j = i-1;
11                 //加入约束条件j>=0,并且要将其放在前面,不然数组越界
12                 while (j>=0 && temp<num[j]){
13                     num[j+1] = num[j];
14                     j--;
15                 }
16                 num[j+1] = temp;
17             }
18         }
19     }

改进:折半插入排序、2-路插入排序

希尔排序

又称为“缩小增量排序”。基本思想:先将整个待排记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

 1 //希尔排序
 2     //插入排序法可以高速处理顺序较整齐的数据,而希尔排序法就是充分发挥插入排序法这一特长的高速算法。
 3     public static void shellSort(int[] num){
 4         if (num==null || num.length==0)
 5             return;
 6 
 7         //生成数组G,表示间隔。因为G的大小不能确定,所以使用list
 8         List<Integer> G = new ArrayList<>();
 9         int h = 1;
10         while (h<num.length){ 
11             G.add(h);
12             h = 3*h + 1;
13         }
14 
15         for (int i=G.size()-1; i>=0; i--){
16             insertionSortShell(num, G.get(i));
17         }
18     }
19 
20     public static void insertionSortShell(int[] num, int g){
21         for (int i=g; i<num.length; i++){
22             int temp = num[i];
23             int j = i-g;
24             while (j>=0 && num[j]>temp){
25                 num[j+g] = num[j];
26                 j -= g;
27             }
28             num[j+g] = temp;
29         }
30     }

快速排序

基本思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

快速排序的递归代码:

 1 // r代表的是数组的最后一个元素
 2     public static int partition(int[] nums, int p, int r){
 3         int x = nums[r];
 4         int i = p-1;
 5         for (int j=p; j<r; j++){
 6             if (nums[j] <= x){
 7                 i++;
 8                 int temp = nums[i];
 9                 nums[i] = nums[j];
10                 nums[j] = temp;
11             }
12         }
13 
14         int temp = nums[i+1];
15         nums[i+1] = nums[r];
16         nums[r] = temp;
17 
18         return i+1;
19     }
20 
21     public static void quickSort(int[] nums, int p, int r){
22         if (p < r){
23             int q = partition(nums, p, r);
24             quickSort(nums, p, q-1);
25             quickSort(nums, q+1, r);
26         }
27     }
28 
29     public static void quickSort2(int[] nums, int l, int r){
30         if (l>=r){
31             return;
32         }
33 
34         int i = l;
35         int j = r;
36         int key = nums[l];
37 
38         while (i<j){
39             while (i<j && nums[j]>=key){
40                 j--;
41             }
42             if (i<j){
43                 nums[i] = nums[j];
44                 i++;
45             }
46 
47             while (i<j && nums[i]<key){
48                 i++;
49             }
50             if (i<j){
51                 nums[j] = nums[i];
52                 j--;
53             }
54         }
55 
56         nums[i] = key;
57         quickSort2(nums, l, i-1);
58         quickSort2(nums, i+1, r);
59     }

快速排序的非递归代码

 1 public static void quickSortUnRec(int[] num, int start, int end){
 2         Stack<Integer> stack = new Stack<>(); //用栈模拟
 3         if (start < end){
 4             stack.push(end);
 5             stack.push(start);
 6             while (!stack.isEmpty()){
 7                 int l = stack.pop();
 8                 int r = stack.pop();
 9                 int index = partition2(num, l, r);
10                 if (l < index-1){
11                     stack.push(index-1);
12                     stack.push(l);
13                 }
14                 if (r > index+1){
15                     stack.push(r);
16                     stack.push(index+1);
17                 }
18             }
19         }
20     }
21 
22     private static int partition2(int[] num, int l, int r) {
23         int pivot = num[l];
24         while(l<r){
25             while(l<r && num[r]>=pivot){
26                 r--;
27             }
28             if (l<r){
29                 num[l] = num[r];
30                 l++;
31             }
32             while(l<r && num[l]<pivot){
33                 l++;
34             }
35             if (l<r){
36                 num[r] = num[l];
37                 r--;
38             }
39         }
40         num[l] = pivot;
41         return l;
42     }

递归算法使用的栈由程序自动产生,栈中包含:函数调用时的参数和函数中的局部变量。如果局部变量很多或者函数内部又调用了其他函数,则栈会很大。每次递归调用都要操作很大的栈,效率自然会下降。

而对于非递归算法,每次循环使用自己预先创建的栈,因此不管程序复杂度如何,都不会影响程序效率。

对于上面的快速排序,由于局部变量只有一个mid,栈很小,所以效率并不比非递归实现的低。

若初始记录序列按关键字有序或者基本有序时,快速排序将蜕化为起泡排序,时间复杂度为O(n2),为改进之,通常依“三者取中”的法则来选取枢纽记录。

堆排序

使用的是最小堆,每次最小的值和最后一个元素交换,因此最后的排序是从大到小排序的。

堆排序的非递归代码:

 1 public static void minHeapSort(int[] nums, int n){
 2         if (nums == null){
 3             return;
 4         }
 5 
 6         buildMinHeap(nums, n);
 7 
 8         for (int i=n-1; i>0; i--){
 9             int temp = nums[0];
10             nums[0] = nums[i];
11             nums[i] = temp;
12 
13             minHeapFixDown(nums, 0, i);
14         }
15 
16     }
17 
18     private static void buildMinHeap(int[] nums, int n) {
19         for (int i=(n-1)/2; i>=0; i--){
20             minHeapFixDown(nums, i, n);
21         }
22     }
23 
24     private static void minHeapFixDown(int[] nums, int i, int n) {
25         int j = 2*i+1;
26 
27         while (j < n){
28             if (j+1<n && nums[j+1]<nums[j]){
29                 j++;
30             }
31             if (nums[i] <= nums[j]){
32                 break;
33             }
34 
35             int temp = nums[i];
36             nums[i] = nums[j];
37             nums[j] = temp;
38 
39             i = j;
40             j = 2*i+1;
41         }
42     }

 堆排序的递归代码(只在调整堆的时候有变化):

 1 private static void minHeapFixDown2(int[] nums, int i, int n){
 2         int j = 2*i+1;
 3 
 4         if (j >= n){
 5             return;
 6         }
 7 
 8         if (j+1<n && nums[j+1]<nums[j]){
 9             j++;
10         }
11         if (nums[i] <= nums[j]){
12             return;
13         }
14 
15         int temp = nums[i];
16         nums[i] = nums[j];
17         nums[j] = temp;
18 
19         minHeapFixDown2(nums, j, n);
20     }

归并排序

假设初始序列有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2(向上取整)个长度为2或1的有序子序列;再两两归并,...,如此重复,直至得到一个长度为n的有序序列为止,这种方法称为2-路归并排序。

归并排序的递归代码:

 1 // right代表的是最后一个元素的后一个元素
 2     public static void mergeSort(int[] nums, int left, int right){
 3         if (left+1 < right){
 4             int mid = (left + right) / 2;
 5             mergeSort(nums, left, mid);
 6             mergeSort(nums, mid, right);
 7             merge(nums, left, mid, right);
 8         }
 9     }
10 
11     private static void merge(int[] nums, int left, int mid, int right) {
12         int n1 = mid - left;
13         int n2 = right - mid;
14 
15         int[] L = new int[n1+1];
16         int[] R = new int[n2+1];
17         for (int i=0; i<n1; i++){
18             L[i] = nums[left+i];
19         }
20         for (int i=0; i<n2; i++){
21             R[i] = nums[mid+i];
22         }
23         L[n1] = Integer.MAX_VALUE;
24         R[n2] = Integer.MAX_VALUE;
25 
26         int i = 0;
27         int j = 0;
28         for (int k=left; k<right; k++){
29             if (L[i] <= R[j]){
30                 nums[k] = L[i++];
31             }else {
32                 nums[k] = R[j++];
33             }
34         }
35     }

 归并排序的非递归代码(merge方法和上面一致):

 1 public static void mergeSortUnRec(int[] num){
 2         int len = 1;
 3         while (len <= num.length){
 4 
 5             for (int i=0; i+len<=num.length; i+=len*2){
 6                 int low = i;
 7                 int mid = i+len;
 8                 int high = i+2*len;
 9 
10                 if (high>num.length){
11                     high=num.length;
12                 }
13                 merge(num, low, mid, high);
14             }
15             len *= 2;
16         }
17     }

计数排序

先统计出每个数对应的个数(数组C),然后每个值都累加前面所有数的个数(数组C进行累加),最后从后向前面遍历所有数。

 1 //假设输入的元素都是0到k之间的整数
 2     public static void countingSort(int[] A, int[] B, int k){
 3 
 4         int[] C = new int[k+1];
 5         for (int i=0; i<A.length; i++){
 6             C[A[i]]++;
 7         }
 8 
 9         for (int i=1; i<=k; i++){
10             C[i] += C[i-1];
11         }
12 
13         for (int i=A.length-1; i>=0; i--){
14             B[C[A[i]]-1] = A[i];
15             C[A[i]]--;
16         }
17     }

参考:

http://www.cnblogs.com/eniac12/p/5329396.html#s3

http://www.cnblogs.com/mist-lee/p/4770662.html

http://blog.csdn.net/pi9nc/article/details/12057381

原文地址:https://www.cnblogs.com/WanJiaJia/p/7476095.html