1. 程式人生 > >常用排序演算法

常用排序演算法

常用演算法總結

最近為了面試,在惡補演算法的一些知識,算是嚐到了大學沒有好好學習苦頭,博文是轉載的,我為了加深一些映像就自己寫一篇,順便加了一些自己的理解,有錯誤的話希望各位能夠指正。

排序演算法大體可分為兩種: 一種是比較排序,時間複雜度O(nlogn) ~ O(n^2),主要有:氣泡排序,選擇排序,插入排序,歸併排序,堆排序,快速排序等。 另一種是非比較排序,時間複雜度可以達到O(n),主要有:計數排序,基數排序,桶排序等。

這裡我們來探討一下常用的比較排序演算法,非比較排序演算法將在下一篇文章中介紹。下表給出了常見比較排序演算法的效能: Alt

排序演算法穩定性的定義: 排序演算法穩定性的簡單形式化定義為:如果Ai = Aj,排序前Ai在Aj之前,排序後Ai還在Aj之前,則稱這種排序演算法是穩定的。通俗地講就是保證排序前後兩個相等的數的相對順序不變。 排序演算法穩定性的好處

: 排序演算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序,前一個鍵排序的結果可以為後一個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位排序後元素的順序在高位也相同時是不會改變的。

氣泡排序

這個演算法就不用介紹了吧,基本每個初學者第一個接觸的演算法就是冒泡演算法,名字的由來就是沒經過一輪比較,最大或最小的數字就會像氣泡一樣浮到最尾段。 冒泡演算法的工作順序為: 1、比較相鄰的元素,如果需要調換位置的就進行調換 2、對每個元素重複步驟一,到最後一個元素將會是最大的元素 3、返回開始繼續重複上述的步驟,除了最後一個元素 4、繼續重複上述的步驟,直至沒有元素任何一對數字需要比較

// BubbleSort
void swap(int A, i, j) {
	int temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void BubbleSort(int A, int n) {
	for(int i = 0; i < n-1; i++) {
		for(int j = 0; j < n-i-1; j++) { //每比完一輪,最大元素就浮到最後面,繼續比較浮完元素的前面元素
			if(A[j] > A[j+1]) {
				swap(A, j, j+1);
			}
		} 
	}
}
void mian() {
	int
A[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; int n = sizeof(A) / sizeof(int); BubbleSort(A, n); printf("氣泡排序結果:"); for (int i = 0; i < n; i++) { printf("%d ", A[i]); } printf("\n"); return 0; }

實現過程如下: Alt

冒泡演算法的優點就是易於理解,但是是很沒有效率的演算法(如果一個數組有n個數,那麼排序完成後需要比較n*(n-1)/2次)

選擇排序

選擇排序也是一種簡單直觀的排序演算法。它的工作原理很容易理解:初始時在序列中找到最小(大)元素,放到序列的起始位置作為已排序序列;然後,再從剩餘未排序元素中繼續尋找最小(大)元素,放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。 選擇排序跟氣泡排序最大區別在於:氣泡排序在遍歷的過程中重要符合條件都會進行交換,而選擇排序則在遍歷過程中記住了當前的最大或最小元素,才進行位置的交換

#include <stdio.h>

void swap(int A[], i, j) {
	int temp = A[i];
    A[i] = A[j];
    A[j] = temp;
}
void SelectSort(int A[], int n) {
	for(int i = 0; i < n-1; i++) {
		int min = i;
		for(int j = i+1; j < n-1; j++) {
			if(A[min] > A[j]) {
				min = j;
			}
		}
		if(min != i) {
			swap(A, min, i);
		}
	}
}
void mian() {
	int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };
	int n = sizeof(A) / sizeof(int);
	SelectSort(A, n);
    printf("氣泡排序結果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

選擇排序實現過程: alt 選擇排序是不穩定的演算法: 比如序列:{ 5, 8, 5, 2, 9 },第一次選擇的最小元素是2,然後把2和第一個5進行交換,從而改變了兩個元素5的相對次序。

插入排序

插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。插入排序的特點就是比較過的元素始終是排好序的。 具體演算法描述如下: 1、預設第一個元素是排好序的,從第二個元素開始進行向前比較 2、與上一個元素進行比較,如果比上一個元素小的話,則向前挪一位 3、重複步驟二,直到上一個元素比該元素小 4、將該元素插入到比較元素的後面 5、重複上述步驟,直到最後一個元素

#include <stdio.h>

void InsertionSort(int A[], int n) {
	for(int i = 1; i < n; i++) { //預設第一個元素是排好序的
		int right = A[i];
		int left = i - 1;
		while(j >= 0 && right < A[left]) { //當等於的時候不進入迴圈,因此插入排序是穩定的
			A[left] = A[left+1]; //如果右邊的元素比左邊的要小,就需要換位置
			left--;
		}
		A[left+1] = A[right]; //最後將元素插入到合適的位置中
	}
}
void main() {
	int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };// 從小到大插入排序
    int n = sizeof(A) / sizeof(int);
    InsertionSort(A, n);
    printf("插入排序結果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

上述程式碼對序列{ 6, 5, 3, 1, 8, 7, 2, 4 }進行插入排序的實現過程如下: alt 插入排序是穩定的演算法,在數量級較小的時候速度還是比較快的,但是當數量級多的時候,元素的移動會變得很費時間和效率。插入排序在工業級庫中也有著廣泛的應用,在STL的sort演算法和stdlib的qsort演算法中,都將插入排序作為快速排序的補充,用於少量元素的排序(通常為8個或以下)。

二分插入排序

因為插入排序前面比較過的元素始終是排序好的,所以後面的元素在查詢插入位置的時候可以用二分查詢,可以減少比較次數。

#include <stdio.h>
void InsertionSortDichotomy(int A[],  n) {
	for(int i = 1; i < n; i++) {
		int left = 0;
		int right = i-1;
		int ins = A[i];
		while(left <= right) {
			int mid = (left + right) / 2;
			if(A[mid] > ins) {
				right = mid - 1;
			} else {
				left = mid + 1;
			}
		}
		for(int j = right; j >= left; j--) {
			A[j+1] = a[j];
		}
		A[left] = ins;
	}
}
int main()
{
    int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 從小到大二分插入排序
    int n = sizeof(A) / sizeof(int);
    InsertionSortDichotomy(A, n);
    printf("二分插入排序結果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

當n較大時,二分插入排序的比較次數比直接插入排序的最差情況好得多,但比直接插入排序的最好情況要差,所當以元素初始序列已經接近升序時,直接插入排序比二分插入排序比較次數少。二分插入排序元素移動次數與直接插入排序相同,依賴於元素初始序列

快速排序

快速排序是由東尼·霍爾所發展的一種排序演算法。在平均狀況下,排序n個元素要O(nlogn)次比較。在最壞狀況下則需要O(n^2)次比較,但這種狀況並不常見。事實上,快速排序通常明顯比其他O(nlogn)演算法更快,因為它的內部迴圈可以在大部分的架構上很有效率地被實現出來。 快速排序使用分治策略(Divide and Conquer)來把一個序列分為兩個子序列。步驟為:

1、從序列中挑出一個元素,作為"基準"(pivot). 2、把所有比基準值小的元素放在基準前面,所有比基準值大的元素放在基準的後面(相同的數可以到任一邊),這個稱為分割槽(partition)操作。 3、對每個分割槽遞迴地進行步驟1~2,遞迴的結束條件是序列的大小是0或1,這時整體已經被排好序了。

#include <stdio.h>
void swap(int A[], i, j) {
	temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void Partition(int A[], left, right) {
	int pivot = A[right]; //將最後一個元素假設為基準值
	int tail = left - 1;
	for(int i = left; i < right; i++) {
		if(A[i] <= pivot) {
			swap(A, ++tail, i);
		}
	}
	swap(A, ++tail, right);
	return tail;
}
void QuitSort(int A[], left, right) {
	if(left >= right) {
		return;
	}
	int pivot = Partition(A, left, right);
	QuitSort(A, left, pivot-1);
	QuitSort(A, pivit+1, right);
}
int main()
{
    int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; // 從小到大快速排序
    int n = sizeof(A) / sizeof(int);
    QuickSort(A, 0, n - 1);
    printf("快速排序結果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}

快速排序是不穩定的排序演算法,不穩定發生在基準元素與A[tail+1]交換的時刻。 比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基準元素是5,一次劃分操作後5要和第一個8進行交換,從而改變了兩個元素8的相對次序。