【算法】第二章排序

##目錄

  1. 初級排序
    1. 選擇排序
    2. 插入排序
    3. 希爾排序
  2. 歸併排序
    1. 自頂向下排序
    2. 自底向上排序
  3. 快速排序
  4. 優先隊列

##初級排序 ###一、 選擇排序

簡述:選擇排序就是遍歷一遍數組把最小的和第一個數字交換。第二遍遍歷數組時候選擇和第二個交換,一次類推。

//注意不要在for循環中用a.length()不然每次都要獲取a.length();
public voiv sort(Comparable[] a){
	//for(int i=0,i<a.length;i++){
	int N = a.length;
	for(int i=0,i<N;i++){
		int min = i;
		for(int j=i+1;j<N;j++){
			//if(a[min]>a[j]){
			if(less(a[j],a[min])){
				min = j;
			}
			//int tmp = a[i];
			//a[i] = a[min];
			//a[min] = tmp;
			exch(a,j,min);
		}
	}
}

二、 插入排序

簡述:類似於打牌時候邊抽牌邊整理。第二張開始,就最大的開始對比,然後直到不大時候插入。

public void sort(Comparable[] a){
	int N = a.length;
	//下面是我寫的,不知道書上有什麼優勢。有空測一下。
	//for(int i=1;i<N;i++){
	//	if(less(a[i],a[i-1])){
	///		int pos = i;
	//		while(less(a[pos],a[pos-1])&&pos>0){
	//			exch(a,pos,pos-1);
	//			pos--;
	//		} 
	//	}
	//}
	
	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);
		}
	}
}

命題C

插入排序的比較次數 大於等於倒置的數量,小於等於倒置數量加上數組大小再減一。
最壞就是全倒置嘛,這時候每倒置一次就要多比一次,然而數組裏每個數都要比較,除了第一個數時候和自身不比較,所以是 最差等於倒置數量+數組大小-1。
所以對 部分有序的數組十分高效,也適合小規模數組。

當不考慮交換和比較成本,兩個差不多,但是感覺數組規模小的化插入有優勢,規模大的化選擇有優勢。【平均而言】

三、希爾排序

public static void sort(Comparable a){
	int N = a.length;
	int h = 1;
	//
	while(h<N/3){
		h = h * 3 +1
	}
	while(h>=1){
		for(int i=h;i<N;i++){
			for(int j=i;j<N&&less(a[j],a[j-h]);j-=h){
				exch(a,j,j-h);
			}
		}
		h=h/3;
		
	}
}

平均每個比較次數增幅爲N1/5 大的話才比較明顯。
沒什麼可說的,三方比較,希爾排序一直賽高。可以把希爾排序看作套殼的插入排序。

歸併排序

最基礎先是合併兩個有序表的方法[書上叫 原地歸併抽象方法,不知道爲甚麼]

//謹記讀數組時候要用copy數組,改寫的時候才用原數組
public static void merge(Comparable[] a,int lo,int mid,int hi){
	int N = a.length;
	//copy
	for(int k = lo;k <= hi;k++){
		auk[k]=a[k];
	}
	int i = lo;
	int j = mid + 1;
	for(int k=0;k<N;k++){
		if(i>mid) 							a[k] = auk[j++];
		else if(j>=hi)						a[k] = auk[i++];
		else if(less(aux[j],aux[i])) 		a[k] = auk[i++];
		else 								a[k] = auk[i++];
	}
}

接下來是使用到原地歸併的兩個歸併方法,使用思想不一樣,但是我覺得本質都一樣。
我測試了 兩個排序50個100萬完全隨機數據,花的時間都差不多。
前者說是自頂向下,其實也是下到兩兩元素纔開始正式比對。

自頂向下的歸併排序 分治思想

private static Comparable[] aux;
public static void sort(Comparable[] a){
	int N = a.length;
	aux = new Comparable(N);
	sort(a,0,N-1);
}

public static void sort(Comparable[] a,int lo ,int hi){
	int mid = lo + (hi - lo)/2;
	if(lo>=hi) return;
	sort(a,lo,mid);
	sort(a,mid+1,hi);
	merge(a,lo,mid,hi);
}

因爲插入排序適合小數組,據說是 歸併 + 插入 賽高。還沒有驗證。

自底向上的歸併排序

private static Compararble[] aux;
public static void sort(Comparable[] a){
	int N = a.length;
	//分割,1 ,2 ,4 ,6 ,8
	//邊界問題最頭疼,首先爲什麼是N 不是N - 1?
	//最重要的一點是 我門用sz表達的是分割的數組數字個數,如果是個數自然是按原值N過來。
	for(int sz=1;sz < N ;sz = sz + sz){	
		//這裏爲什麼是N
		//暫時不知道,但是我用-1試過好幾次也沒差。反正就是爲了防止過界又防止餘下部分太小
		for(int lo = 0;lo < N - sz;lo +=2*sz){
			merge(a,lo,lo + sz -1 ,Math.min(lo + 2*sz - 1,N - 1));
		}
	}
}

最後的幾個結論不是很懂,只能二刷時候再看了。

快速排序

快速排序也是把問題分開再分開,不同的是它並不是平均分成兩組,而是產生一個分開因子。籠統而言和歸併差不多吧,但是我測試速度感覺比歸併快一點。時間能達到1/2甚至更多

public static void sort(Comparable[] a){
	int N = a.length;
	sort(a,0,N-1);
}

public static void sort(Comparable[] a,int lo,int hi){
	if(lo>=hi) return;
	int par = partition(a,lo,hi);
	sort(a,lo,par - 1);
	sort(a,par + 1 ,hi);
}

//core code
public static void partition(Comparable[] a, int lo,int hi){
	Comparable v = a[lo];
	i = lo;
	j = hi+1;
	while(true){
		//while(less(a[++i],v)) if(i>=hi) break;
		while(less(a[++i],v)) if(i==hi) break;
		//while(less(v,a[--j])) if(j<=lo) break;
		//因爲a[lo] = v; 所以 當j = lo時候根本進不來,所以if(j==lo)是個多餘的條件
		while(less(v,a[--j])) {}/*if(j==lo) break;*/
		if(i>=j) break;
		exch(a,i,j);
	}
	exch(a,j,lo);
	return j;
}

//quick3way
public static void sort3way(Comparable[] a,int lo,int hi){
	if(lo>=hi) return;
	
	int lt = lo;
	int gt = hi;
	int i = lo + 1;
	Comparable v = a[lo];
	//其實就是用 i 去遍歷每一個數字,小的從 lt 插入 大的 從 gt插入。此爲三向切分
	while(i<=gt){
		int cmp = a[i].compareTo(v);
		if(cmp<0) exch(a,lt++,i++);
		//換了之後i不用往下走,因爲gt已經把一個陌生值換給它了。
		else if(cmp>0) exch(a,i,gt--);
		//和自己相等,下一個
		else i++;
	}
	//lt-1 都是比v小的
	//gt+1 都是比v大的
	//剩下的都是等於v的。
	sort3way(a,lo,lt-1);
	sort3way(a,gt+1,hi);
}

###優先隊列 優先隊列是一種思想,只能說堆排序用了這種思想。 大概就是當一個完全二叉樹時候,對於一個節點k(從 1 開始的序號), 他的父節點爲k/2 左子節點爲 2k 右子節點爲 2k + 1 下面的節點總比上面的節點大(根據實際用途,自己要求) 我們可以通過遍歷某個節點的 當我們添加一個方法的時候 可以先將插入對象添加到隊尾 可以使用 swim方法,讓其上浮 ####由下至上的對有序化(上浮) ``` //注意我們這裏假設數組從 index 1 開始 public void swim(Comparable[] a,int k){ //k爲插入元素index while(k>=1){ if(less(a[k/2],a[k])){ exch(a,k/2,k); k = k/2; } } //這樣直接上浮到適合的位置爲止。 } ``` 書上介紹優先隊列用法,就是用來快速刪除最大(最小【需另設隊列】)元素的。

當我們刪除最大節點,先使他下沉,使他子元素補位,
由上至下的堆有序化,下沉

//注意我們這裏假設數組從 index 1 開始
public void sink(Comparable[] a,int k,int N){
	while(k<=N){
		int j = 2 * k;
		//讓哪個兒子重哪個兒子上
		if(j<N && less(a[j],a[j+1]))  j=j+1;
		if(less(a[j],a[k])) break;
		exch(a,k,j);
		//換做最大元素,下一回合。
		k = j;
	}
}

堆排序

public static void sort(Comparable[] a){
	//第一步 排成優先隊列
	int N = a.length;
	//元素先按1開始,等到使用數組的時候,再恢復實際元素
	for(int k=N/2;k>=1;k--){
		sink(a,k,N);
	}

	while(N>1){
		//最前面肯定使最大的,所以將其和正確位置置換
		exch(a,0,N-1);
		//最先面的已經不是最輕的了,換人。
		sink(a,1,--N);
	}
}

public static void sink(Comparable[] a,int k,int N){
	while(2*k<=N){
		int j = 2 * k;
		//使用時候按實際位置所以要-1.包括less 和 exch方法
		if(j < N && less(a[j-1],a[j])) j = j + 1;
		//如果不比兒子輕,就此結束,下一回合。
		if(!less(a[k-1],a[j-1])) break;
		exch(a,j-1,k-1);
		k = j;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章