排序總結系列五:快速排序

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次。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章