1. 程式人生 > >插入排序(Insertion Sort)以及優化改進

插入排序(Insertion Sort)以及優化改進

介紹:

插入排序是一種簡單直觀的排序演算法。它的工作原理非常類似於我們抓撲克牌

      

 

  對於未排序資料(右手抓到的牌),在已排序序列(左手已經排好序的手牌)中從後向前掃描,找到相應位置並插入。

  插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。

  具體演算法描述如下:

  1. 從第一個元素開始,該元素可以認為已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 如果該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置後
  6. 重複步驟2~5

對序列{ 6, 5, 3, 1, 8, 7, 2, 4 }進行插入排序的實現過程如下

      

Java程式碼示例:

package com.insertsort;

//分類 ------------- 內部比較排序
//資料結構 ---------- 陣列
//最差時間複雜度 ---- 最壞情況為輸入序列是降序排列的,此時時間複雜度O(n^2)
//最優時間複雜度 ---- 最好情況為輸入序列是升序排列的,此時時間複雜度O(n)
//平均時間複雜度 ---- O(n^2)
//所需輔助空間 ------ O(1)
//穩定性 ------------ 穩定

public class Insert {
	public void insertionSort(int array[]) { // 定義一個排序陣列
		// 假設當前陣列第一個,即下標為0的數是已經排好的序,因此,起始位置是第二個數(下標為1)
		for (int i = 1; i < array.length; i++) { 
			int preIndex = i - 1; // 當前已經排好序的
			int current = array[i]; // 當前需要排序的數(從陣列第二個開始,1號下標)
			// 將當前需要排序的數與已排好的數從右到左依次比較,滿足以下條件:已存在有排好的數;當前數與已排好的數相比較,當前數小於已排好的數,就往前依次比較,直到找到當前數大於等於已排數,即是當前數該排的位置。
			while (preIndex >= 0 && array[preIndex] > current) { //開始時:1號下標數與0號下標數相比較
				array[preIndex + 1] = array[preIndex]; // 如果已排好序的數比當前數大,就將其往右移動
				preIndex--;
			}
			// 直到已排的數比當前數小(或者相等),將當前數插入到已排好序的數的右邊(相等元素的相對次序未變,所以插入排序是穩定的)
			array[preIndex + 1] = current; 
		}
	}

}

測試程式碼:

package com.insertsort;

public class Test {

	public static void main(String[] args) {
		Insert insert = new Insert();
		int array[] = { 6, 5, 3, 1, 8, 7, 2, 4 };
		insert.insertionSort(array);
		System.out.println("插入排序結果:");
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + " ");
		}
	}
}

動態演示圖例項:

       插入排序不適合對於資料量比較大的排序應用。但是,如果需要排序的資料量很小,比如量級小於千,那麼插入排序還是一個不錯的選擇。 插入排序在工業級庫中也有著廣泛的應用,在STL的sort演算法和stdlib的qsort演算法中,都將插入排序作為快速排序的補充,用於少量元素的排序(通常為8個或以下)。

插入排序的改進:二分插入排序

對於插入排序,如果比較操作的代價比交換操作大的話,可以採用二分查詢法來減少比較操作的次數,我們稱為二分插入排序

Java實現程式碼

package com.insertsort;

/**
 * 二分插入排序法 
 * 分類 -------------- 內部比較排序
 * 資料結構 ---------- 陣列 最差時間複雜度 ---- O(n^2)
 * 最優時間複雜度 ---- O(nlogn) 
 * 平均時間複雜度 ---- O(n^2) 
 * 所需輔助空間 ------ O(1) 
 * 穩定性 ------------穩定
 * 
 * @author Lenovo
 *
 */
public class IsertSortDichotomy {
	public void insertionSortDichotomy(int array[]) {
		for (int i = 1; i < array.length; i++) {
			int get = array[i]; // 右手抓到一張撲克牌
			int left = 0; // 拿在左手上的牌總是排序好的,所以可以用二分法
			int right = i - 1; // 手牌左右邊界進行初始化
			while (left <= right) { // 採用二分法定位新牌的位置
				int min = (left + right) / 2;
				if (array[min] > get) {
					right = min - 1;
				} else {
					left = min + 1;
				}
			}
			for (int j = i - 1; j >= left; j--) { // 將欲插入新牌位置右邊的牌整體向右移動一個單位
				array[j + 1] = array[j];
			}
			array[left] = get; // 將抓到的牌插入手牌
		}
	}
}

測試:

IsertSortDichotomy iDichotomy = new IsertSortDichotomy();
		int array1[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };
		iDichotomy.insertionSortDichotomy(array1);
		System.out.println("二分插入排序結果:");
		for (int i = 0; i < array1.length; i++) {
			System.out.print(array1[i] + " ");
		}

插入排序的更高效改進:希爾排序(Shell Sort)

介紹:

       1959年Shell發明,第一個突破O(n2)的排序演算法,是簡單插入排序的改進版。它與插入排序的不同之處在於,它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序

       希爾排序,也叫遞減增量排序,是插入排序的一種更高效的改進版本。希爾排序是不穩定的排序演算法。

  希爾排序是基於插入排序的以下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的資料操作時,效率高,即可以達到線性排序的效率
  • 但插入排序一般來說是低效的,因為插入排序每次只能將資料移動一位

  希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的效能。這樣可以讓一個元素可以一次性地朝最終位置前進一大步。然後演算法再取越來越小的步長進行排序,演算法的最後一步就是普通的插入排序,但是到了這步,需排序的資料幾乎是已排好的了(此時插入排序較快)。
  假設有一個很小的資料在一個已按升序排好序的陣列的末端。如果用複雜度為O(n^2)的排序(氣泡排序或直接插入排序),可能會進行n次的比較和交換才能將該資料移至正確位置。而希爾排序會用較大的步長移動資料,所以小資料只需進行少數比較和交換即可到正確位置。

動態演示圖:

Java程式碼示例:

package com.insertsort;

//分類 -------------- 內部比較排序
//資料結構 ---------- 陣列
//最差時間複雜度 ---- 根據步長序列的不同而不同。已知最好的為O(n(logn)^2)
//最優時間複雜度 ---- O(n)
//平均時間複雜度 ---- 根據步長序列的不同而不同。
//所需輔助空間 ------ O(1)
//穩定性 ------------ 不穩定
public class Shell {
	public void shellSort(int array[]) {
		int h = 0;
		while (h <= array.length) { // 生成初始增量
			h = 3 * h + 1;
		}
		while (h >= 1) {
			for (int i = h; i < array.length; i++) {
				int j = i - h;
				int get = array[i];
				while (j >= 0 && array[j] > get) {
					array[j + h] = array[j];
					j = j - h;
				}
				array[j + h] = get;
			}
			h = (h - 1) / 3; // 遞減增量
		}
	}
}

測試:

Shell shell = new Shell();
int array2[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };
shell.shellSort(array2);
System.out.println("希爾排序結果:");
for (int i = 0; i < array2.length; i++) {
	System.out.print(array2[i] + " ");
}

以23, 10, 4, 1的步長序列進行希爾排序:  

  希爾排序是不穩定的排序演算法,雖然一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂。

  比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=2時分成兩個子序列 { 3, 10, 7, 8, 20 } 和  { 5, 8, 2, 1, 6 } ,未排序之前第二個子序列中的8在前面,現在對兩個子序列進行插入排序,得到 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,兩個8的相對次序發生了改變。

參考:https://www.cnblogs.com/onepixel/articles/7674659.html   http://www.cnblogs.com/eniac12/p/5329396.html