數據結構(23)--排序篇之插入排序

參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社

本文中的代碼可從這裏下載:https://github.com/qingyujean/data-structure

1.直接插入排序

1.1基本思想

    一趟直接插入排序的基本思想: 將記錄L.r[i]插入到有序子序列L.r[1..i-1]中,使記錄的有序序列從L.r[1..i-1]變爲L.r[1..i]。 完成這個“插入”分三步進行:
     1.查找L.r[i]在有序子序列L.r [1..i-1]中的插入位置j;
     2.將L.r [j..i-1]中的記錄後移一個位置;
     3.將L.r [i]複製到L.r [j]的位置上。
     整個排序過程進行n–1趟插入,即:先將序列中的第1個記錄着成一個有序的子序列,然後從第2個記錄起逐個插入,直至整個序列變成接關鍵字非遞減有序序列爲止。

1.2代碼實現

 

package sort.insertionSort;
public class StraightSort {
	/**
	 * @param args
	 */
	//對順序表L做直接插入排序
	public static void InsertSort(int[] L){
		//先將第一個元素看成是一個有序子序列
		for(int i = 2; i <= L.length-1; i++){
			//在已經有序的1->i-1的子序列中插入第i個元素,以保證仍然有序,成爲一個1->i的有序子序列
			L[0] = L[i];//監視哨
			int j = i-1;
			/*
			for(; j > 0; j--){//沒有利用監視哨,仍然用j>0作爲條件以避免數組下標越界
				if(L[0] < L[j])
					L[j+1] = L[j];
				else
					break;
			}
			*/
			for(; L[0] < L[j]; j--)
				L[j+1] = L[j];//利用監視哨
			//當L[0] >= <[j]時跳出循環,由於j做了一次自減,所以是L[j+1] = L[0],
			//當是因爲=而跳出循環時(j後來沒有自減),L[0]插在L[j]的後面以保證了“穩定” 
			L[j+1] = L[0];
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 53, 27, 36, 15, 69, 42}; //0號單元未使用
		InsertSort(test);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}

運行結果:

 

1.3性能分析

    直接插入排序性能分析 ,實現排序的基本操作有: (1)“比較” 關鍵字的大小 (2)“移動”記錄
    對於直接插入排序: 
    最好情況“比較”次數:n-1;“移動”次數:2(n-1)
    最壞的情況“比較”和“移動”的次數均達到最大值,分別爲:(n+2)(n-1)/2;(n+4)(n-1)/2
    由於待排記錄序列是隨機的,取上述二值的平均值。所以直接插入排序的時間複雜度爲 O(n^2)
    直接插入排序是“穩定的”:關鍵碼相同的兩個記錄,在整個排序過程中,不會通過比較而相互交換。

2.折半插入排序

2.1基本思想

    考慮到 L.r[1..i-1] 是按關鍵字有序的有序序列,則可以利用折半查找實現“ L.r[1…i-1]中查找 L.r[i] 的插入位置”如此實現的插入排序爲折半插入排序。折半插入排序在尋找插入位置時,不是逐個比較而是利用折半查找的原理尋找插入位置。待排序元素越多,改進效果越明顯。

2.2代碼實現

 

package sort.insertionSort;

public class BinaryInsertionSort {

	/**
	 * @param args
	 */
	//對順序表L做折半插入排序,利用折半查找快速找到要插入的位置
	public static void binaryInsertSort(int[] L){
		for(int i = 2; i <= L.length-1; i++){		
			//利用折半查找找到要插入的位置
			int low = 1, high = i-1;//在1->i-1的有序子序列中插入第i個元素,使之成爲1->i的有序子序列
			L[0] = L[i];//暫存要插入的元素
			while(low <= high){
				int mid = (low+high)/2;
				if(L[0] < L[mid])
					high = mid -1;
				else
					//L[0] >= L[mid]
					low = mid+1;//等於當成大於處理,這樣後出現的相等值就會排在後面,從而到達“穩定”
			}
			//此時high = low-1,且high+1即low的位置即爲要插入的位置
			for(int j = i-1; j >= low; j--)
				L[j+1] = L[j];
			L[low] = L[0];
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 53, 27, 36, 15, 69, 42}; //0號單元未使用
		binaryInsertSort(test);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}

運行結果:

 

2.3性能分析

    折半插入排序減少了關鍵字的比較次數,但記錄的移動次數不變,其時間複雜度與直接插入排序相同,時間複雜度爲O(n^2) 。折半插入排序是“穩定的”。

 

3.希爾排序(縮小增量排序)

3.1基本思想

 

    希爾排序(Shell Sort)又稱爲“縮小增量排序”。其基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因爲直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。

3.2代碼實現

 

package sort.insertionSort;

public class ShellSort {

	/**
	 * @param args
	 */
	//對順序表L做一趟希爾排序,本算法和一趟直接插入排序相比,做了如下修改:
	//1.前後記錄位置的增量式dk,而不是1
	//2.L[0]只是暫存單元,而不再是哨兵
	public static void shellInsert(int[] L, int dk){
		/*對於下面for循環的i = i+dk和i++的分析:
		一趟希爾排序裏有L.length/dk個子序列,每個子序列要進行直接插入排序,即要進行L.length/dk個直接插入排序
		子序列輪着來(有點併發的感覺),即第一個子序列的第2個數排完序,然後是第2個子序列的第2個數排序,然後是第3個子序列。。。
		i繼續自增,然後是第1個子序列的第3個數往第一個子序列的1->2的有序子序列裏插入並排序,然後是第2個子序列的第3個數
		往第2個子序列的1->2的有序子序列裏插入並排序,然後是第3個子序列的第3個數往第3個子序列的1->2的有序子序列裏插入並排序。。。
		i繼續自增,然後是第1個子序列的第4個數往第一個子序列的1->3的有序子序列裏插入並排序,接着
		第2個子序列的第4個數往第2個子序列的1->3的有序子序列裏插入並排序.。。。。。以此類推,直到所有的完成
		 */
		//相當於每個子序列的第一個數都被看成是每個子序列的有序子序列
		for(int i = 1+dk; i <= L.length-1; i++){
			L[0] = L[i];
			//找L[i]應該插入的位置
			int j = i-dk;
			for(; j>0&&L[0]<L[j]; j-=dk)
				L[j+dk] = L[j];
			L[j+dk] = L[0];
		}
	}
	//按增量dlta[0......len(dlta)-1]對順序表L做希爾排序
	public static void shellSort(int[] L, int[] dlta){
		for(int i = 0; i < dlta.length; i++)
			shellInsert(L, dlta[i]);
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 65, 49, 97, 25, 25, 13}; //0號單元未使用
		int[] dlta = {3, 2, 1};//應使增量序列中的值沒有除1之外的公因子,並且最後一個增量值必須等於1。
		shellSort(test, dlta);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}

 

 

 

 

 

運行結果:

3.3性能分析

    雖然我們給出的算法是三層循環,最外層循環爲log2n數量級,中間的for循環是n數量級的,內循環遠遠低於n數量級,因爲當分組較多時,組內元素較少;此循環次數少;當分組較少時,組內元素增多,但已接近有序,循環次數並不增加。因此,希爾排序的時間複雜性在O(nlog2n)和O(n^2 )之間,大致爲O(n^1. 3)
    希爾排序的時間複雜度較直接插入排序低。希爾排序的分析是一個複雜的問題,因爲它的時間是和所取“增量”序列的函數密切相關。到目前爲止,還沒有求得一種最好的增量序列,但有大量的局部結論。 
    注意:應使增量序列中的值沒有除1之外的公因子,並且最後一個增量值必須等於1。
    由於希爾排序對每個子序列單獨比較,在比較時進行元素移動,有可能改變相同排序碼元素的原始順序,因此希爾排序是不穩定的。

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