1. 程式人生 > >資料結構 排序

資料結構 排序

知識要點:

1、插入排序法(含折半插入排序法)、選擇排序法、泡排序法(氣泡排序)、快速排序法、

堆積排序法(堆排序)、歸併排序、基數排序等排序方法排序的原理、規律和特點;

2、各種排序演算法的時空複雜度的簡單分析。

排序的穩定性:排序後不改變原序列中相同元素的相對順序,則此排序方法是穩定的,反之是不穩定的;

                         一般情況下,排序的穩定性與其效率成反比;

插入排序法(含折半插入排序法):

演算法思想:每次將一個待排序的資料,跟前面已經有序的序列的數字一一比較找到自己合適的位置,插入到序列中,直到全部數

據插入完成。(找位置+挪位置)

void InsertSort(int data[],int n){
	int j;
	for(int i=1;i<n;i++){
		int t=data[i];//待插入資料
		for(j=i;j>0;j--){
			if(data[j-1]>t){
				data[j]=data[j-1];
				//元素後移;
			}
			else{
				break;
				//找到插入位置
			}
		}
		data[j]=t;
	}
}

折半插入排序和插入排序的時間複雜度相同,兩者的區別在於比較的次數不同 ,對於插入排序最壞比較次數為n(n-1)/2,即平均

比較次數為n^2數量級;而對於折半插入排序,採用折半查詢的方式查詢元素的插入位置,平均比較次數為nlogn數量級;兩者的

就在於確定元素插入位置的比較次數;

時間複雜度:O(n^2);    穩定性:穩定;

選擇排序法:

演算法思想:  陣列分成有序區和無序區,初始時整個陣列都是無序區,然後每次從無序區選一個最小的元素直接放到有序區的最

後,直到整個陣列變有序區。每一次選擇待排序列中的最小(最大)的值放入有序序列為尾部;

void SelectSort(int data[],int n){
	for(int i=0;i<n;i++){
		int t=i;
		for(int j=i+1;j<n;j++){
			if(data[j]<data[t]){
				t=j;
			}
		}
		if(t!=i){
			int temp=data[t];
			data[t]=data[i];
			data[i]=temp;
		}
	}
}

時間複雜度:O(n^2);    穩定性:不穩定/穩定;(基於交換的是不穩定的,基於插入的是穩定的)

泡排序法(氣泡排序):

演算法思想:依次比較相鄰的兩個數,將小數放在前面,大數放在後面,主觀上看就是大數依次排到末尾位置;

void BubbleSort(int data[],int n){
	for(int i=0;i<n;i++){
		for(int j=1;j<n;j++){
			if(data[j]<data[j-1]){
				int t=data[j];
				data[j]=data[j-1];
				data[j-1]=t;
			}
		}
	}
}

時間複雜度:O(n^2);    穩定性:穩定;

快速排序法:

演算法思想:選取一個基準,從後往前找比此數值大(小)的元素的位置,從前往後找比此數值小(大)的元素的位置

經過每一次排序都可將數值劃分為兩部分(比基準大和小的兩部分),在採用遞迴的方式分別將基準兩側的待排序列

進行同樣的操作,直至各個待排序列均有序;

void QuickSort(int data[],int start,int end){
	int t=data[start];//比較基準
	if(start<end){
		int i=start,j=end-1;
		while(i<j){
			while(i<j){
				//從後往前找比當前值小的數的位置;
				if(data[j]<t){
					break;
				}
				j--;
			}
			if(i<j){
				data[i]=data[j];
				i++;
			}
			while(i<j){
				//從前往後找比當前值大的數的位置
				if(data[i]>t){
					break;
				}
				i++;
			}
			if(i<j){
				data[j]=data[i];
				j--;
			}
		}
		data[i]=t;//將基準放入當前位置
		QuickSort(data,start,i);
		QuickSort(data,i+1,end);
	}
} 

在快速排序中如果每次選擇的中樞值可以將待排子表劃分為長度近似相等的子表是,排序的速度是最快的,當元素本身有序時

則速度是最慢的,快速排序的遞迴樹最高為n,最小為nlog2n;

一道例題:求下列資料經理一次快排後的序列(排序序列由小到大);

下標 0 1 2 3 4 5 6 7
data 49 38 65 97 76 13 27 49

起始基準為49,i=0,j=7,從後向前找第一個比49小的數;當j=6時此時滿足要求,data[0]=data[6]=27,i=1,j=6;

在從前往後找第一個比49大的數,當i=2時滿足要求,data[6]=data[2]=65,i=2,j=5;

重複以上步驟直至i與j相等為止;則最終一次快排序列為

下標 0 1 2 3 4 5 6 7
data 27 38 13 49 76 97 65 49

時間複雜度:最優時間複雜度O(nlog2n),最壞時間複雜度O(n^2);                                     排序的穩定性:不穩定;

堆積排序法(堆排序):

大根堆(根結點的關鍵字的值不小於其孩子結點的值)

小根堆(根結點的關鍵字的值不大於其孩子結點的值)

堆的結構形式為完全二叉樹,可採用一維陣列的方式進行存取,父節點對應陣列下標為i,其左右孩子結點如果有分別

對應陣列下標2i+1和2i+2;(最後一個非葉子結點為(n/2)-1,n為結點的個數)

堆排序就是交換根結點與最後一個結點,並重新調整堆的過程,對於大根堆全部序列"刪除"完後,其陣列有小到大

對於小根堆其全部序列“刪除”完後,其陣列由大到小;

void Swap(int *a,int *b){
	int t=*a;
	*a=*b;
	*b=t;//交換元素的值
}
void AdjustHeap(int data[],int loc,int n){
	//調整為大根堆
	if(loc<=(n/2)-1){
		int maxloc=2*loc+1;//非葉子結點一定有左孩子
		if(2*loc+2<n&&data[maxloc]<data[2*loc+2]){
			maxloc=2*loc+2;//有右孩子且值大於左孩子
		}//儲存loc孩子結點的最大值
		if(data[loc]<data[maxloc]){
			Swap(data+loc,data+maxloc);//交換資料
			AdjustHeap(data,maxloc,n);//重新調整堆
		}
	}
}
void CreateHeap(int data[],int n){
	for(int i=n/2-1;i>=0;i--){
		AdjustHeap(data,i,n);
		//葉子結點一定都滿足堆定義,從第一個非葉子
		//結點開始調整堆,第一個非葉子結點為(n/2)-1
	}
}
void HeapSort(int data[],int n){
	for(int i=n;i>0;i--){
		Swap(data,data+i-1);
		AdjustHeap(data,0,i-1);
	}
}

一道例題:以上程式碼是按照大根堆調整得出的,小根堆調整隻需要修改比較引數即可;

下標 0 1 2 3 4 5
data 7 3 5 9 1 12

拿到此類題目首先計算結點個數,找出最大的非葉子結點開始調整;最大非葉子結點下標為(n/2)-1

6個元素非葉子結點為2號,其孩子結點只有左孩子且滿足小根堆的定義無需調整;

在調整1號結點,其值為3,有一個比其小的右孩子(data[4]=1),交換得到一個小根堆滿足要求;

最後調整0號結點,其值為7,有一個比其小的左孩子(data[1]=1),交換後其左子樹部分不滿足小根堆定義,

此時一號結點為7,有一個比其小的右孩子(data[4]=3),交換後滿足要求;

最後得到的建堆序列為:

下標 0 1 2 3 4 5
data 1 3 5 9 7 12

建堆圖示: 

時間複雜度:O(nlog2n);              穩定性:不穩定;

希爾排序(縮小增量排序):

定義:每一次取不同的增量,在每一組增量內部採用直接插入排序,每一次排序完成增量內部元素有序,在排序的最後一次

所取得增量必為1。(一般情況下理】第一次增量區間d選取為n/2(表長的一半),之後依次為上一次的一半)

}
void ShellSort(int data[],int n){
	//預設增量為序列長度的1/2
	for(int i=n/2;i>=1;i=i/2){
		for(int j=i;j<n;j++){
			int temp=data[j];
			for(int k=j;k>=i&&temp<data[k-i];k=k-i){
				data[k]=data[k-i];
			}
			data[k]=temp;
			//k為確定的插入位置
		}
	}
}

歸併排序:(分治法)

演算法思想: 歸併排序主要分為兩步:分數列(divide),每次把數列一分為二,然後分到只有兩個元素的小數列;合數列

(Merge),合併兩個已經內部有序的子序列,直至所有數字有序。用遞迴可以實現。

時間複雜度:O(nlog2n);              穩定性:穩定;

基數排序(桶排序):(非重點)

基數排序,第一步根據數字的個位分配到每個桶裡,在桶內部排序,然後將數字再輸出(串起來);然後根據十位分桶,繼續排

序,再串起來。直至所有位被比較完,所有數字已經有序。

時間複雜度:O(d(n+r));              穩定性:穩定;

排序的每一次過程中是否可以確定一個待排元素的位置:

排序名稱 位置是否確定 原因
氣泡排序 每一次排序最大(最小)的元素總位於相應的位置
直接插入排序 不一定 無序序列中仍可能有元素改變有序序列元素的位置
折半插入排序 不一定 無序序列中仍可能有元素改變有序序列元素的位置
希爾排序 不一定 元素在最後一次排序中達到穩定
快速排序 確定為基準的元素位於排序後的位置
堆排序 大(小)根堆根部元素與最後一個元素交換位置
簡單選擇排序 每一次選擇的是序列中的最大(小)值,位置確定

各個排序演算法的時間/空間複雜度以及穩定性分析:

總結:對於時間複雜度小的,其排序大多情況下是不穩定的;