1. 程式人生 > >八種排序演算法原理及實現

八種排序演算法原理及實現

一:氣泡排序

氣泡排序(Bubble Sort),是一種電腦科學領域的較簡單的排序演算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。氣泡排序演算法的運作如下:(從後往前)
  1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
  3. 針對所有的元素重複以上的步驟,除了最後一個。
  4. 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
複雜度O(n^2)
void Bubblesort(int *a, int n)
{
	int i, j, k;
	for (i = 1;i < n;i++)
	{
		for (j = 1;j <= n - i;j++)
		{
			if (a[j] > a[j + 1])
				k = a[j], a[j] = a[j + 1], a[j + 1] = k;
		}
	}
}

二:選擇排序

選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。 

複雜度O(n^2)

void Selectionsort(int *a, int n)
{
	int i, j, k;
	for (i = 1;i <= n;i++)
	{
		for (j = i + 1;j <= n;j++)
		{
			if (a[i] > a[j])
				k = a[i], a[i] = a[j], a[j] = k;
		}
	}
}

三:插入排序—直接插入排序

直接插入排序(Straight Insertion Sort)是一種最簡單的排序方法,其基本操作是將一條記錄插入到已排好的有序表中,從而得到一個新的、記錄數量增1的有序表。

每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。第一趟比較前兩個數,然後把第二個數按大小插入到有序表中; 第二趟把第三個資料與前兩個數從後向前掃描,把第三個數按大小插入到有序表中;依次進行下去,進行了(n-1)趟掃描以後就完成了整個排序過程。直接插入排序是由兩層巢狀迴圈組成的。外層迴圈標識並決定待比較的數值。內層迴圈為待比較數值確定其最終位置。直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層迴圈是從第二個數值開始的。當前一數值比待比較數值大的情況下繼續迴圈比較,直到找到比待比較數值小的並將待比較數值置入其後一位置,結束該次迴圈。
void insertsort(int *a, int n)
{
	int i, j, k, id;
	for (i = 2;i <= n;i++)
	{
		for (j = 1;j < i;j++)
		{
			if (a[j] > a[i])
			{
				k=a[i],id = j;
				break;
			}
		}
		for (j = i;j > id;j--)
			a[j] = a[j - 1];
		a[id] = k;
	}
}

四:插入排序—希爾排序

希爾排序(Shell's Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。該方法因D.L.Shell於1959年提出而得名。希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,演算法便終止。希爾排序屬於插入類排序,是將整個有序序列分割成若干小的子序列分別進行插入排序。排序過程:先取一個正整數d1<n,把所有序號相隔d1的陣列元素放一組,組內進行直接插入排序;然後取d2<d1,重複上述分組和排序操作;直至di=1,即所有記錄放進一個組中排序為止。
void shellsort(int *a, int n)
{
	int i, j, k, id, gap;
	for (gap = n / 2;gap;gap /= 2)
	{
		for (i = gap;i <= n;i++)
		{
			for (j = i - gap;j >= 0 && a[j] > a[j + gap];j -= gap)
				k = a[j], a[j] = a[j + gap], a[j + gap] = k;
		}
	}
}

五:歸併排序

歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為二路歸併。歸併過程為:比較a[i]和b[j]的大小,若a[i]≤b[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素b[j]複製到r[k]中,並令j和k分別加上1,如此迴圈下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。歸併排序的演算法我們通常用遞迴實現,先把待排序區間[s,t]以中點二分,接著把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。

複雜度:O(nlogn)

void mergesort(int *a, int l, int r, int *b)
{
	int mid = (l + r) / 2;
	int i = l, j = mid + 1, n = mid, m = r, cnt = 0;
	if (l != r)
	{
		mergesort(a, l, mid, b);
		mergesort(a, mid + 1, r, b);
	}
	while (i <= n&&j <= m)
	{
		if (a[i] < a[j])
			b[++cnt] = a[i++];
		else
			b[++cnt] = a[j++];
	}
	while (i <= n) b[++cnt] = a[i++];
	while (j <= m) b[++cnt] = a[j++];
	for (i = 1;i <= cnt;i++)
		a[l + i - 1] = b[i];
}

六:計數排序

計數排序是一個非基於比較的排序演算法,該演算法於1954年由 Harold H. Seward 提出。它的優勢在於在對一定範圍內的整數排序時,它的複雜度為Ο(n+k)(其中k是整數的範圍),快於任何比較排序演算法。[1-2] 當然這是一種犧牲空間換取時間的做法,而且當O(k)>O(n*log(n))的時候其效率反而不如基於比較的排序(基於比較的排序的時間複雜度在理論上的下限是O(n*log(n)), 如歸併排序,堆排序)

假設輸入的線性表L的長度為n,L=L1,L2,..,Ln;線性表的元素屬於有限偏序集S,|S|=k且k=O(n),S={S1,S2,..Sk};則計數排序可以描述如下:1、掃描整個集合S,對每一個Si∈S,找到線上性表L中小於等於Si的元素的個數T(Si);2、掃描整個線性表L,對L中的每一個元素Li,將Li放在輸出線性表的第T(Li)個位置上,並將T(Li)減1。
void cntsort(int *a, int n, int *s,int *Rank)
{
	int i, j;
	for (i = 1;i <= n;i++)
		s[a[i]]++;
	for (i = 1;i <= 100;i++) 
		s[i] += s[i - 1];
	for (i = n;i >= 1;i--)
		Rank[s[a[i]]--] = a[i];
	for (i = 1;i <= n;i++)
		printf("%d ", Rank[i]);
	printf("\n");
}

七:快速排序

快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列

設要排序的陣列是A[0]……A[N-1],首先任意選取一個數據(通常選用陣列的第一個數)作為關鍵資料,然後將所有比它小的數都放到它前面,所有比它大的數都放到它後面,這個過程稱為一趟快速排序。值得注意的是,快速排序不是一種穩定的排序演算法,也就是說,多個相同的值的相對位置也許會在演算法結束時產生變動。一趟快速排序的演算法是:1)設定兩個變數i、j,排序開始的時候:i=0,j=N-1;2)以第一個陣列元素作為關鍵資料,賦值給key,即key=A[0];3)從j開始向前搜尋,即由後開始向前搜尋(j--),找到第一個小於key的值A[j],將A[j]和A[i]互換;4)從i開始向後搜尋,即由前開始向後搜尋(i++),找到第一個大於key的A[i],將A[i]和A[j]互換;5)重複第3、4步,直到i=j; (3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]不大於key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到為止。找到符合條件的值,進行交換的時候i, j指標位置不變。另外,i==j這一過程一定正好是i+或j-完成的時候,此時令迴圈結束)。

複雜度:O(nlogn)

void quicksort(int *a, int l, int r)
{
	int i = l, j = r, k = a[l];
	if (l < r)
	{
		while (i < j)
		{
			while (i < j&&a[j] >= k)
				j--;
			if (i < j) a[i++] = a[j];
			while (i < j&&a[i] <= k)
				i++;
			if (i < j) a[j--] = a[i];
		}
		a[i] = k;
		quicksort(a, l, i - 1);
		quicksort(a, i + 1, r);
	}
}

八:堆排序

堆排序(Heapsort)是指利用堆積樹(堆)這種資料結構所設計的一種排序演算法,它是選擇排序的一種。可以利用陣列的特點快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在陣列的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。

大根堆排序演算法的基本操作:①建堆,建堆是不斷調整堆的過程,從len/2處開始調整,一直到第一個節點,此處len是堆中元素的個數。建堆的過程是線性的過程,從len/2到0處一直呼叫調整堆的過程,相當於o(h1)+o(h2)…+o(hlen/2) 其中h表示節點的深度,len/2表示節點的個數,這是一個求和的過程,結果是線性的O(n)。②調整堆:調整堆在構建堆的過程中會用到,而且在堆排序過程中也會用到。利用的思想是比較節點i和它的孩子節點left(i),right(i),選出三者最大(或者最小)者,如果最大(小)值不是節點i而是它的一個孩子節點,那邊互動節點i和該節點,然後再呼叫調整堆過程,這是一個遞迴的過程。調整堆的過程時間複雜度與堆的深度有關係,是lgn的操作,因為是沿著深度方向進行調整的。③堆排序:堆排序是利用上面的兩個過程來進行的。首先是根據元素構建堆。然後將堆的根節點取出(一般是與最後一個節點進行交換),將前面len-1個節點繼續進行堆調整的過程,然後再將根節點取出,這樣一直到所有節點都取出。堆排序過程的時間複雜度是O(nlgn)。因為建堆的時間複雜度是O(n)(呼叫一次);調整堆的時間複雜度是lgn,呼叫了n-1次,所以堆排序的時間複雜度是O(nlgn)[2]完整程式碼
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxm = 100005;
int a[maxm];
void max_heap(int a[], int x, int n)
{
	int id = x, L = x * 2, R = x * 2 + 1;
	if (L <= n&&a[L] > a[id]) id = L;
	if (R <= n&&a[R] > a[id]) id = R;
	if (id != x)
	{
		swap(a[id], a[x]);
		max_heap(a, id, n);
	}
}
void build_maxheap(int a[], int n)
{
	for (int i = n / 2;i >= 1;i--)
		max_heap(a, i, n);
}
void heapsort(int a[], int n)
{
	int len = n, i;
	build_maxheap(a, len);
	for (i = n;i >= 2;i--)
	{
		swap(a[1], a[i]);
		len--;
		max_heap(a, 1, len);
	}
}
int main()
{
	int n, i, j;
	scanf("%d", &n);
	for (i = 1;i <= n;i++)
		scanf("%d", &a[i]);
	heapsort(a, n);
	for (i = 1;i <= n;i++)
		printf("%d ", a[i]);
	printf("\n");
	return 0;
}