排序算法

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

排序算法

 

 

关于时间复杂度:

  1. 平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
  2. 线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
  3. O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
  4. 线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性:

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

名词解释:

n:数据规模

k:“桶”的个数

In-place:占用常数内存,不占用额外内存

Out-place:占用额外内存

稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同

 

采用了《算法4》中的写法

 1 // 比较两个元素的大小
 2 public static boolean less(Comparable v, Comparable w){
 3     return v.compareTo(w) < 0;
 4 }
 5 
 6 // 交换两个元素
 7 public static void exch(Comparable[] arr, int i, int j){
 8     Comparable temp = arr[i];
 9     arr[i] = arr[j];
10     arr[j] = temp;
11 }

 

Bubble Sort

算法思想:从第一个元素开始,不断和隔壁的元素进行比较,最后最大的元素会到达末尾,就像最大的泡泡会跑到水面。

这样会导致,数组后面的是排好序的。所以第二次循环不用比较后面的。

代码实现:

public class Bubble{
    public static void Sort(Comparable[] arr){
        for(int i = 0; i < arr.length-1; i++){
            for(int j = 0; j < arr.length - i - 1; j++){
                if(less(arr[j+1], arr[j])){
                    exch(arr, j, j+1);
                } 
            }
        } 
    }
}

 

 

Selection Sort

算法思想:外层循环的每一轮都会放好未排列数据中最小的值,内层循环则是比较找出未排序数据中最小的值。结果是,前面是排序好的数据。后面是待排序的数据。

代码实现:

 1 public class Selection {
 2     public static void sort(Comparable[] a) { 
 3     
 4         int N = a.length;    
 5         for (int i = 0; i < N; i++) { 
 6             // Exchange a[i] with smallest entry in a[i+1...N). 
 7             int min = i;
 8             for (int j = i+1; j < N; j++) 
 9                 if (less(a[j], a[min])) 
10                     min = j; 
11             exch(a, i, min);
12         } 
13     } 
14 }

 

Insertion Sort

算法思想:就像我们打牌的时候,在摸牌期间,我们每抽到一张牌,会从我们手上的牌的右手边开始看,当前的牌与排好序的牌进行比较,直到找到左边全是比当前牌小的(遍历手上已经排好序的牌),然后插入。遍历所有抽到的牌;

代码实现:

 1 public class Insertion {
 2     public static void sort(Comparable[] a) {
 3         int N = a.length;
 4         for (int i = 1; i < N; i++) { 
 5             // Insert a[i] among a[i-1], a[i-2], a[i-3]... .. 
 6             for (int j = i; j > 0 && less(a[j], a[j-1]); j--) 
 7                 exch(a, j, j-1);
 8         } 
 9     } 
10 }

Shell sort

算法思想:要跟间隔 h 的元素相比。

代码实现:

 1 public class Shell {
 2     public static void sort(Comparable[] a) { 
 3         int N = a.length; int h = 1;
 4     while (h < N/3) h = 3*h + 1;     // 1, 4, 13, 40, 121, 364, 1093, ... 
 5         while (h >= 1) { 
 6             // h-sort the array. 
 7             for (int i = h; i < N; i++) { 
 8                 // Insert a[i] among a[i-h], a[i-2*h], a[i-3*h]... . 
 9                 for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) 
10                     exch(a, j, j-h);
11             } 
12             h = h/3; 
13         } 
14     } 
15 }

 

Merge Sort

算法思想:

代码实现:

 1 public static void merge(Comparable[] a, int lo, int mid, int hi) { 
 2     // Merge a[lo..mid] with a[mid+1..hi]. 
 3     int i = lo, j = mid+1;
 4     for (int k = lo; k <= hi; k++) // Copy a[lo..hi] to aux[lo..hi]. 
 5         aux[k] = a[k];
 6     for (int k = lo; k <= hi; k++) // Merge back to a[lo..hi]. 
 7         if(i > mid) 
 8             a[k] = aux[j++];
 9         else if (j > hi ) 
10             a[k] = aux[i++];
11         else if (less(aux[j], aux[i])) 
12             a[k] = aux[j++]; 
13         else
14         a[k] = aux[i++];
15 }

there are four conditions:

  • left half exhausted (take from the right),

  • right half exhausted (take from the left),

  • current key on right less than current key on left (take from the right),

  • current key on right greater than or equal to current key on left (take from the left).

 Top-down mergesort

使用了分治的思想。

 1 public class Merge{
 2     private static Comparable[] aux;
 3     public static void sort(Comparable[] a){
 4         aux = new Comparable[a.length];
 5         sort(a, 0, a.length-1);
 6     }
 7     public static void sort(Comparable[] a, int lo, int hi){
 8         if(hi <= lo){
 9             return;
10         }
11         int mid = lo +(hi - lo) / 2;
12         sort(a, lo, mid);
13         sort(a, mid+1, hi);
14         merge(a, l, mid, hi);
15     }
16 }

Bottom-up mergesort

Bottom-up mergesort consists of a sequence of passes over the whole array, doing sz-by-sz merges, starting with sz equal to 1 and doubling sz on each pass. The final subarray is of size sz only when the array size is an even multiple of sz (otherwise it is less than sz).

多次遍历,子数组,从 1 开始,每次翻倍。最里面的那个循环就是在遍历所有的子数组。

merge(Comparable[] a, int lo, int mid, int hi)。

hi 为什么是 Math.min(lo+sz+sz-1, N-1)? 因为 sz 是子数组的长度,那我们归并的时候的长度,当然是 sz + sz -1

但是最后为什么是 N-1呢? 因为有可能不是偶数啊!

 1 public class MergeBU {
 2     private static Comparable[] aux;     // auxiliary array for merges
 3   
 4     public static void sort(Comparable[] a) { 
 5         // Do lg N passes of pairwise merges. 
 6         int N = a.length; 
 7         aux = new Comparable[N];
 8         for (int sz = 1; sz < N; sz = sz+sz)    // sz: subarray size
 9             for (int lo = 0; lo < N-sz; lo += sz+sz) // lo: subarray index 
10                 merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));
11     }
12 }

 

 

Quick Sort

【算法 4 里面的还不太理解,以下是参考的《白话经典算法》】

算法思想:

快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用,因此很多软件公司的笔试面试,包括像腾讯,微软等知名IT公司都喜欢考这个,还有大大小的程序方面的考试如软考,考研中也常常出现快速排序的身影。

总的说来,要直接默写出快速排序还是有一定难度的,因为本人就自己的理解对快速排序作了下白话解释,希望对大家理解有帮助,达到快速排序,快速搞定。

 

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

该方法的基本思想是:

    • 1.先从数列中取出一个数作为基准数。
    • 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
    • 3.再对左右区间重复第二步,直到各区间只有一个数。  

虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法:

先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。

         

以一个数组作为示例,取区间第一个数为基准数。

0

1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85

初始时,i = 0;  j = 9;   X = a[i] = 72

由于已经将 a[0] 中的数保存到 X 中,可以理解成在数组 a[0] 上挖了个坑,可以将其它数据填充到这来。

从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;

         

数组变为:

0

1

2

3

4

5

6

7

8

9

48

6

57

88

60

42

83

73

88

85

         

i = 3;   j = 7;   X=72

再重复上面的步骤,先从后向前找,再从前向后找。

从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;

从i开始向后找,当i=5时,由于i==j退出。

此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。

数组变为:

0

1

2

3

4

5

6

7

8

9

48

6

57

42

60

72

83

73

88

85

可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。  

         

对挖坑填数进行总结:

        • 1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
        • 2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
        • 3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
        • 4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

照着这个总结很容易实现挖坑填数的代码:

 1 int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置
 2 {
 3     int i = l, j = r;
 4     int x = s[l]; //s[l]即s[i]就是第一个坑
 5     while (i < j)
 6     {
 7         // 从右向左找小于x的数来填s[i]
 8         while(i < j && s[j] >= x) 
 9             j--;  
10         if(i < j) 
11         {
12             s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
13             i++;
14         }
15  
16         // 从左向右找大于或等于x的数来填s[j]
17         while(i < j && s[i] < x)
18             i++;  
19         if(i < j) 
20         {
21             s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
22             j--;
23         }
24     }
25     //退出时,i等于j。将x填到这个坑中。
26     s[i] = x;
27  
28     return i;
29 }

再写分治的代码:

1 void quick_sort1(int s[], int l, int r)
2 {
3     if (l < r)
4     {
5         int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
6         quick_sort1(s, l, i - 1); // 递归调用 
7         quick_sort1(s, i + 1, r);
8     }
9 }

整理一下:

 1 //快速排序
 2 void quick_sort(int s[], int l, int r)
 3 {
 4     if (l < r)
 5     {
 6         //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
 7         int i = l, j = r, x = s[l];
 8         while (i < j)
 9         {
10             while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
11                 j--;  
12             if(i < j) 
13                 s[i++] = s[j];
14             
15             while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
16                 i++;  
17             if(i < j) 
18                 s[j--] = s[i];
19         }
20         s[i] = x;
21         quick_sort(s, l, i - 1); // 递归调用 
22         quick_sort(s, i + 1, r);
23     }
24 }

快速排序还有很多改进版本,如随机选择基准数,区间内数据较少时直接用另的方法排序以减小递归深度。有兴趣的筒子可以再深入的研究下。

注1,有的书上是以中间的数作为基准数的,要实现这个方便非常方便,直接将中间的数和第一个数进行交换就可以了。

 

堆排序

计数排序

桶排序

基数排序

 

参考资料

1、白话经典算法

2、《算法4》

上一篇:JavaSE-15.2.2【自然排序Comparable的使用】


下一篇:编译安装python3.6后pip3无法安装模块问题处理