經典排序(7+3)

定義:排序是將一組具有相同數據類型的數據元素調整爲按關鍵字從小到大(從大到小)排列的過程。

概念:關鍵字是數據元素或記錄中某個數據項的值,它可以標記一個數據元素。若關鍵字可以唯一標識一個數據元素,則稱此關鍵字爲主關鍵字;若其可以標識若干數據元素,則稱爲次關鍵字。

穩定排序和不穩定分排序:在排序過程中,若按次關鍵字排序,且具有相同關鍵字的數據元素之間的相對次序或位置不變,則稱這種排序方法爲穩定排序;反之,爲不穩定排序。(所有跨關鍵字比較的排序方法都是不穩定的)

增排序和減排序:若排序結果是按照關鍵字從小到大的順序排列,則稱其爲增排序;若排序結果是按照關鍵字按從大到小排序,則稱其爲減排序。

內部排序和外部排序:若在排序過程中,整個文件或數據表都是放在內存中處理,排序是不涉及數據的內外存交換,這種排序稱爲內部排序;若排序過程中需要進行數據的內外交換,則稱這種排序叫外部排序。

非線性時間比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此稱爲非線性時間比較類排序。

線性時間非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間運行,因此稱爲線性時間非比較類排序。

時間複雜度:對排序數據的總的操作次數。反映當n變化時,操作次數呈現什麼規律。

空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。


排序方法 時間複雜度(最好) 時間複雜度(最壞) 時間複雜度(平均) 空間複雜度 穩定性
插入排序 O(n) O(n^2) O(n^2) O(1) 穩定
希爾排序 O(n) O(n^2) O(n^1.3) O(1) 不穩定
選擇排序 O(n^2) O(n^2) O(n^2) O(1) 不穩定
堆排序 O(n*logn) O(n*logn) O(n*logn) O(1) 不穩定
冒泡排序 O(n) O(n^2) O(n^2) O(1) 穩定
快速排序 O(n*logn) O(n^2) O(n*logn) O(logn)~O(n) 不穩定
歸併排序 O(n*logn) O(n*logn) O(n*logn) O(n) 穩定
           
計數排序 O(n+k) O(n+k) O(n+k) O(n+k) 穩定
桶排序 O(n) O(n^2) O(n+k) O(n+k) 穩定
基數排序 O(n*k) O(n*k) O(n*k) O(n+k) 穩定
#define maxsize 20
typedef struct{
	KeyType key;//關鍵字 
	OtherType otherdata;
}RecordType;
typedef struct{
	RecordType r[maxsize+1];//r[0]爲工作單元或閒置,對1~n中的單元進行排序 
	int length;//順序表長度 
}SeqList; 

 


插入排序(Insertion sort):通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。

算法描述:

  1. 從第一個元素開始,該元素可以認爲已經被排序;
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描;
  3. 如果該元素(已排序)大於新元素,將該元素移到下一位置;
  4. 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
  5. 將新元素插入到該位置後;
  6. 重複步驟2~5。
void insertion(SeqList *L){
	int i,j;
	for(i=2;i<L->length;i++){
		L->r[0]=L->r[i];//設置監視哨:免去查找過程每一步都要檢測查找表查找是否完畢,提高查找效率 
		j=i-1;
		while(L->r[0].key<L->r[j].key){
			L->r[j+1]=L->r[j];
			j=j-1;
		}
		L->r[j+1]=L->r[0];
	}
} 

希爾排序(Shell sort):簡單插入排序的改進版。它與插入排序的不同之處在於,它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序。

算法描述:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法描述:

  1. 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列個數k,對序列進行k 趟排序;
  3. 每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。
void shellinsert(SeqList *L,int delta){
	//對順序表做一趟希爾插入排序,delta爲該趟排序增量
	int i,j,k;
	for(i=1;i<=delta;i++){
		for(j=i+delta;j<=L->length;j=j+delta){
			L->r[0].key=L->r[j].key;//備份L->r[j],不做監視哨
			k=j-delta;
			while(L->r[0].key<L->r[k].key && k>0){
				L->r[k+delta]=L->r[k];
				k=k-delta;
			} 
			L->r[k+delta]=L->r[0];
		}
	} 
} 
void shell(SeqList* &L,int di[],int n){
	//對順序表L按照增量序列di[0]--di[n-1]進行希爾排序,其中d[n-1]爲1;
	for(int i=0;i<=n-1;i++){
		shellinsert(L,di[i]);
	} 
}

選擇排序(Selection sort):首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

算法描述:n個記錄的直接選擇排序可經過n-1趟直接選擇排序得到有序結果。具體算法描述如下:

  1. 初始狀態:無序區爲R[1..n],有序區爲空;
  2. 第i趟排序(i=1,2,3…n-1)開始時,當前有序區和無序區分別爲R[1..i-1]和R(i..n)。該趟排序從當前無序區中-選出關鍵字最小的記錄 R[k],將它與無序區的第1個記錄R交換,使R[1..i]和R[i+1..n)分別變爲記錄個數增加1個的新有序區和記錄個數減少1個的新無序區;
  3. n-1趟結束,數組有序化了。
void selection(SeqList *L){
	int k;//選擇當前待排序列中關鍵字最小的下標爲k元素,與下標爲i元素互換,避免每次比較成功後就互換所帶來的開銷; 
	RecordType x;
	for(int i=1;i<L->length;i++){
		k=i;
		for(int j=i+1;j<=n;j++){
			if(L->r[j].key<L->r[k].key){
				k=j;
			}
		} 
		if(k!=j){
			x=L->r[i];
			L->r[i]=L->r[k];
			L->r[k]=x;
		}
	}
}

堆排序(Heap sort):利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。

算法描述:

  1. 將初始待排序關鍵字序列(R1,R2….Rn)構建成大頂堆,此堆爲初始的無序區;
  2. 將堆頂元素R[1]與最後一個元素R[n]交換,此時得到新的無序區(R1,R2,……Rn-1)和新的有序區(Rn),且滿足R[1,2…n-1]<=R[n];
  3. 由於交換後新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,……Rn-1)調整爲新堆,然後再次將R[1]與無序區最後一個元素交換,得到新的無序區(R1,R2….Rn-2)和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數爲n-1,則整個排序過程完成。

具體實現請點擊--


冒泡排序(Bubble sort):重複地走訪過要排序的數列,一次比較兩個元素,如果它們的順序錯誤就把它們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因爲越小的元素會經由交換慢慢“浮”到數列的頂端。

算法描述:

  1. 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
  2. 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
  3. 針對所有的元素重複以上的步驟,除了最後一個;
  4. 重複步驟1~3,直到排序完成。
void Bubble(SeqList *L){
	//每輪比較的結果是確定最後一個元素位置 
	int change=1;//使得順序表有序時,可以結束比較,提前退出
	RecordType x;
	for(int i=1;i<L->length && change;i++){
		change=0;
		for(int j=1;j<=L->length-i;j++){
			if(L->r[j].key>L->r[j+1].key){
				x=L->r[j];
				L->r[j]=L->r[j+1];
				L->r[j+1]=x;
				change=1;
			}
		}
	} 
}

快速排序(Quick sort):通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

算法描述:快速排序使用分治法來把一個串(list)分爲兩個子串(sub-lists)。具體算法描述如下:

  1. 從數列中挑出一個元素,稱爲 “基準”(pivot);
  2. 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作;
  3. 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
int partition(SeqList *H,int left,int right){
	//對順序表H中的H->r[left]到H->[right]部分進行快速排序一次劃分,並返回中間數
	RecordType x=H->r[left];
	int low,high;
	low=left;
	high=right;
	while(low<high){
		//首先從右到左掃描,查找第一個關鍵字小於x.key的元素 
		while(H->r[high].key>=x.key && low<high) high--;
		if(low<high){
			H->r[low]=H->r[high];
			low++;
		}
		//然後從右到左掃描,查找第一個關鍵字不小於x.key的元素 
		while(H->r[low].key<x.key && low<high) low++;
		if(low<high){
			H->r[high]=H->r[low];
			high--;
		}
	} 
	H->r[low]=x;
	return low;
} 
void quick(SeqList *L,int low,int high){
	//對順序表L用快速排序算法進行排序
	int mid;
	if(low<high){
		mid=partition(L,low,high);
		quick(L,low,mid-1);
		quick(L,mid+1,high);
	} 
}

歸併排序(Merge sort):採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。

算法描述:歸併排序算法有兩個基本的操作,一個是分,也就是把原數組劃分成兩個子數組的過程。另一個是治,它將兩個有序數組合併成一個更大的有序數組。它將數組平均分成兩部分,當數組分得足夠小時,即數組中只有一個元素時,只有一個元素的數組自然而然地就可以視爲是有序的,此時就可以進行合併操作了。因此,合併兩個有序的子數組,是從 只有一個元素 的兩個子數組開始合併的。

void mergex(RecordType R[],RecordType R1[],int low,int mid,int high){
	//數組R[]中R[low]~R[mid]和R[mid+1]~R[high] 分別按照關鍵字有序排列
	//將它們合併成一個有序序列,存放在數組R1的R1[low]~R1[high]中
	int i,j,k;
	i=low;
	j=mid+1;
	k=low;
	while(i<=mid && j<=high){
		if(R[i].key<=R[j].key){
			R1[k]=R[i];
			i++;
		}else{
			R1[k]=R[j];
			j++;
		}
		k++;
	} 
	while(i<=mid) R1[k++]=R[i++];
	while(j<=high) R1[k++]=R[j++];
}
void mergey(RecordType R[],RecordType R1[],int len){
	//對R進行一趟歸併,結果放在R1中
	int j,i=0;
	while(i+len*2<=n){//n爲待排元素個數 
		mergex(R,R1,i,i+len-1,i+len*2-1);
		i=i+len*2;
	} 
	if(i+len-1<n-1) mergex(R,R1,i,i+len-1,n-1);
	else
		for(j=i;j<n;j++) R1[j]=R[j];
}
void merge(SeqList L){
	int len,n;
	RecordType *R1;
	n=L.length;
	len=1;
	R1=(RecordType*)malloc(sizeof(RecordType)*n);
	while(len<=n/2+1){
		mergey(L.r,R1,len);
		len=len*2;
		mergey(R1,L.r,len);
		len=len*2;
	}
	free(R1);
}

計數排序(Counting sort):計數排序不是基於比較的排序算法,其核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。 作爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有確定範圍的整數。

算法描述:

  1. 找出待排序的數組中最大和最小的元素;
  2. 統計數組中每個值爲i的元素出現的次數,存入數組C的第i項;
  3. 對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加);
  4. 反向填充目標數組:將每個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1。

計數排序是一個穩定的排序算法。當輸入的元素是 n 個 0到 k 之間的整數時,時間複雜度是O(n+k),空間複雜度也是O(n+k),其排序速度快於任何比較排序算法。當k不是很大並且序列比較集中時,計數排序是一個很有效的排序算法。


桶排序(Bucket sort):桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的確定。桶排序的工作的原理:假設輸入數據服從均勻分佈,將數據分到有限數量的桶裏,每個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排)。

算法描述:

  1. 設置一個定量的數組當作空桶;
  2. 遍歷輸入數據,並且把數據一個一個放到對應的桶裏去;
  3. 對每個不是空的桶進行排序;
  4. 從不是空的桶裏把排好序的數據拼接起來。 

桶排序最好情況下使用線性時間O(n),桶排序的時間複雜度,取決與對各個桶之間數據進行排序的時間複雜度,因爲其它部分的時間複雜度都爲O(n)。很顯然,桶劃分的越小,各個桶之間的數據越少,排序所用的時間也會越少。但相應的空間消耗就會增大。


基數排序(Radix sort):基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。

算法描述:

  1. 取得數組中的最大數,並取得位數;
  2. arr爲原始數組,從最低位開始取每個位組成radix數組;
  3. 對radix進行計數排序(利用計數排序適用於小範圍數的特點);

基數排序基於分別排序,分別收集,所以是穩定的。但基數排序的性能比桶排序要略差,每一次關鍵字的桶分配都需要O(n)的時間複雜度,而且分配之後得到新的關鍵字序列又需要O(n)的時間複雜度。假如待排數據可以分爲d個關鍵字,則基數排序的時間複雜度將是O(d*2n) ,當然d要遠遠小於n,因此基本上還是線性級別的。

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