1. 程式人生 > >排序演算法分析歸納總結

排序演算法分析歸納總結

 排序方法分類:

按照策略 劃分內部排序方法為五大類:

插入排序選擇交換歸併分配排序。

下面我了歸納上述型別的排序演算法和其他經典演算法。

以下預設升序!!

插入排序:

直接插入排序:

排序思想:

將所有資料放入陣列R[1 ... n]中,初始狀態R[1]是有序區,無序區為R[2 .. n],從R[1... n]經過比較依次插入R[1]有序區中,得到一個有序序列。

那麼按照定義來寫:

void InsertSort(int a[],int n)
{
	int i,j,k;
	for(i = 1;i< n;i++)  //初始的有序區為a[0],所以待插入數從a[1]開始。
	{
		for(j = i-1;j >= 0;j--)       //在有序區中找到a[1]插入的位置
		{
			if(a[i]>a[j])
				break;
		}
		if(j!= i-1)                    //如果找到的位置是原位,則說明待插入數也將加入有序區。
		{
			int temp = a[i]; 
			for(k = i;k>j;k--)   //標記待插入的數的值,然後從此一直到要插入的位置進行移位
			{
				a[k] = a[k-1];
			}
			a[k+1] = temp;             //移位完成後插入資料。
		}
	}
}

程式碼優化:

首先,我們可以將if(a[j]< a[i])這條語句加入到第二層for迴圈的判斷中,考慮到如果,要插入的資料比有序區的最後一位要小,是不用執行移位、插入操作的,所以得到優化過後的程式碼:

void InsertSort(int a[],int n)
{
	int i,j,k;
	for(i = 1;i< n;i++)
	{
		if(a[i] <a[i-1])
		{
		int temp = a[i];
		for(j = i-1;j >= 0 && a[j] > a[i];j--)
		{
			a[j+1] = a[j];
		}
		a[j+1] = temp;
		}
	}
}

演算法分析:

 對於具有n個數據,要進行n-1趟排序。

  各種狀態下的時間複雜度:


│ 初始狀態        正序        反序      無序(平均)  


│ 第i趟的關鍵       1          i+1      (i-2)/2  
比較次數      


│總比較次數        n-1      (n+2)(n-1)/2     ≈n2/4    


│第i趟記錄移動次數    0        i+2         (i-2)/2  
 
│總的記錄移動次數     0      (n-1)(n+4)/2     ≈n2/4     


│時間複雜度        0(n)      O(n2)       O(n2)    


注意:
     按遞增有序,簡稱"正序"。
      按遞減有序,簡稱"反序"。 

演算法的空間複雜度分析:
演算法所需的輔助空間是一個監視哨,輔助空間複雜度S(n)=O(1)。是一個就地排序。

直接插入排序的穩定性:
直接插入排序是穩定的排序方法

希爾排序:

排序思想:取一個小於n 的正整數d1,然將資料分為d1組,所有相距d1距離的資料元素為同一組元素,在同一個組內進行直接插入排序,然後取第二個增量d2(小於d1)重複此操作,直至所取的增量為1,即:所有的元素放在同一組 內進行直接插入排序。

該方法的實質:分組插入排序。要知道 的是:希爾排序只會在最後一趟排序後生成有序 序列,在此之前 ,希爾排序並不會生成 有序區 ,但是每趟排序之後,都會逼近 有序序列。

code:

嚴格 按照定義:

void ShellSort(int a[],int n)
{
	int temp;
	int i,j;
	int gap = n/2;
	while(gap > 0)
	{
		for(i = gap;i<n;i++)
		{
			temp = a[i];
			for(j = i- gap;j>=0&&temp > a[j];)
			{
				a[j+ gap] = a[j];
				j = j- gap;
			}
			a[j+gap] = temp;
		}
		gap = gap/2;
	}
}

以n=10的一個數組49, 38, 65, 97, 26, 13, 27, 49, 55, 4為例:

第一次:gap = n/2 = 5;

49 38 65 97 26 13 27 49 55 4

1     2  3   4   5   1   2   3   4  5 

在這裡相同數字標記的為同一組元素,那麼久將這10個數據分為 {49, 13},{38,27},{65,49},{97,55},{26,4}這五組資料,分別在組內進行直接插入排序,   得到排序結果{13,49},{27,38},{49,65},{55,97},{4,26}.還原到原來的同一組是資料為:

13,27,49,55,4,49,38,65,97,26

 第二次:gap = gap/2 = 2;

13,27,49,55,4,49,38,65,97,26

 1    2   1  2   1  2  1  2    1   2

分為兩組:{13,49,4,38,97,} 和{27,55,49,65,26}這兩組陣列,在組內分別執行直接插入排序得到:{4,13,38,49,97},{26,27,49,55,65},還原到同一組:

4,26,13,27,38,49,49,55,97,65,

第三次排序:gap = gap/2 = 1

4,26,13,27,38,49,49,55,97,65,

1 1    1   1   1   1    1   1   1  1 

那麼這就意味一所有資料處於同一組資料中,直接一趟直接插入排序得到最終結果:

得到最終資料:

4 13 26 27 38 49 49 55 65 97

演算法分析:

希爾排序的時間複雜度是所取增量的序列的函式,而增量的選取是無法確定的,所以造成希爾排序的時間複雜度是難以確定的,但是一般認為希爾排序的平均時間複雜度為O(n的1.3次方);

希爾排序與直接插入排序的比較:
希爾排序的時間效能優於直接插入排序,原因:

1.資料基本有序的時候直接插入排序所需要的比較和移動次數較少

2.當n值較少時,n的平方與n 的差距也較小,所有直接插入排序演算法的最好時間複雜度為O(n),最壞時間複雜度為O(n 的平方)

3.希爾排序當初始時增量大,分組較多,導致直接插入排序較快,而當增量減小時,分組較少,但是每一句都趨近與有序序列,導致直接插入  排序較快,

因此希爾排序在直接插入排序中有很大的改進,

希爾排序是不穩定的。

交換排序:

氣泡排序:

排序思想 :

總體來說:對於一組資料,先遍歷一次找到最小的,放在R[0]處,然後第二次遍歷找到第二小的放在R[1]處。依次類推。

細節來說:

給一組資料:,5 2 3 4 1,此時:標記 i = a[0]  = 5;

第一次排序:

5 比 2 小,則2 於 5 交換,i = a[0 = 2]得到:2 5 3 4 1 

第二次比較:

2 比3小。不交換位置

第三次比較:

2比四小,不交換位置;

第五次比較:

2 比1 小,交換位置:得到:1 5 3 4 2,

依次類推 最終得到結果:1 2 3 4  5.

即每一次排序都將一個值放在他應在的位置上去。

void BubbleSort(int a[].int n)
{
	int i,j;
	for(i = 0;i< n - 1;i++)
	{
		for(j = i+1;j<n;j++)
		{
			if(a[i] > a[j])
			{
				int temp = a[i];
				a[i] = a[j];
				a[j]  = temp;

			}
		}
	}
}

其他方式:

既然,氣泡排序演算法每次將最無序區中最小的元素 上浮到 最前面,稱為有序區的一部分,那麼每次 比較的都是有序區以後 的部分,相當於說,我們可以 從資料末尾到無序區開始範圍內 進行比較,(倒著來)

code:

void BubbleSort(int a[].int n)
{
	int i,j;
	for(i = 0;i < n - 1;i++)
	{
		for(j = n-1;j > i;j--)
		{
			if(a[i] > a[j])
			{
				int temp = a[i];
				a[i] = a[j];
				a[j] = temp;

			}
		}
	}
}

程式碼優化:

這樣倒著來的時候,我們可以將演算法進一步的優化。這是因為此時每趟排序中 都 會有 位置的交換操作發生,如果沒有發生,則說明無序區的所有位置都相對是有序的,即:無序區是有序的,那麼可以說明整個資料是有序的(因為有序區的最後的一個數是小於無序區最小元素的值的),那麼我們可以設定一個變數來檢測此行為:

void BubbleSort(int a[].int n)
{
	int i,j;
	for(i = 0;i < n - 1;i++)
	{
		bool  isOver = true;
		for(j = n-1;j > i;j--)
		{
			if(a[i] > a[j])
			{
				int temp = a[i];
				a[i] = a[j];
				a[j] = temp;
				isOver  =  false;
			}
		}
		if(isOver == true)
		{
			return ;
		}
	}
}

分析:

若檔案的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C 和記錄移動次數M均達到最小值:所以,氣泡排序最好的時間複雜度    。
  若初始檔案是反序的,需要進行(n-1) 趟排序。每趟排序要進行  (n-i)  次關鍵字的比較(1≤i≤n-1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:
氣泡排序的最壞時間複雜度為綜上,因此氣泡排序總的平均時間複雜度為   穩定性: 氣泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變,所以氣泡排序是一種穩定排序演算法。

快速排序:

排序思想: 快速排序的實質思想是分治法。 1.找出一個元素作為基準點(一般都是以第一個元素為基準點) 2.分割槽中 將比基準點小的元素轉到左邊,比基準點大的數調整到右邊,而基準點調整到排序完成後的正確位置, 3.遞迴執行2操作,直到將其他n-1個元素調整到它應該在的正確位置上去,最終完成快速排序行程有序序列。 (快速排序的重點是調整基準點的位置和操作分割槽。) 為了能讓快速排序的過程看起來簡單易懂,我在這裡舉一個例子 現在有5顆樹,由於先天原因,樹長的高度參差不齊,本寶寶於是看不下去了,要把樹挖了重新栽樹,讓它們從低到高排列。 演算法模擬實現: 給出樹的高度:22 4 96  30 13,米。這7顆樹給下標0,1,2,3,4,基準點為 第一顆樹: 初始時i指向第一棵樹,j指向最後一棵樹,X記錄第一棵樹的高度並將第一棵樹挖出來:

   0    

   1  

     2   

     3   

     4   

   22

   4 

    96

    30

    13

此時: i= 0;j= 4 ; X = a[i] = 22; 首先從j開始從後到前找到一個比基準點X低的樹,即:當j= 4時符合要求,那麼將第5棵樹挖出來栽在第一個坑裡:

   0    

   1  

     2   

     3   

     4   

   13

   4 

    96

    30

    13

此時:a[0] = a[4];  i++;  (i= 1) 這樣第一個坑種上了樹,但是最後一個位置是沒有樹的,那麼我們從i的位置,即第二棵樹開始找到比X高的樹:當i= 2時滿足,於是把第二顆樹挖出來,種到最後一個位置,

   0    

   1  

     2   

     3   

     4   

  13

   4 

    96

    30

    96

此時:a[4] =a[2] ;j--;(j = 3)現在第三個位置是空的,那麼從j往前找比22低的樹,發現找到臨界點i== j,則將基準點歸位於a[2] = 22;

   0    

   1  

     2   

     3   

     4   

  13

   4 

    22

    30

    96

得到資料: 13 4 22 30 96 ,會發現基準點22 的位置就是排序終結的正確位置,下一步操作是對a[0...1] 與 a[3...4 ]這兩個子區間重複此操作,如果還沒有得到最後的排序結果, 遞迴此操作。 總結: 1. i = l,j = r,基準數為首元素,記錄X首元素, 2.j --從後往前找比X小的數,賦值給a[i]; 3.i ++從前往後找比X大的數,賦值給a[j], 重複2,3步,直到 i== j ;讓a[i] = X.即:基準數歸位。 最後遞迴完成排序操作。 說了這麼久了: code:
void QuickSort(int a[], int l, int r)
{
	if (l < r){
		int i = l;
		int j = r;
		int X = a[l];
		while (i < j)
		{
			while (i < j && a[j] >= X)
				j--;
			if (i < j)
			{
				a[i++] = a[j];
			}
			while (i < j && a[i] < X)
			{
				i++;
			}
			if (i < j)
			{
				a[j--] = a[i];
			}
		}
		a[i] = X;
		QuickSort(a, l, i - 1);
		QuickSort(a, i + 1, r);
	}
}
有的書上介紹的時候採取的是中間點為基準點:只需要在程式碼中加入一個數據轉換操作即可:

void QuickSort(int a[], int l, int r)
{
	if (l < r){
		int i = l;
		int j = r;
		//Swap(a[l], a[(l + r) / 2]); //首元素和中間元素調換位置
		int X = a[l];
		while (i < j)
		{
			while (i < j && a[j] >= X)
				j--;
			if (i < j)
			{
				a[i++] = a[j];
			}
			while (i < j && a[i] < X)
			{
				i++;
			}
			if (i < j)
			{
				a[j--] = a[i];
			}
		}
		a[i] = X;
		QuickSort(a, l, i - 1);
		QuickSort(a, i + 1, r);
	}
}

演算法分析:
快速排序-時空複雜度:
快速排序每次將待排序陣列分為兩個部分,在理想狀況下,每一次都將待排序陣列劃分成等長兩個部分,則需要logn次劃分。
而在最壞情況下,即陣列已經有序或大致有序的情況下,每次劃分只能減少一個元素,快速排序將不幸退化為氣泡排序,所以快速排序時間複雜度下界為O(nlogn),最壞情況為O(n^2)。在實際應用中,快速排序的平均時間複雜度為O(nlogn)。
快速排序在對序列的操作過程中只需花費常數級的空間。空間複雜度S(1)。
但需要注意遞迴棧上需要花費最少logn最多n的空間。


選擇排序:

直接選擇排序:

排序思想: n個數據經過n-1次排序成為有序序列, 第一趟排序:資料分為有序區(開始時為空),和無序區R[0……n-1];從無序區中找到最小的資料元素與無序區的第一位交換,構成有序區R[0...0];無序區:R[1...n-1]; 第i趟排序:有序區:R[0...i-1],無序區R[i。。n-1],那麼該趟排序從無序中找到最小的元素與無序區的第一位交換位置,並將無序區的第一個元素劃分到有序區。 這樣,n個數據通過n-1趟排序得到有序序列。
void SelectSort(int a[], int n)
{
	int i, j, k;
	for (i = 0; i < n -1;i++)
	{
		k = i;
		for (j = i + 1; j < n; j++)
		{
			if (a[j] < a[k])
			{
				k = j;
			}
		}
		int temp = a[i];
		a[i] = a[k];
		a[k] = temp;
	}
}
演算法分析: 在直接選擇排序中,共需要進行n-1次選擇和交換,每次選擇需要進行 n-i 次比較 (1<=i<=n-1),而每次交換最多需要3次移動,因此,總的比較次數C=(n*n - n)/2,
總的移動次數 3(n-1).由此可知,直接選擇排序的時間複雜度為 O(n2) (n的平方),所以當記錄佔用位元組數較多時,通常比直接插入排序的執行速度快些。
由於在直接選擇排序中存在著不相鄰元素之間的互換,因此,直接選擇排序是一種不穩定的排序方法。

堆排序:

堆有好幾種堆形式,但是由於二叉堆在運用時很常見,一般將二叉堆簡稱為堆。 堆排序定義:

n個關鍵字序列Kl,K2,…,Kn稱為堆,當且僅當該序列滿足如下性質(簡稱為堆性質):  (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1。

堆的性質:

由定義可以得出,堆的父節點總是大於等於或者小於等於它的子節點;每個節點的左兒子和有兒子又都是一個二叉堆;

二叉堆分為兩種:

1.最大堆:父節點的值大於等於子任何一個子節點的值時的二叉堆

2.最小堆:父節點的值小於等於任何一個子節點的值時的二叉堆。

最小堆和最小堆的示例:


下面以最大堆為例子:

最大堆對應 的就是挑選 最大元素,將陣列看為一顆完全二叉樹,那麼此時我們 可以利用完全二叉樹的父親與孩子節點的關係來選擇最大元素。

首先我們 把資料放在R[1...n]中(為了與二叉樹的順序儲存結構相一致,堆排序的開始下邊標從1開始),把每個資料看做一個我節點,那麼首元素R[1]則為完全二叉樹的根,以下元素依次每層從左到右排列在陣列 中,R[i]的左孩子是R[2i],右節點R[2i+1];

堆排序的首要任務是 建堆,假設現在完全二叉樹中的某個節點i,(左子樹2i,右子樹2i +1),我們需要將它的左子樹和右子樹和此i節點比較選取最大者當做這三個數的父節點,當然,這樣比較後交換位置後可能會造成下一級的堆被破壞,所以我們需要接著按照上述方法進行下一級的建堆,直到完全二叉樹中的節點i構成堆為止。對於任意一顆完全二叉樹,i取[n/2]~1,反覆利用此方法建堆。大數上調,小數下降,

調整堆的方法:

void Sift(int a[], int low, int high)
{
	int i = low;              
	int temp = a[i];
	int j = 2 * i;
	while (j <= high)
	{
		if (j < high &&a[j] < a[j + 1])   //判斷兩個孩子節點哪個大(要注意此時判斷的j是不能加上=號的。因為j代表左孩子節點,理論上還有j+1)
		{
			j++;
		}
		if (a[j] > temp)                  //如果最大的孩子節點比父節點大的話,
		{
			a[i] = a[j];                  //<span style="font-family: Arial, Helvetica, sans-serif;">讓其成為父節點</span>
			i = j;                         //接著該節點往下進行建堆
			j = 2 * i;
		}
		else
			break;
	}
	a[i] = temp;
}

在初始化堆構造後,最大的數一定位於根節點,將其放在序列的最後,也就是第一個數和最後一個數進行交換,由於最大的元素已經歸位,所以待排序的資料中就減少了一位。但由於根節點的改變,這n-1個節點不一定還是堆,所以要再次初始化建堆,初始化建堆之後其根節點為次大的資料,將它放在序列的倒數第二位。如此反覆的進行操作,知道完全二叉樹只剩下一個根為止。

實現堆排序的演算法如下:

void HeapSort(int a[], int n)
{
	int i;
	for (i = n / 2; i >= 1; i--)
	{
		Sift(a, i, n);
	}
	for (i = n; i >= 2; i--)
	{
		int temp = a[1];
		a[1] = a[i];
		a[i] = temp;
		Sift(a, 1, i - 1);
	}
}

演算法模擬:

資料: 72 73 71 23 94 16 5 68

對應的完全二叉樹1.:


模擬堆排:

初始時 i= n/2 = 4;j = i*2;那麼68比13小,交換位置,j > n結束。


 i 執行i--;i = 3;j = i*2 = 6,那麼16 和5都比71小,直到j > n都未發生交換,結束,


4.i 執行 i--;i = 2;j = i*2 =4;那麼 94比68和73大,執行73與94的交換,直到j>n;結束。

 

執行i--;i = 1;j = i*2 = 2;那麼94比71 和72 大,成為父節點,直到j >n結束:


那麼該建堆就結束了,可以看到最大的值是在根節點,我們將94與13交換,然後截斷94的連線點,那麼94就相當於已經歸位,我們需要做的是將切斷後的資料重新建堆,

找出次大的數。一直重複此操作,就可以得到有序序列。

完整程式碼:

void Sift(int a[], int low, int high)
{
	int i = low;
	int temp = a[i];
	int j = 2 * i;
	while (j <= high)
	{
		if (j < high &&a[j] < a[j + 1])
		{
			j++;
		}
		if (a[j] > temp)
		{
			a[i] = a[j];
			i = j;
			j = 2 * i;
		}
		else
			break;
	}
	a[i] = temp;
}
void HeapSort(int a[], int n)     //使用時呼叫此函式即可。
{
	int i;
	for (i = n / 2; i >= 1; i--)
	{
		Sift(a, i, n);
	}
	for (i = n; i >= 2; i--)
	{
		int temp = a[1];
		a[1] = a[i];
		a[i] = temp;
		Sift(a, 1, i - 1);
	}
}

演算法分析:

直接選擇 排序中,為了從R[1..n]中選出最大的資料,必須進行n-1次比較,然後在R[2..n]中選出最大的資料需要執行n-2次比較,事實上,這些比較中有很多都是在重複比較。有很多比較可能已已經在n-1次比較中進行了,但由於未保留比較後的結果,所以執行了重複性的比較工作,而堆排序會對比較過後的結果進行保留,(根據上面的圖例就可以看出來),減少了一些重複的比較。

時間複雜度:

堆排序的執行時間打過消耗在初始化建堆和重建堆的反覆篩選中。二叉樹從最底層右邊的非終結點開始建堆,將其與其孩子進行比較和可能的交換,對於每個非終結點點來談,最多進行量次比較和交換資料操作。在排序時(此時已經初始化建堆完成了),第i次去最大值記錄重建堆需要O(logi)時間,並且需要取n-1次堆頂記錄,所以重建堆的時間複雜度為O(nlogn)。

總體說:堆排序的時間複雜度為O(nlogn);

歸併排序:

歸併排序是建立在歸併操作上的一種排序演算法,該演算法採用了分治法,是分治法的一種典型的例子。

排序思想:

將已經有序的子序列進行合併,得到完全有序的序列。即對於一組資料,我們先分為若干個子序列,然後讓子序列有序,有序後合併子序列形成新的子序列,我們再對新的子序列進行操作使其有序。那麼當所有的子序列經過有序合併後最終會形成一組有序序列。

那麼什麼是歸併操作?

歸併操作也叫歸併演算法,指的是將兩個順序序列合併成一個有序序列的方法。

首先我們先說如何實現歸併操作:

1.我們先申請一個空間(可以容納兩個已經有序序列總和),用來存在最終結果。

2.然後設定連個標記分別標記兩個有序序列的下標,通過這兩個標記來比較較小的資料元素放入已經申請好的空間內,並移動下標

3.重複操作操作某一個下標超出序列,然後將另一個序列的剩下元素直接複製到最終空間內。

為了傳入引數的簡化,我們將兩個有序的序列存入一個數組內相鄰位置。

code:

void Marge(int a[], int low, int mid, int high)
{
	int i = low, j = mid + 1, k = 0;
	int *r;
	r = (int *)malloc(sizeof(high - low - 1) *sizeof(int));  //申請空間
	while (i <= mid && j <= high)  //如果第一個陣列或者第二個陣列沒有達到尾,繼續比較
	{
		if (a[i] <= a[j])
		{
			r[k++] = a[i++];
		}
		else
		{
			r[k++] = a[j++];
		}
	}
	while (i <= mid)  //如果是i標記的數資料元素沒有複製完
	{
		r[k++] = a[i++];
	}
	while (j <= high)  //如果是j標記的資料元素沒有複製完
	{
		r[k++] = a[j++];
	}
	for (k = 0, i = low; i <= high; k++, i++)  //將臨時空間裡的資料傳給a陣列
	{
		a[i] = r[k];
	}
	free(r);
}
那麼經過這樣的歸併操作,得到的最終資料將是有序資料。

那麼問題來了:如果我們要對一組無序的資料進行歸併排序怎麼弄呢?

這裡要要用到分治的思想。

分治法:通俗的說,分治法就是將一塊領土分解,分解為若干個小塊部分,然後將一塊塊領土佔領,被分解的可以是不同的政治派別或者是其他什麼的,然後讓他們彼此異化。

分治法精髓:

分- 將問題分解為規模更小的子問題。

治- 將這些規模更小的子問題逐個解決。

合- 將已經解決的問題進行合併,最終得出母問題的解。

針對歸併排序,我們先來一組資料模擬一下你就能用理解如果用分治法實現的歸併排序了。

針對這一問題,我們想將 18 2 20 34 12 32 6 16 通過歸併排序使其成為一個有序陣列:

初始: (18 2) ( 20 34) (12 32)  (6 14)

第一趟歸併:(2 18  20 14) (12 32 6 16)

第2趟歸併:(2 18 20 34 6 12 16 32)

第三趟歸併: 2 6 12 16 18 20 32 34(得到有序序列)

Marge實現了一次歸併,那麼現在我們 需要利用Marge解決一趟歸併,在某趟歸併中,假設各子表的長度為length,(最後一個字表的長度可能 小於length,n/length不一定不剩餘),則歸併前a[0...n-1]中共有[n/length]個子表,呼叫Marge函式將相鄰一對子表 進行歸併時必須要判斷子表的個數的奇偶,以及最後一個字表的長度小於length這兩種情況,如果子表為奇數,則最後一個字表此次歸併輪空,如果是偶數則注意到最後一個子表的上界是 n-1。

code:

void MergePass(int a[], int length, int n)
{
	int i;
	for (i = 0; i + length * 2 - 1 < n; i = i + 2 * length)
	{
		Marge(a, i, i + length - 1, i + 2 * length - 1);
	}
	if (i + length - 1 < n)
	{
		Marge(a, i,i + length - 1, n - 1);
	}
}

在進行二路歸併排序的第一趟 ,將待排序的表看作是n個 長度為1的有序序列 ,將這些表兩兩歸併,如果n為偶數,得到n/2個長度為2的子序列,如果為奇數,則最後一個 子表輪空,本趟結束後,最後一個字表的長度 為 1。第二趟排序,將第一趟排序所得的[n/2]個有序的子表兩兩歸併,反覆操作,直到得到一個長度為n的有序表。

對應的二路歸併演算法:

void MergeSort(int a[], int n)
{
	int length;
	for (length = 1; length < n; length = 2 * length)
	{
		MergePass(a, length, n);
	}
}

完整演算法:
void Marge(int a[], int low, int mid, int high)
{
	int i = low, j = mid + 1, k = 0;
	int *r;
	r = (int *)malloc((high - low + 1) *sizeof(int)); 
	while (i <= mid && j <= high)  
	{
		if (a[i] <= a[j])
		{
			r[k++] = a[i++];
		}
		else
		{
			r[k++] = a[j++];
		}
	}
	while (i <= mid)  
	{
		r[k++] = a[i++];
	}
	while (j <= high)  
	{
		r[k++] = a[j++];
	}
	for (k = 0, i = low; i <= high; k++, i++)  
	{
		a[i] = r[k];
	}
	free(r);
}
void MergePass(int a[], int length, int n)
{
	int i;
	for (i = 0; i + length * 2 - 1 < n; i = i + 2 * length)
	{
		Marge(a, i, i + length - 1, i + 2 * length - 1);
	}
	if (i + length - 1 < n)
	{
		Marge(a, i,i + length - 1, n - 1);
	}
}
void MergeSort(int a[], int n)
{
	int length;
	for (length = 1; length < n; length = 2 * length)
	{
		MergePass(a, length, n);
	}
}

未完,明天接著寫。

相關推薦

排序演算法分析歸納總結

 排序方法分類: 按照策略 劃分內部排序方法為五大類: 插入排序、選擇、交換、歸併和分配排序。 下面我了歸納上述型別的排序演算法和其他經典演算法。 以下預設升序!! 插入排序: 直接插入排序:

演算法分析總結排序演算法效能及比較總結

一、按平均時間將排序分為四類: (1)平方階(O(n2))排序      一般稱為簡單排序,例如直接插入、直接選擇和氣泡排序; (2)線性對數階(O(nlgn))排序      如快速、堆和歸併排序; (3)O(n1+£)階排序      £是介於0和1之間的常數,即0

十大排序演算法的實現 十大經典排序演算法最強總結(含JAVA程式碼實現)

十大經典排序演算法最強總結(含JAVA程式碼實現)   最近幾天在研究排序演算法,看了很多部落格,發現網上有的文章中對排序演算法解釋的並不是很透徹,而且有很多程式碼都是錯誤的,例如有的文章中在“桶排序”演算法中對每個桶進行排序直接使用了Collection.sort

排序演算法分析 --- 快速排序

一 演算法描述 假設有n個元素,現在要把這些元素按照從小到大的順序進行排序,那麼演算法步驟如下, 選擇中間位置的元素作為基準值(pivot value),其索引為( 0 + (n-1) ) / 2 從第0個元素開始從左往右找到比基準值大的元素,其索引為 i 從

七大排序演算法的個人總結(三)

堆排序(Heap): 要講堆排序之前先要來複習一下完全二叉樹的知識。 定義: 對一棵具有n個結點的二叉樹按層序編號,如果編號為i(0 <= i <= n)的結點與同樣深度的滿二叉樹編號為i的結點在二叉樹中位置完全相同,則這棵二叉樹稱為完全二叉樹。   如上面

七大排序演算法的個人總結(二)

歸併排序(Merge Sort):   歸併排序是一個相當“穩定”的演算法對於其它排序演算法,比如希爾排序,快速排序和堆排序而言,這些演算法有所謂的最好與最壞情況。而歸併排序的時間複雜度是固定的,它是怎麼做到的? 兩個有序陣列的合併: 首先來看歸併排序要解決的第一個問題:兩

七大排序演算法的個人總結(一)

氣泡排序(Bubble Sort): 很多人聽到排序第一個想到的應該就是氣泡排序了。也確實,氣泡排序的想法非常的簡單:大的東西沉底,汽泡上升。基於這種思想,我們可以獲得第一個版本的冒泡: public static void sort1(int[] array) { for

排序演算法——比較與總結

排序演算法1——圖解氣泡排序及其實現(三種方法,基於模板及函式指標) 排序演算法2——圖解簡單選擇排序及其實現 排序演算法3——圖解直接插入排序以及折半(二分)插入排序及其實現 排序演算法4——圖解希爾排序及其實現 排序演算法5——圖解堆排序及其實現 排序演算法6——圖解歸併排序及其遞迴與非

關於冒泡、快排、二分排序演算法分析

前面的話: 把自己總結的排序方法分享一下,也當作自己的筆記本了 冒泡: 氣泡排序演算法的主要思想是每次只比較相鄰的兩個元素,一輪排序之後,最大的元素就會沉到最下面(全文預設升序),然後每次迴圈比較,就可以得到一個已排序好的陣列。 程式碼實現: public static void

快速排序演算法分析

快速排序:它的基本思想是:找出一個元素(理論上可以在所有值中隨便找一個)作為基準,通過一趟排序將要排序的資料分割成獨立的兩部分,基準左邊的資料都小於基準值,右邊的部分都大於基準值,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有

Java排序演算法分析與實現:快排、氣泡排序、選擇排序、插入排序、歸併排序(一)

轉載  https://www.cnblogs.com/bjh1117/p/8335628.html   一、概述:   本文給出常見的幾種排序演算法的原理以及java實現,包括常見的簡單排序和高階排序演算法,以及其他常用的演算法知識。   簡單排序:氣泡排序、選擇排序、

排名演算法(二)--淘寶搜尋排序演算法分析

原文:https://blog.csdn.net/u011966339/article/details/78052569  淘寶搜尋排序的目的是幫助使用者快速的找到需要的商品。從技術上來說,就是在使用者輸入關鍵詞匹配到的商品中,把最符合使用者需求的商品排到第一位,其它的依次排在後續相應

十大經典排序演算法最強總結(含JAVA程式碼實現 +演算法Gif動圖)

最近在複習排序演算法,對於演算法自己理解的總是不那麼透徹,所以在網路上搜索到有很多優秀的總結,借前輩們的經驗來灌輸一下自己,也不失為一件有效的學習方法,更有效的學習和記憶,適合自己的都是好方法。這裡總結了十大經典排序演算法,並且有Gif動圖,讓你學習起來一目瞭然,快來一起學

各種排序演算法分析與比較

1.直接插入排序 每一趟將一個待排序的元素作為關鍵字,按照其關鍵字的大小插入到已經排好的部分序列的適當位置上。平均時間複雜度為O(n2),空間複雜度為O(1)。 void InsertSort(int R[], int n) { if (R == nullptr ||

常見的三種排序演算法分析及對比實現(冒泡、選擇、插入)

1. 氣泡排序      1)基本思想:           在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,           讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就

三色旗(荷蘭旗)排序演算法分析

三色旗問題又叫荷蘭旗問題,前提是有一個無序的char陣列,裡面的元素只能是{‘R’,'G','B'}中的一個,比如{'B','R','G','R','B','B','G','B','R'},現在要求不允許藉助額外的空間,即只能通過自身元素交換的方法將陣列重排序,最終達到RG

排序演算法分析

據說在計算機發明後的很長一段時間內,30%以上的算力都是用來做排序這一件事情。 很久以來,自己由於習慣於依賴各種現成的庫,對於各種排序演算法生疏久已,甚至對於選擇、冒泡、插入等排序的區別都有些模糊不清了。我們慶幸於生在這個年代,安心享受著前輩大師們的智

C語言中冒泡法、選擇法、插入法三種常見排序演算法分析

一、冒泡法(起泡法)  演算法要求:用起泡法對10個整數按升序排序。     演算法分析:如果有n個數,則要進行n-1趟比較。在第1趟比較中要進行n-1次相鄰元素的兩兩比較,在第j趟比較中要進行n-j次兩兩比較。比較的順序從前往後,經過一趟比較後,將最值沉底(換到最後一個元

五中排序演算法效能比較總結

1 概述 本文對比較常用且比較高效的排序演算法進行了總結和解析,並貼出了比較精簡的實現程式碼,包括選擇排序、插入排序、歸併排序、希爾排序、快速排序等。演算法效能比較如下圖所示: 2 選擇排序 選擇排序的第一趟處理是從資料序列所有n個數據中選擇一個最小的資料作為有

經典排序演算法分析

從學第一門計算機語言開始就瞭解了選擇、氣泡排序等演算法,後來又學習了高效的歸併、快速排序,排序演算法種類很多,一直都想把它們總結一下,但卻信心不足。這幾天終於下定決心嘗試寫一寫,於是看了演算法導論,在網上也查了很多資料,最終將這些排序演算法一個一個用C語言實現,