1)基本思想
快速排序的基本思想是在陣列中選擇乙個樞紐元素,然後分別從陣列的開始和結束位置處開始遍歷陣列,將比樞紐元素小的都移到樞紐元素的左邊,將比樞紐元素大的元素都移到它的右邊,然後遞迴對樞紐元素前面的元素和後面的元素進行排序。即:
這裡的乙個關鍵地方是如何選取樞紐元,有三種選擇:
a. 選取陣列的第乙個元素 這種策略就是比較懶惰的將得到的陣列的第乙個元素作為樞紐元,如果陣列元素是隨機排列的,這是可行的。但如果陣列是預排序或反序的,那麼這將造成陣列幾乎所有的元素都落在s1或者s2中,這樣一來,最終的時間複雜度將達到o(n^2)。
b. 利用隨機數生成器產生隨機樞紐元 一般來講除非運氣比較差會導致隨機產生的樞紐元導致a中的結果,否則還是比較安全的。但是隨機數生成器的使用成本還是很高的,它會抵消節省下來的平均執行時間。
c. 三數中值法 這種方式分別取出陣列元素的第乙個元素,中間的元素和最後乙個元素進行排序並將中值作為樞紐元,最終的結果是三者中最小的元素放在了陣列的起始位置,最大的元素放到了陣列的最後的位置,三者中的中值放在了中間位置上。
這裡選擇c中的方法來選取樞紐元,並將樞紐元移至陣列的倒數第二個位置,從陣列的第二個位置和陣列的倒數第三個位置開始遍歷陣列,設為位置i和j,當i處的元素大於等於樞紐元時停止,當j位置的元素小於等於樞紐元時停止,如果i < j, 交換兩位置處的元素,然後i和j繼續相向遍歷,直到兩個位置指向同一位置或交錯時,此時,交換i處的元素和樞紐元。這樣的結果就是樞紐元前面的元素都比它小或者等於它,樞紐元後面的元素都比它大或者相等。
採用c中方案的乙個好處就是i和j不會有越界風險,因為陣列的第乙個元素比樞紐元小,所以j不會越界,又因為倒數第二個位置是樞紐元,i停在大於等於樞紐元的位置,所以i也不會越界。
需要注意的地方是,在遍歷到等於樞紐元的位置時,不管i或者j這裡都採用停止而非繼續向前的策略,這裡簡單的做乙個反例:對於乙個全部相同的陣列來說,如果i和j在a[i] = a[j]時不交換繼續向前,那麼必須有乙個方法防止i或者j越界,並且最終樞紐元還是被交換到i最後到過的位置,即倒數第二個位置,最終導致兩個極不均衡的陣列,時間複雜度為o(n^2)。而如果在相等處採取交換的策略,那麼最終得到的是幾乎均等的陣列,排序的複雜度為o(nlogn),i和j會交錯,所以不用擔心越界風險。
2)演算法實現
1 #include "view codestdafx.h
"2 #include 3
using
namespace
std;45
const
int cutoff = 3;6
7void swap(int* a, int*b)813
14int media3(int a, int left, int
right)
1521
if(a[left] >a[right])
2225
if(a[center] >a[right])
2629 swap(&a[center], &a[right-1
]);30
31return a[right-1
];32}33
34void insertsort(int a, int left, int
right)
3546
else
47break;48
}49 a[j] =temp;50}
51}5253
void qsort(int a, int left, int
right)
5463
while(a[--j] >pivot)
64if(i
6568
else
6972
}73 swap(&a[i], &a[right-1
]);74 qsort(a, left, i - 1
);75 qsort(a, i + 1
, right);76}
77else
7881}82
83void print(int a, int
n)84
89 cout<
9192
void quicksort(int a, int
n)93
9798
int _tmain(int argc, _tchar*ar**)99;
101 print(a, 10
);102 quicksort(a, 10
);103
104 system("
pause");
105106
return0;
107 }
3)時間複雜度分析
對於n=5~20,採取插入排序更快,因為不需要遞迴的成本。
最壞情況下,樞紐元是最小元素,那麼:
t(n) = t(n-1) + cn
t(n-1) = t(n-2) + c(n-1)
t(2) = t(1) + c*1
=> t(n) = t(1) + c(1+2+...+n) = o(n^2)
最好情況下,樞紐元剛好是陣列的中值,那麼陣列剛好被分為兩個均等的陣列:
t(n) = 2t(n/2) + c*n
t(n)/n = t(n/2)/n/2 + c
t(2)/2 = t(1)/1 + c
t(n)/n = t(1)/1 + c*logn
所以,t(n) = n + c*n*logn = o(nlogn).
平均情況下,假設元素大小是等可能的,那麼t(i) = (1/n)(t(1) + t(2) + ... + t(n-1)),那麼:
t(n) = t(i) + t(n-i-1) + c*n = (2/n)(t(0) + t(1) + t(2) + ... + t(n-1)) + c*n.
令 sumn = t(1) + t(2) + ... + t(n-1),則:
nt(n) = 2(t(0) + t(1) + t(2) + ... + t(n-1))+ c*(n^2)
(n-1)t(n-1) = 2(t(0) + t(1) + t(2) + ... + t(n-2)) + c*((n-1)^2)
所以,nt(n) - (n-1)t(n-1) = 2*t(n-1) + 2cn - c
t(n) = o(nlogn).
排序演算法之快速排序
快速排序使用分治法 divide and conquer 策略來把乙個序列 list 分為兩個子串行 sub lists 步驟為 從數列中挑出乙個元素,稱為 基準 pivot 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面 相同的數可以到任一邊 在這個分割槽退出...
排序演算法之快速排序
快速排序入口 public void quicksort int lists 遞迴呼叫該函式。原理 每次從陣列從選乙個標兵 本實現為簡單起見直接選取給定範圍內的第乙個元素為標兵 讓後在給定範圍內進行雙向遍歷,目的是以標兵為分界線,將所有小於標兵值的數字排一邊,將所有大於標兵的數字 放到另一邊。標兵移...
排序演算法之快速排序
快速排序是一種不穩定的排序演算法,它的基本思想是,以某個元素為基準,將所有大於等於它的值放在右邊,小於它的值放在左邊,這樣陣列就被分為兩部分,遞迴對這兩部分進行快速排序,而單個元素我們認為是已經排好序的。這是一種歸併思想,當然在最後一步,合併,我們什麼也沒有做也不用做。每一次排序都有乙個元素被放在正...