1.快速排序的思想
經過一趟排序將待排序的記錄分割成獨立的兩部分,其中一部分記錄比關鍵字大的,一部分記錄比關鍵字小的,在可分別對這兩部分記錄繼續進行排序,以達到整體有序的目的。
<pre name="code" class="cpp">void Qsort(int *ar, int left, int right); //函數聲明
int partition(int *arr, int left, int right);//函數聲明
/////////////////////////////////////////////////////////////////////////////////////////////
void quick_sort(int *arr, int len) //排錯
{
assert(arr != NULL);
Qsort(arr, 0, len - 1);
}
void Qsort(int *arr, int left, int right) //左右來篩
{
int mid = 0;
if (left < right)
{
mid = partition(arr, left, right);//一次劃分
Qsort(arr, left, mid - 1);
Qsort(arr, mid + 1, right);
}
}
int partition(int *arr, int left, int right)
{
int key = arr[right];//注意和下面左篩右篩對應
while (left < right)
{
while (left < right && arr[left] <= key) //左篩
{
left++;
}
if (left < right) swap(arr, left, right); //交換
while (left < right && arr[right] >= key)//右篩
{
right--;
}
if (left < right) swap(arr, left, right); //交換
}
return left;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void swap(int *arr, int left, int right)
{
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
partition函數要做的事情,就是先選取其中的一個關鍵字,然後想辦法將它放到一個位置,使得它左邊的值都比它小,右邊的值都比它大,我們稱這樣的關鍵字爲樞紐。pivot
2.空間時間
時間: 最優情況O(nlogn) 最壞情況0(n^2)
空間: 最好情況O(log2n) 最壞情況O(n) 平均情況 空間O(logn);
穩定: 不穩定
3.快速排序的優化:
3.1優化選取樞軸: 隨機取中法。
三數取中法。(取三個關鍵字先進行排序,將中間數作爲樞軸,一般是取左端、右端和中間三個數。
int pivotkey;
int m=low + (high-low)/2;
if(ar[low] > ar[high) swap(ar,low,high);
if(ar[m] > ar[high]) swap(ar,high,m);
if(ar[m] > ar[low]) swap(ar,m,low);
pivotkey = ar[low];
3.2優化不必要的交換
3.3優化小數組時的排序方案
3.4優化遞歸操作。
/////////////////////////////////////////////////////////////////////////////////////////////////
/**使用遞歸快速排序**/
template<typename Comparable>
void quicksort1(vector<Comparable> &vec, int low, int high)
{
if (vec.empty() || low < 0) return;
if (low < high)
{
int mid = partition(vec, low, high);
quicksort1(vec, low, mid - 1);
quicksort1(vec, mid + 1, high);
}
}
/**使用棧的非遞歸快速排序**/
<pre name="code" class="cpp">/**使用棧的非遞歸快速排序**/
template<typename Comparable>
void quicksort2(vector<Comparable> &vec, int low, int high)
{
stack<int> st;
if (low < high)
{
int mid = partition(vec, low, high);
if (low < mid-1)
{
st.push(low);
st.push(mid-1);
}
if (mid+1 < high)
{
st.push(mid+1);
st.push(high);
}
//其實就是用棧保存每一個待排序子串的首尾元素下標,下一次while
//循環時取出這個範圍,對這段子序列進行partition操作
while (!st.empty())
{
int high = st.top(); //範圍的右邊
st.pop();
int low = st.top(); //範圍的左邊
st.pop();
mid = partition(vec, low, high);
if (low < mid - 1)
{
st.push(low);
st.push(mid-1);
}
if (mid + 1< high)
{
st.push(mid+1);
st.push(low);
}
}
}
}
//一次劃分
template <typename Comparable>
int partition(vector<Comparable> &vec, int low, int high)
{
Comparable pivot = vec[low]; //任選元素作爲軸,這裏選首元素
while (low < high)
{
while (low < high && vec[high] >= pivot) high--;
if (low < high) vec[low] = vec[high];
while (low < high && vec[low] <= pivot) low++;
if (low < high) vec[high] = vec[low];
}
vec[low] = pivot; //此時low==high
return low;
}
可以看到非遞歸的算法比遞歸實現還要慢。下面解釋爲什麼會這樣。
遞歸算法使用的棧由程序自動產生,棧中包含:函數調用時的參數和函數中的局部變量。如果局部變量很多或者函數內部又調用了其他函數,則棧會很大。每次遞歸調用都要操作很大的棧,效率自然會下降。
而對於非遞歸算法,每次循環使用自己預先創建的棧,因此不管程序複雜度如何,都不會影響程序效率。
但對於上面的快速排序,由於局部變量只有一個mid,棧很小,所以效率並不比非遞歸實現的低。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
對長度爲n 的線性表作快速排序,在最壞情況下,比較次數爲n(n-1)/2.
快速排序的最壞情況就是數組有序,選的基數要和每一個數進行比較,(n-1)+(n-2)+...+1=n(n-1)/2次。