演算法之快速排序

2021-09-11 14:48:12 字數 4708 閱讀 9209

比如乙個陣列為

int  arr = new

int

複製**

先從該陣列中選乙個數,將它放到它原本的位置,什麼意思呢?就是比如我們選擇 4,我們必須保證 4 之前的所有數都比 4 小,4 之後的所有數都比 4 大,這時 4 就是在它排序後的正確位置。

就像這樣:

2,3,1,4,6,5,7,8

複製**

然後 4 之前數和 4 之後的數再使用同樣的方法進行排序。逐漸遞迴。

如何將上述的 4 移到正確的位置就是快速排序的核心。 這個過程叫做partition(分割)

通常使用陣列的第乙個數作為分割陣列的標誌點。然後通過遍歷陣列將陣列分割為 2 部分。如圖:

用 l 記錄第乙個數的索引,j 記錄< v的最後乙個數的索引,用 i 記錄當前數的索引。 這樣就滿足下面的關係:

[ arr[l+1], arr[j] ] < v

[ arr[j+1], arr[i-1]] > v

複製**

對於當前位置a[i] = e; 它有 2 種情況:

e > v

直接併入到>v區間的後面,,這時 i 後移乙個位置,需要把 i 加 1 :i++

e < v

只需要將 e 和> v的第乙個位置交換位置,這時 j 多了乙個,需要把 j 加 1 :j++

最後將 l 位置的數和 j 位置的數調換一下,就完成了排序。

private

static

void

sort

(comparable arr, int l, int r)

複製**

// 對 arr[l...r] 部分進行分割操作

// 返回乙個索引 p,使得 arr[l...p-1] < arr[p] < arr[p+1...r]

@suppresswarnings("unchecked")

private

static

intpartition

(comparable arr, int l, int r)

}// 將 l 位置和 j 位置換一下順序

swap(arr, l, j);

return j;

}複製**

當資料量較小的時候,使用插入排序來優化。

對於近乎有序的陣列來說,基礎版本的快排比歸併排序慢很多,這是為什麼?

歸併排序每次都將乙個陣列平均一分為二。

快速排序雖然也是一分為二,但是不是平均分。分出來的陣列一大一小。繼續分下去也是這樣。

我們不能保證快排的這顆二叉樹的高度是 logn, 但是歸併排序卻可以保證。

快排的最差情況是當陣列為完全的有序陣列時。

右邊的陣列都比 v 大,所以每次只能分乙個陣列,這樣這顆樹就會有 n 層。每層的操作又會有 o(n) 的複雜度,就會產生 0(n^2) 的複雜度。

問題的原因就是我們現在選擇的是最左邊的元素作為中間分割元素,我們不知道它在陣列中處於什麼位置,導致分割的陣列長度不確定,甚至只能分割成乙個陣列。

我們現在只要去隨機選擇這個 v。這種情況下,o(n^2) 幾乎不存在。

@suppresswarnings("unchecked")

private

static

void

sort

(comparable arr, int l, int r)

// 返回乙個中間數的索引

int p = partition(arr, l, r);

sort(arr, l, p - 1);

sort(arr, p + 1, r);

}複製**

// 對 arr[l...r] 部分進行分割操作

// 返回乙個索引 p,使得 arr[l...p-1] < arr[p] < arr[p+1...r]

@suppresswarnings("unchecked")

private

static

intpartition

(comparable arr, int l, int r)

}// 將 l 位置和 j 位置換一下順序

swap(arr, l, j);

return j;

}複製**

如果乙個百萬級別的陣列中含有大量的重複元素。上面的快排會很慢。為什麼呢?

大量的重複元素導致分割完的 2 個陣列,非常不平衡。要麼是<=v的部分特別大,要麼是>=v的部分特別大。 這時我們的排序演算法會退化到o(n^2)的複雜度。

換乙個新的思路,採用新的 partition 方案:

<=v>=v放在陣列的兩端。兩端都帶=是因為這樣左右同時判斷=的情況就不會出現相等的數集中一端,而是近似平均的分布在 2 端。

從 i 這個位置開始向右掃瞄,當前元素是<=v的時候就繼續右移掃瞄。直到碰到>v的數時就停止右移。

j 位置也一樣,從 j 向左移動掃瞄,當前元素是>=v時就繼續左移,知道碰到當 i 和 j 都停止的時候,i 位置的數滿足arr[i] > v,j 位置的數滿足arr[j] < v,此時將 i 和 j 位置的數交換位置。使得 v 右邊的數滿足arr[0...j] < varr[i...r] < v。 這樣陣列就排序完了。

修改後的**:

/**

* 雙路同時進行分割

*/@suppresswarnings("unchecked")

private

static

int partition2(comparable arr, int l, int r)

// >=v 部分的迴圈

for (int i = r; i > j; i--)

// 將 k 位置和 j 位置換一下順序

swap(arr, k, j);

return

newint;

}複製**

@suppresswarnings("unchecked")

private

static

void

sort

(comparable arr, int l, int r)

// 返回一對數的索引,0 位置是 <=v 的最右端,1 位置是 >=v 的最左端

int p = partition2(arr, l, r);

sort(arr, l, p[0]);

sort(arr, p[1], r);

}複製**

三路排序和之前的思想類似,把陣列分為了< v== v> v三部分。

e == v

e 直接併入 == v 的部分,然後i++右移

e < v

將 e 和 == v 區間的第乙個數交換位置,lt++,i++

e > v

將 e 和gt-1位置的數互換位置,gt--,i++

最終會變成這樣:

gt 和 i 重合的時候就是結束的時候。

此時將v< v的最後乙個數互換位置,就完成了一次 partition。

**如下:

@suppresswarnings("unchecked")

private

static

void

sort

(comparable arr, int l, int r)

int p = partition3(arr, l, r);

int lt = p[0];

int gt = p[1];

sort(arr, l, lt-1);

sort(arr, gt, r);

}複製**

@suppresswarnings("unchecked")

private

static

int partition3(comparable arr, int l, int r)

// arr[i] > v,將 e 和 gt-1 位置的數互換位置

else

if (arr[i].compareto(v) > 0)

// arr[i] == v

else

}swap(arr, l, lt);

return

newint;

}複製**

演算法與資料結構

演算法 排序演算法之快速排序

很受打擊啊啊啊啊啊!這道排序題我很久之前就做過,而且當時沒用20分鐘就搞定了,可是,今天在公司做完手上的活之後打算刷題時,又心血來潮的想重做一遍,心想反正也花不了多少時間,結果。血崩了。要求 對於乙個int陣列,請編寫乙個快速排序演算法,對陣列元素排序。給定乙個int陣列a及陣列的大小n,請返回排序...

演算法 排序演算法之快速排序

快速排序是由東尼 霍爾所發展的一種排序演算法。在平均狀況下,排序 n 個專案要 nlogn 次比較。在最壞狀況下則需要 n2 次比較,但這種狀況並不常見。事實上,快速排序通常明顯比其他 nlogn 演算法更快,因為它的內部迴圈 inner loop 可以在大部分的架構上很有效率地被實現出來。快速排序...

排序演算法之快速排序

快速排序使用分治法 divide and conquer 策略來把乙個序列 list 分為兩個子串行 sub lists 步驟為 從數列中挑出乙個元素,稱為 基準 pivot 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面 相同的數可以到任一邊 在這個分割槽退出...