比如乙個陣列為
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] < v
,arr[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 區間的第乙個數交換位置,e > vlt++
,i++
將 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 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面 相同的數可以到任一邊 在這個分割槽退出...