本篇文章參考《算法(第四版)》以下為文章的整體結(jié)構(gòu),方便瀏覽,也方便我以后回憶。

初級排序算法
游戲規(guī)則
我們以數(shù)組為源數(shù)據(jù),將他們按照順序排列好。以下為對應算法的模板。
排序算法類的模板
public class Example
{
public static void sort(Comparable[] a){}
private static boolean less(Comparable v, Comparable w)
{return v.compareTo() <0;}
private static void exch(Comparable[] a, int i, int j)
{Comparable t = a[i]; a[i] = a[j];a[j]= t;}
private static void show(Comparable[] a)
{
for(int i = 0; i< a.length; i++)
StdOut.print(a[i] + " ");
StdOut.println();
}
public static boolean isSorted(Comarable[] a)
{
for(int i = 1; i< a.length; i++)
if(less(a[i], a[i-1])) return false;
return true;
}
public static void main(String[] args){
String[] a = In.readStrings();
sort(a);
assert isSorted(a);
show(a);
}
}
在大多數(shù)情況下,排序的代碼智慧通過兩個方法操作數(shù)據(jù):less() 方法對元素進行比較,exch() 方法將元素交換位置。isSorted()判斷是否完成排序。
額外的內(nèi)存使用 : 排序算法的額外內(nèi)存和運行時間同等重要。排序算法可以分成兩類,除了函數(shù)調(diào)用所需要的棧和固定數(shù)目的實例變量之外無需額外內(nèi)存的原地排序算法,以及需要額外內(nèi)存空間來存儲另一份數(shù)組副本的其他排序算法。
選擇排序
public class Selection
{
public static void sort(Comparable[] a)
{ // 將a[] 按升序排列
int N = a.length; // 數(shù)組長度
for(int i = 0; i< N; i++){ // 將a[i] 和 a[i+1.. N] 中最小元素交換
int min = i; // 最小元素的索引
for(int j = i+1; j< N; j++)
if(less(a[j], a[min])) min =j;
exch(a,i, min);
}
}
}
總的來說:排序算法是一種很容易理解和實現(xiàn)的簡單排序算法,它有兩個很鮮明的特點。
- 運行時間和輸入無關
- 數(shù)據(jù)一定是最少的。
命題A: 對于長度為N 的數(shù)組,選擇排序需要大約 N^2 /2 次比較和 N 次交換。
插入排序
public class Insertion
{
public static void sort(Comparable[] a)
{
int N = a.length;
for(int i = 1; i< N; i++)
{
for(int j = i; j > 0 && less(a[j], a[j-1]); j--)
exch(a,j,j-1);
}
}
}
下面是幾種典型的部分有序的數(shù)組:
- 數(shù)組中每個元素距離它的最終位置都不遠
- 一個有序的數(shù)組接一個小數(shù)組
- 數(shù)組中有幾個元素的位置不確定
插入排序?qū)@樣的數(shù)組很有效,而選擇排序則不然。事實上,當?shù)怪玫臄?shù)量很少時,插入排序很可能比本章中的其他任何算法都要快。
命題B 對于隨機排列的長度為N且主鍵不重復的數(shù)組,平均情況下插入排序需要 ~ N^2 /4 次比較以及 ~ N^2 /4 次交換。最壞情況下需要 ~N^2/2 次比較和 ~ N^2/2 最好的情況需要 N-1 次比較和0 次交換。
插入排序需要的交換操作和數(shù)組中倒置的數(shù)量相同,需要的比較次數(shù)大于等于倒置的數(shù)量,小于等于倒置的數(shù)量加上數(shù)組的大小再減一。
比較兩個算法
對于隨機排序的無重復主鍵的數(shù)組,插入排序和選擇排序的運行時間是平方級別的,兩者之比應該是一個較小的常熟。
希爾排序
希爾排序的思想是一個 h 有序數(shù)組就是 h 個互相獨立的有序數(shù)組編織在一起組成一個數(shù)組。
public class Shell
{
public static void sort(Comparable[] a)
{
int N = a.length;
int h = 1;
while(h < N/3) h = 3*h+1;
while(h >= 1)
{
for(int i = h; i<N; i++)
{
for(int j = i; j>= h && less(a[j],a[j-h]; j-= h))
exch(a,i,j-h);
}
h = h/3;
}
}
}
歸并排序
要將一個數(shù)組排序,可以先將它分成兩半分別排序,然后將結(jié)果歸并起來。
-
自頂向下的歸并排序
public class Merge{ private staitc Comparable[] aux; //歸并所需的輔助數(shù)組 public static void sort(Comparable[] a){ aux = new Comparable[a.kength]; //一次性分配空間 sort(a,0,a.length - 1); } private static void sort(Comparable[] a, int lo, int hi){ if(hi <= lo) return; int mid = lo +(hi - lo)/2; sort(a,lo,mid); //將左半邊排序 sort(a, mid+1, hi); //將右半邊排序 merge(a,lo,mid,hi); //歸并結(jié)果 } }命題F: 對于長度為N的任意數(shù)組,自頂向下的歸并排序需要 1/2N lg(N) 至 Nlg(N)次比較。
命題G: 對于長度為N的任意數(shù)組,自頂向下的歸并排序最多需要訪問數(shù)組 6NlgN 次。
- 對小規(guī)模子數(shù)組使用插入排序
- 測試數(shù)組是否已經(jīng)有序
- 不將元素復制到輔助數(shù)組
-
自底向上的歸并排序
public class MergeBU{ private static Comparable[] aux; public static void sort(Comparable[] a){ //進行l(wèi)gN 次兩兩歸并 int N = a.length; aux = new Comparable[N]; for(int sz = 1; sz < N; sz = sz+sz) //sz 子數(shù)組大小 for(int lo = 0; lo < N-sz; lo += sz+sz) //lo: 子數(shù)組索引 merge(a,lo,lo+sz-1, Math.min(lo+sz+sz+1, N-1)); } }命題H: 對于長度為 N 的任意數(shù)組,自底向下的歸并排序需要 1/2 NlgN 至 NlgN 次比較,最多訪問數(shù)組 6NlgN 次。
命題I: 沒有任何基于比較的算法能夠保證使用少于 lg(N!) ~ NlgN 次比較長度將問 N 的數(shù)組排序。
排序算法的復雜度
快速排序
-
基本算法
public class Quick{ public static void sort(Comparable[] a){ StdRandom.shuffle(a); //消除對輸入的依賴 sort(a,0, a.length -1); } private static void sort(Comparable[] a, int lo, int hi){ if(hi <= lo) return; int j = partition(a, lo, hi); //切分 sort(a, lo, j-1); //將左半部分 sort(a, j+1, hi); //將右半部分 } }// 快速排序的切分 private static int partition(Comparable[] a, int lo, int hi){ int i = lo, j = hi+1; //左右掃描指針 Comparable v = a[lo]; // 切分元素 while(true){ // 掃描左右,檢查掃描是否結(jié)束并交換文件 while(less(a[++i],v)) if(i == hi) break; while(less(v,a[--j])) if(j == lo) break; if(i>= j) break; exch(a,i,j); } exch(a,lo,j); //將 v = a[j] 放入正確的位置 return j; }
- 性能特點
命題K: 將長度為 N 的無重復數(shù)組排序,快速排序平均需要 ~2NlnN次比較
命題L: 快速排序最多約 N^2 次比較,但是隨機打亂數(shù)組能夠預防這種情況。
-
算法改進
- 切換到插入排序
- 三取樣切分
public class Quick2way{ private static void sort(Comparable[] a, int lo, int hi){ if(hi <= lo) return; int lt = lo, i = lo+1, gt = hi; Comparable v = a[lo]; while(i <= gt){ int cmp = a[i].compareTo(v); if(cmp < 0) exch(a, lt++, i++); else if(cmp > 0) exch(a,i,gt--); else i++; } sort(a, lo, lt - 1); sort(a, gt+1, hi); } }命題M: 不存在任何基于比較的排序算法能夠保證在 NH-N 次比較之內(nèi)將 N 個 元素排序,其中H 為由主鍵值出現(xiàn)頻率定義的香農(nóng)信息量。
命題N: 對于大小為 N 的數(shù)組,三向切分的快速排序需要~(2ln2)NH次比較。其中H 為由主鍵值出現(xiàn)頻率定義的香農(nóng)信息量。
- 熵最優(yōu)的排序
優(yōu)先隊列
API
-
初級實現(xiàn)
- 數(shù)組實現(xiàn)(無序)
- 數(shù)組實現(xiàn)(有序)
- 鏈表表示法
-
堆的定義
定義: 當一棵二叉樹的每個節(jié)點都大于等于它的兩個子結(jié)點時,它被成為堆有序。
-
堆的算法
由下至上的堆有序化(上?。?/p>
-
由上至下的堆有序化(下沉)
public class MaxPQ<Key extends Comparable<key>>{ private Key[] pd; private int N = 0; public MaxPQ(int maxN){ pq = (key[]) new Comparable[maxN+1]; } public boolean isEmpty(){ return N == 0; } public int size(){ return N; } public void insert(Key v){ pq[++N] = v; swim(N); } public Key delMax(){ Key max = pq[1]; //從根結(jié)點得到最大元素 exch(1,N--); //將其和最后一個結(jié)點交換 pq[N+1] = null; // 防止越界 return max; // 恢復堆的有序性 } }命題Q: 對于一個含有N 個元素的基于堆的優(yōu)先隊列,插入元素操作只需要不超過(lgN+1) 次比較,刪除最大元素的操作需要不超過 2lgN 次比較。
-
堆排序
public static void sort(Comparable[] a){ int N = a.length; for(int k = N/2; k >= 1;k--) sink(a,k,N); while(N>1){ exch(a,1,N--); sink(a,1,N); } }- 堆的構(gòu)造
- 下沉排序
命題 S: 將N個元素排序,堆排序只需要少于(2NlgN+2N)次比較。