數據結構 No.8 插入排序 希爾排序

比較

名稱數據對象穩定性時間複雜度額外空間複雜度描述
平均最壞
冒泡排序數組{\displaystyle O(n^{2})}O(n^{2}){\displaystyle O(1)}O(1)(無序區,有序區)。
從無序區通過交換找出最大元素放到有序區前端。
選擇排序數組{\displaystyle O(n^{2})}O(n^{2}){\displaystyle O(1)}O(1)(有序區,無序區)。
在無序區裏找一個最小的元素跟在有序區的後面。對數組:比較得多,換得少。
鏈表
插入排序數組、鏈表{\displaystyle O(n^{2})}O(n^{2}){\displaystyle O(1)}O(1)(有序區,無序區)。
把無序區的第一個元素插入到有序區的合適的位置。對數組:比較得少,換得多。
堆排序數組{\displaystyle O(n\log n)}O(n\log n){\displaystyle O(1)}O(1)(最大堆,有序區)。
從堆頂把根卸出來放在有序區之前,再恢復堆。
歸併排序數組{\displaystyle O(n\log ^{2}n)}{\displaystyle O(n\log ^{2}n)}{\displaystyle O(1)}{\displaystyle O(1)}把數據分爲兩段,從兩段中逐個選最小的元素移入新數據段的末尾。
可從上到下或從下到上進行。
{\displaystyle O(n\log n)}O(n\log n){\displaystyle O(n)+O(\log n)}O(n)+O(\log n)
如果不是從下到上
鏈表{\displaystyle O(1)}O(1)
快速排序數組{\displaystyle O(n\log n)}O(n\log n){\displaystyle O(n^{2})}O(n^{2}){\displaystyle O(\log n)}O(\log n)(小數,基準元素,大數)。
在區間中隨機挑選一個元素作基準,將小於基準的元素放在基準之前,大於基準的元素放在基準之後,再分別對小數區與大數區進行排序。
希爾排序數組{\displaystyle O(n\log ^{2}n)}O(n\log ^{2}n){\displaystyle O(n^{2})}O(n^{2}){\displaystyle O(1)}O(1)每一輪按照事先決定的間隔進行插入排序,間隔會依次縮小,最後一次一定要是1。

計數排序數組、鏈表{\displaystyle O(n+m)}O(n+m){\displaystyle O(n+m)}O(n+m)統計小於等於該元素值的元素的個數i,於是該元素就放在目標數組的索引i位(i≥0)。
桶排序數組、鏈表{\displaystyle O(n)}O(n){\displaystyle O(m)}O(m)將值爲i的元素放入i號桶,最後依次把桶裏的元素倒出來。
基數排序數組、鏈表{\displaystyle O(k\times n)}O(k\times n){\displaystyle O(n^{2})}O(n^{2})
一種多關鍵字的排序算法,可用桶排序實現。
   本次介紹的排序有兩種、希爾排序和快速排序

希爾排序

    定義

希爾排序按其設計者希爾(Donald Shell)的名字命名,該算法由1959年公佈。一些老版本教科書和參考手冊把該算法命名爲Shell-Metzner,即包含Marlene Metzner Norton的名字,但是根據Metzner本人的說法,“我沒有爲這種算法做任何事,我的名字不應該出現在算法的名字中。”
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
  1. 插入排序在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率。
  2. 但插入排序一般來說是低效的,因爲插入排序每次只能將數據移動一位。

     基本思想

先取一個小於n的整數d1作爲第一個增量,把文件的全部記錄分組。所有距離爲d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2<d1重複上述的分組和排序,直至所取的增量
  
=1(
  
<
  
…<d2<d1),即所有記錄放在同一組中進行直接插入排序爲止。
該方法實質上是一種分組插入方法
比較相隔較遠距離(稱爲增量)的數,使得數移動時能跨過多個元素,則進行一次比較就可能消除多個元素交換。D.L.shell於1959年在以他名字命名的排序算法中實現了這一思想。算法先將要排序的一組數按某個增量d分成若干組,每組中記錄的下標相差d.對每組中全部元素進行排序,然後再用一個較小的增量對它進行,在每組中再進行排序。當增量減到1時,整個要排序的數被分成一組,排序完成。
一般的初次取序列的一半爲增量,以後每次減半,直到增量爲1。
給定實例的shell排序的排序過程
假設待排序文件有10個記錄,其關鍵字分別是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次爲:
5,3,1
希爾排序實例希爾排序實例

     代碼實現

/*
 * 希爾排序
 */
public class ShellSort {
	/**
	 * 排序方法
	 */
	public static void sort(long[] arr) {
		//初始化一個間隔
		int h = 1;
		//計算最大間隔
		while(h < arr.length / 3) {
			h = h * 3 + 1;
		}
		
		while(h > 0) {
			//進行插入排序
			long tmp = 0;
			for(int i = h; i < arr.length; i++) {
				tmp = arr[i];
				int j = i;
				while(j > h - 1 && arr[j - h] >= tmp) {
					arr[j] = arr[j - h];
					j -= h;
				}
				arr[j] = tmp;
			}
			//減小間隔
			h = (h - 1) / 3;
		}
	}
}

快速排序

    簡介

快速排序(Quicksort)是對冒泡排序的一種改進。
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列

    思路分析

設要排序的數組是A[0]……A[N-1],首先任意選取一個數據(通常選用數組的第一個數)作爲關鍵數據,然後將所有比它小的數都放到它前面,所有比它大的數都放到它後面,這個過程稱爲一趟快速排序。值得注意的是,快速排序不是一種穩定的排序算法,也就是說,多個相同的值的相對位置也許會在算法結束時產生變動。
一趟快速排序的算法是:
1)設置兩個變量i、j,排序開始的時候:i=0,j=N-1;
2)以第一個數組元素作爲關鍵數據,賦值給key,即key=A[0];
3)從j開始向前搜索,即由後開始向前搜索(j--),找到第一個小於key的值A[j],將A[j]和A[i]互換;
4)從i開始向後搜索,即由前開始向後搜索(i++),找到第一個大於key的A[i],將A[i]和A[j]互換;
5)重複第3、4步,直到i=j; (3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]不大於key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到爲止。找到符合條件的值,進行交換的時候i, j指針位置不變。另外,i==j這一過程一定正好是i+或j-完成的時候,此時令循環結束)。
排序演示

      

假設用戶輸入瞭如下數組:
下標
0
1
2
3
4
5
數據
6
2
7
3
8
9
創建變量i=0(指向第一個數據), j=5(指向最後一個數據), k=6(賦值爲第一個數據的值)。
我們要把所有比k小的數移動到k的左面,所以我們可以開始尋找比6小的數,從j開始,從右往左找,不斷遞減變量j的值,我們找到第一個下標3的數據比6小,於是把數據3移到下標0的位置,把下標0的數據6移到下標3,完成第一次比較:
下標
0
1
2
3
4
5
數據
3
2
7
6
8
9
i=0 j=3 k=6
接着,開始第二次比較,這次要變成找比k大的了,而且要從前往後找了。遞加變量i,發現下標2的數據是第一個比k大的,於是用下標2的數據7和j指向的下標3的數據的6做交換,數據狀態變成下表:
下標
0
1
2
3
4
5
數據
3
2
6
7
8
9
i=2 j=3 k=6
稱上面兩次比較爲一個循環。
接着,再遞減變量j,不斷重複進行上面的循環比較。
在本例中,我們進行一次循環,就發現i和j“碰頭”了:他們都指向了下標2。於是,第一遍比較結束。得到結果如下,凡是k(=6)左邊的數都比它小,凡是k右邊的數都比它大:
下標
0
1
2
3
4
5
數據
3
2
6
7
8
9
如果i和j沒有碰頭的話,就遞加i找大的,還沒有,就再遞減j找小的,如此反覆,不斷循環。注意判斷和尋找是同時進行的。
然後,對k兩邊的數據,再分組分別進行上述的過程,直到不能再分組爲止。
注意:第一遍快速排序不會直接得到最終結果,只會把比k大和比k小的數分到k的兩邊。爲了得到最後結果,需要再次對下標2兩邊的數組分別執行此步驟,然後再分解數組,直到數組不能再分解爲止(只有一個數據),才能得到正確結果。

    代碼實現

/*
 * 快速排序
 */
public class QuickSort {

	/**
	 * 劃分數組
	 */
	public static int partition(long arr[],int left, int right,long point) {
		int leftPtr = left - 1;
		int rightPtr = right;
		while(true) {
			//循環,將比關鍵字小的留在左端
			while(leftPtr < rightPtr && arr[++leftPtr] < point);
			//循環,將比關鍵字大的留在右端
			while(rightPtr > leftPtr && arr[--rightPtr] > point);
			if(leftPtr >= rightPtr) {
				break;
			} else {
				long tmp = arr[leftPtr];
				arr[leftPtr] =  arr[rightPtr];
				arr[rightPtr] = tmp;
			}
		}
		//將關鍵字和當前leftPtr所指的這一個進行交換
		long tmp = arr[leftPtr];
		arr[leftPtr] =  arr[right];
		arr[right] = tmp;
		return leftPtr;
	}
	
	public static void sort(long[] arr, int left, int right) {
		if(right - left <= 0) {
			return;
		} else {
			//設置關鍵字
			long point = arr[right];
			//獲得切入點,同時對數組進行劃分
			int partition = partition(arr, left, right, point);
			//對左邊的子數組進行快速排序
			sort(arr,left,partition - 1);
			//對右邊的子數組進行快速排序
			sort(arr,partition + 1, right);
		}
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章