快速排序及快速選擇終極版

2021-09-30 12:39:39 字數 3751 閱讀 1014

快速排序(quick sort)是一種非常經典高效的排序演算法,採用了基於比較的遞迴分治策略,平均情況下複雜度為o(nlogn),但最壞情況下會退化到o(n^2)。

在筆試面試過程中經常會問到有關於快速排序的問題,甚至要求面試者當場手寫**,本文將簡單介紹快排原理並提供具體的**實現,以及其在求第n小的數中的經典應用。

c++的stl已經提供了快速排序的函式sort,在實際場合如比賽的過程中可以直接進行呼叫。

使用快速排序函式需要使用#include 包含標頭檔案,使用時把陣列的頭尾指標傳遞進去即可。注意尾指標的元素並不參與排序

sort預設完成的是從小到大的排序,需要其他規則的排序時可以自定義排序的比較函式,使用比較函式可以對任意的自定義型別進行排序。

具體的使用方法如下所示:

bool cmp(const int &a, const int &b) //比較函式

void test1()

, n = 5;

cout << "原始:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //原始:4 3 1 2 5

sort(x, x+n); //從小到大

cout << "公升序:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //公升序:1 2 3 4 5

sort(x, x+n, cmp); //根據cmp規則排序,這裡是從大到小

cout << "降序:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //降序:5 4 3 2 1

}

快速排序是乙個遞迴的思想,首先選擇乙個數作為基數,把陣列中小於它的數放在它的左邊,把大於它的數放在它的右邊,然後對左右兩邊的數遞迴進行排序。

演算法的關鍵部分是實現陣列的劃分,即怎麼把陣列的元素劃分成兩部分,使得左邊的數比基數小,右邊的數比基數大。劃分有許多不同的實現方法,這裡主要使用單向掃瞄的方法,後面再稍微介紹雙向掃瞄的方法。

選擇最右邊的數字作為基數。使用乙個變數j記錄當前左邊數字(比基數小的數)的最右的下標值。然後使用變數i從左到右遍歷陣列,如果a[i]小於等於基數,說明a[i]屬於左邊的數,就把j自增,然後交換a[j]和當前的a[i]。因為自增前的j是左邊數字最右的下標,自增後的a[j]肯定不屬於左邊了,把其跟a[i]交換後,新的a[j]是屬於左邊的,而且此時j也重新變為左邊數字最右的下標了。

掃瞄結束後,把j自增(因為a[j]將會被交換到最右邊,因此要選屬於右邊的數字)後與最右邊的基數交換,此時的j即為劃分的結果。

實際應用中有乙個優化,因為快速排序在陣列本來有序的情況下複雜度會退化為o(n^2)。為了避免這點,在選取基數的時候可以隨機地進行選擇。具體做法是把最右邊的數字跟乙個隨機的數字交換位置。另外還有一種三數取中的方法,即選擇首尾跟中間某個數共三個數的中值作為基數。

具體**實現為:

int partition(int a, int l, int r) //對陣列a下標從l到r的元素進行劃分

劃分完成之後快速排序就很簡單了,**如下:

void quicksort(int a, int l, int r) //對陣列a下標從l到r的元素排序

這裡也用乙個簡單的例子進行測試:

void test2()

, n = 5;

cout << "原始:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //原始:4 3 1 2 5

quicksort(x, 0, n-1);

cout << "公升序:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //公升序:1 2 3 4 5

}

剛剛提到,劃分陣列時有另一種雙向掃瞄的方法。

還是以最右邊的元素作為基數。目的是左邊的元素都是小於或等於基數,右邊的元素都是大於基數的。先不管最右的數字,i從左往右掃瞄知道遇到乙個不屬於左邊的數字,j從右往左掃瞄直到遇到乙個不屬於右邊的數字,然後就可以交換i和j上的數字,那麼這兩個數字就放在了它們應該在的位置。然後i++,j–再繼續掃瞄,直到i和j相遇。最後要把最右邊的基數和i上面的數字交換,i就是劃分的結果。

**如下:

int partition2(int a, int l, int r)

swap(a[i], a[r]);

return i;

}

快速排序有許多應用,其中乙個是求乙個陣列中的第n個數。

求第n個數時可以把陣列進行排序,然後直接取出第n個數。但這樣做了許多不必要的排序,實際上可以進行部分排序,每次進行劃分之後判斷要求的第n個數時在左邊還是右邊,然後直接排序相應的部分即可。在部分排序左邊或者右邊的時候,需要把n換成在當前排序部分中的新的n值。

注意這樣求得第n個數時陣列元素的順序已經被改變了。

int nthelement(int a, int l, int r, int id) //求陣列a下標l到r中的第id個數

這裡使用了上面的partition函式進行劃分。乙個簡單的測試例子為:

void test3()

, n = 5, id = 3;

cout << "原始:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //原始:4 3 1 2 5

cout << "第" << id << "個數:" << nthelement(x, 0, n-1, id);

cout << endl; //第3個數:3

}

使用這種方法可以直接求乙個無序陣列的中位數,對於帶權中位數可以通過在計算id和cur的關係時先統計l到m的權值之和再分析得到。

另外,對於這個簡單的應用stl也提供了相應的函式進行求解,實際中也不用自己實現。stl中的函式是algorithm標頭檔案中的nth_element函式,使用nth_element(a, a + mid, a + n)會對陣列a進行部分排序,結果保證第mid+1個數是正確的。

具體的使用方法如下:

void test4()

, n = 5, id = 3;

cout << "原始:";

for (int i = 0; i < n; i++) cout << x[i] << " ";

cout << endl; //原始:4 3 1 2 5

nth_element(x, x + id - 1, x + n);

cout << "第" << id << "個數:" << x[id- 1];

cout << endl; //第3個數:3

}

快速排序也可以不通過遞迴進行實現。主要思想是用棧來儲存子陣列的左右邊界,以下實現使用陣列來模擬棧。
void quicksortiterative (int arr, int l, int h)

// if there are elements on right side of pivot, then push right

// side to stack

if ( p+1 < h )

}}

鍊錶快速排序終極版

快速排序1 演算法只交換節點的val值,平均時間複雜度o nlogn 不考慮遞迴棧空間的話空間複雜度是o 1 這裡的partition我們參考陣列快排partition的第二種寫法 選取第乙個元素作為樞紐元的版本,因為鍊錶選擇最後一元素需要遍歷一遍 具體可以參考here 這裡我們還需要注意的一點是陣...

快速排序,終極研究

快速排序由霍爾 hoare 提出,它是一種對氣泡排序的改正。由於其排序速度快,故稱快速排序 quick sort 快速排序方法的實質是將一組關鍵字 k 1 k 2 k n 進行分割槽交換排序。1.演算法思路 以第乙個關鍵字 k 1 為控制字,將 k 1 k 2 k n 分成兩個子區,使左區所有關鍵字...

陣列及排序(冒泡 選擇 快速排序)

1.陣列 使用單獨的變數名來儲存一系列的值 簡單的說,陣列的作用,就是乙個容器,將多個資料儲存起來 2.宣告陣列的方法 字面量 var arr eg var arr 兔子1 兔子2 兔子3 兔子4 console.log arr 建構函式方法 var arr new array 如果引數為乙個數值 ...