做爲統治世界的算法之一,快速排序(Quick Sort)在不少場合下都能發揮其強大的力量。數據量在百萬級別的數據量對快速排序來講是小case. 該算法最先是由圖靈獎得到者Tony Hoare設計出來的,他在形式化方法理論以及ALGOL60編程語言的發明中都有卓越的貢獻。能夠認爲是冒泡排序的升級,它們都屬於交換排序類。即經過不斷的比較和移動交換來實現排序,只不過它的實現,增大了記錄的比較和移動的距離,將關鍵字較大的記錄移到後面,較小的移到前面,從而減小了總的比較次數和移動交換次數。html
在實現快速排序的過程當中,咱們通常須要關注2點,一個是樞紐元的選擇(即pivot), 還有很重要的一點是兩個工做指針的初始位置以及他們的運動方向。node
看了不少的實現,pivot通常來講有4種選擇:算法
一、頭元素 Hoare版本的作法,《數據結構與算法分析》中不推薦將第一個元素做爲樞紐元,由於在輸入是預排序或反序時,會產生糟糕的分割。 其實尾元素道理是同樣的。編程
二、尾元素 Lomuto版本的作法數據結構
三、中間元素(包括3數中值分割法(median of three), 即取3個關鍵字先進行排序,將中間數做爲pivot, 通常取左中右3個數,也能夠隨機選取。)app
四、隨機選擇 排除隨機數生成的代價外,是一種不錯的選擇less
快排通常來講有如下兩個版本,編程語言
1、先看看Hoare最原始的版本,pivot爲首元素,其工做指針分別在一頭一尾,這徹底就一稍微快一點的冒泡嘛= =:ide
具體實現以下:函數
#include <stdio.h> void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; } void printArray(int a[], int num) { for (int i = 0; i < num; i++) { printf("%d ", a[i]); } printf("\n"); } int HoarePartition(int a[], int p, int r) { int key = a[p], i = p - 1, j = r + 1; // fprintf(fp, "key = %d, i = %d, j = %d\n", key, i, j); while (1) { do { j--; }while (a[j] > key); do { i++; }while (a[i] < key); if (i < j) { swap(&a[i], &a[j]); } else { return j; } } } void QuickSort(int a[], int start, int end) { int q; // fprintf(fp, "new sort: start = %d, end = %d\n", start, end); if (end <= start) return; q = HoarePartition(fp, a, start, end); QuickSort(fp, a, start, q); QuickSort(fp, a, q + 1, end); } int main() { int a[] = {3, 4, 12, 56, 0, 6, 9, 10, 6, 23}; printf(fp, "init array: \n"); printArray(fp, a, 10); QuickSort(fp, a, 0, 9); printf(fp, "sorted: \n"); printArray(fp, a, 10); fclose(fp); return 0; }
2、Nico Lomuto也提出了一個版本,pivot爲尾元素,工做指針都從左向右一個方向運動,我的以爲這個更好理解:
Lomuto-Partition(A, p, r) x = A[r] i = p - 1 for j = p to r - 1 if A[j] <= x i = i + 1 swap( A[i], A[j] ) swap( A[i+1], A[r] ) return i + 1 QUICKSORT(A, p, r) if p < r then q ← Lomuto-Partition(A, p, r) QUICKSORT(A, p, q - 1) QUICKSORT(A, q + 1, r)
分析以下,摘自算法導論:
兩種實現方式對比的話, Lomuto版本更加簡單易實現,但不適用於庫函數的實現,由於它使用了更多的交換次數。更加細節參見:StackExchange
下面是Lomuto版本的實現代碼:
#include <stdio.h> void printArray(int a[], int num) { for (int i = 0; i < num; i++) { printf("%d ", a[i]); } printf("\n"); } void swap(int *a, int *b) { int t; t = *a; *a = *b; *b = t; } int Partition(int data[], int p, int r) { int key = data[r]; int i = p - 1; for (int j = p; j < r; j++) { if (data[j] <= key) { i++; swap(&data[i], &data[j]); } } swap(&data[i+1], &data[r]); return i + 1; } void QuickSort(int data[], int start, int end) { if (start < end) { int q = Partition(data, start, end); QuickSort(data, start, q - 1); QuickSort(data, q + 1, end); } } int main() { int a[] = {3, 32, 13, 8, 9, 9, 12, 33, 41}; printf("init array:\n"); printArray(a, 9); QuickSort(a, 0, 8); printf("sorted:\n"); printArray(a, 9); }
下面討論一下變種版本:
3、一種Hoare變種實現,pivot爲首元素,工做指針從兩邊向中間移動,也比較好理解
#include <stdio.h> void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; } void printArray(int a[], int num) { for (int i = 0; i < num; i++) { printf("%d ", a[i]); } printf("\n"); } int HoarePartition(int a[], int p, int r) { int key = a[p], i = p, j = r; // fprintf(fp, "key = %d, i = %d, j = %d\n", key, i, j); while (i < j) { while (a[j] >= key && i < j) j--; a[i] = a[j]; while (a[i] <= key && i < j) i++; a[j] = a[i]; } a[i] = key; return i; } void QuickSort(int a[], int start, int end) { int q; // fprintf(fp, "new sort: start = %d, end = %d\n", start, end); if (end <= start) return; q = HoarePartition(a, start, end); QuickSort(a, start, q - 1); QuickSort(a, q + 1, end); } int main() { int a[] = {3, 4, 12, 56, 0, 6, 9, 10, 9, 23}; // FILE *fp = fopen("log", "w+"); printf("init array: \n"); printArray(a, 10); printf("sorted: \n"); QuickSort(a, 0, 9); printArray(a, 10); fclose(fp); return 0; }
#include <stdio.h> void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } int partition(int *a, int start, int end) { int key = a[end]; int i = start - 1; int j = start; for ( ; j < end; j++) { if (a[j] < key) { i++; swap(&a[i], &a[j]); } } swap(&a[++i], &a[j]); return i; } void QuickSort(int *a, int size) { int stack[100]; int top = -1; int start, end; stack[++top] = 0; stack[++top] = size - 1; while (top > 0) { end = stack[top--]; start = stack[top--]; int i = partition(a, start, end); if (start < i - 1) { stack[++top] = start; stack[++top] = i - 1; } if (i + 1 < end) { stack[++top] = i + 1; stack[++top] = end; } } } void printArray(int *a, int num) { for (int i = 0; i < num; i++) { printf("%d ", a[i]); } printf("\n"); } int main() { int a[] = {3, 45, 15, 6, 9, 15, 90, 17, 28, 10}; printf("init array:\n"); printArray(a, 10); QuickSort(a, 10); printf("after sorted:\n"); printArray(a, 10); return 0; }
那麼單鏈表能夠使用快速排序嗎?答案是能夠,實現思路是使用兩個鏈表,一個保存比key小的值,另外一個保存比key大的,最後把兩個鏈表再鏈接起來。這樣經過調整指針的指向便可實現排序效果。
#include <stdio.h> #include <time.h> typedef struct tagLinkNode { int value; struct tagLinkNode *next; }LinkNode; void QuickSort(LinkNode** head, LinkNode** end) { LinkNode *head1, *head2, *end1, *end2; head1 = head2 = end1 = end2 = NULL; if (*head == NULL || *end == NULL) { // printf("head or end is null\n"); return; } // printf("head=%d, end=%d\n", (*head)->value, (*end)->value); LinkNode *p, *pre1, *pre2; p = pre1 = pre2 = NULL; int key = (*head)->value; // printf("key is %d\n", key); // divide head node p = (*head)->next; (*head)->next = NULL; while (p != NULL) { // value less than key if (p->value < key) { // printf("less than key!\n"); if (!head1) { head1 = p; pre1 = p; } else { pre1->next = p; pre1 = p; } p = p->next; pre1->next = NULL; } // value larger than key else { // printf("larger than key!\n"); if (!head2) { head2 = p; pre2 = p; } else { pre2->next = p; pre2 = p; } p = p->next; pre2->next = NULL; } } // printf("merge it\n"); end1 = pre1; end2 = pre2; // recuring QuickSort(&head1, &end1); QuickSort(&head2, &end2); // printf("after recuring\n"); /* conjection 2 List */ // if 2 List all exist if (head1 && head2) { end1->next = *head; (*head)->next = head2; *head = head1; *end = end2; } // only left list exist else if (head1) { end1->next = *head; *end = *head; *head = head1; } // only right list exist else if (head2) { (*head)->next = head2; *end = end2; } } void addList(LinkNode **head, LinkNode *node) { node->value = rand() % 50 + 1; node->next = (*head)->next; (*head)->next = node; printf("%d ", node->value); } LinkNode* getListFirst(LinkNode *head) { return head->next; } LinkNode* getListLast(LinkNode *head) { LinkNode *p = head; while (p->next != NULL) { p = p->next; } return p; } void printList(LinkNode *head) { LinkNode *p = head->next; while (p != NULL) { printf("%d ", p->value); p = p->next; } } int main() { srand(time(NULL)); int i; LinkNode linkArray[10]; LinkNode *listHead; listHead = (LinkNode *) malloc(sizeof(LinkNode)); if (NULL == listHead) { printf("listHead malloc failed\n"); return -1; } listHead->value = 10; listHead->next = NULL; printf("init array: \n"); for (i = 0; i < 10; i++) { addList(&listHead, &linkArray[i]); } printf("\n"); LinkNode *first, *end; first = getListFirst(listHead); end = getListLast(listHead); QuickSort(&first, &end); listHead->next = first; // important printf("after sorted: \n"); printList(listHead); printf("\n"); }
這裏有一篇快排細節優化的文章,對pivot的選取以及存在相同元素的狀況作了詳述。