第二節課(2)

快速排序

劃分

  • 給定一個數列A[lo...hi]
  • 重排數列A[lo...hi],使其成爲兩個子數組(可能爲空)A[lo...mi-1]和A[mi+1...hi]

對任何lo \leq u\leq mi-1mi+1\leq v\leq hi,都有A[u]< A[mi]< A[v]

圖1

 x稱爲軸(pivot)。

快速排序的步驟:

  • 先調用劃分
  • 遞歸排序A[lo...mi-1]和A[mi+1...hi]

劃分的思想:

使用A[hi]作爲軸,從左向右擴展分區。

圖2
  •  初始化(i,j) = (lo-1,lo)
  • j每次加1,必要時i加1(當A[j] > x時,只增加j;當A[j] <= x時,還需增加i,並交換i,j號元素)
  • 當j==hi時,結束

 c++實現:

/************************************************************** 
	快速排序一次劃分 (Partition) 
	時間複雜度:O(n) 
	備註:[lo,hi]爲閉區間 
***************************************************************/
int Partition(vector<int>& v, int lo, int hi)
{
	int p = lo + rand()%(hi-lo+1);	//隨機取軸
	//swap v[p] and v[hi] 
	int temp = v[p];
	v[p] = v[hi];
	v[hi] = temp;
	int pivot = v[hi];
	int i = lo - 1;
	for(int j = lo; j < hi; ++j){
		if(v[j] <= pivot){
			++i;
			//swap v[i] and v[j]
			temp = v[i];
			v[i] = v[j];
			v[j] = temp;
		}
	}
	//swap v[i+1] and v[hi]
	temp = v[i+1];
	v[i+1] = pivot;
	v[hi] = temp;	
	return i+1;
}
/******************************************************************
	快速排序 (QuickSort) 
	時間複雜度:O(nlogn) 
	備註:[lo,hi)爲左閉右開區間 
*******************************************************************/
void QuickSort(vector<int>& v, int lo, int hi)
{
	if(hi - lo < 2)	//單元素區間自然有序 
		return;
	int mi = Partition(v,lo,hi-1);
	QuickSort(v,lo,mi);
	QuickSort(v,mi+1,hi); 
}

 

堆排序

優先級隊列

優先級隊列是一種抽象數據結構,支持以下兩種操作:

  • 插入(Insert):插入堆新的元素
  • 提取最小值(Extract-Min):從隊列中移除並返回最小元素

二叉堆(Binary Heap):完全二叉樹

堆序:A[parent(i)] <= A[i](最小堆)

堆的性質:

如果維持好堆序,堆可以高效支持以下兩種操作:(假設隊中有n個元素)

  • 插入(Insert):O(logn)時間複雜度
  • 提取最小值(Extract-Min):O(logn)時間複雜度

插入(Insertion)

  • 插入新元素到堆末
  • 恢復最小堆的性質(滲透:如果該元素的父節點比該元素更大,交換它們)
  • 正確性:每次交換後,新元素爲根的子樹滿足最小堆的性質
  • 時間複雜度:O(logn)

提取最小值(Extract-Min)

  • 複製最後元素到根
  • 通過下滲透恢復最小堆的性質(如果該元素比它的兩個孩子中之一更大,將該元素與較小的孩子交換)
  • 正確性:每次交換後,除了包含該元素的節點外其他所有節點都滿足最小堆的性質
  • 時間複雜度:O(logn)

堆排序

  • 構建一個n個元素的二叉堆(一個一個插入n個元素,更高效的方法見《算法導論》)
  • 執行Extract-Min操作n次

c++實現:

/******************************************************************
	堆插入(HeapInsert)
	時間複雜度:O(logn)
	備註1:v爲最小堆,k爲插入元素
	備註2:堆數組從索引0開始,計算父子索引時略有改變
*******************************************************************/
void HeapInsert(vector<int>& v, int k)
{
	v.push_back(k);
	int i = v.size() - 1;
	int p = (i - 1) / 2;	//父節點索引 
	while (i > 0 && v[p] > v[i]) {
		int temp = v[p];
		v[p] = v[i];
		v[i] = temp;
		i = p;
		p = (i - 1) / 2;
	}
	return;
}
/******************************************************************
	提取最小值(Extract-Min)
	時間複雜度:O(logn)
	備註1:v爲最小堆,返回堆頂元素
	備註2:堆數組從索引0開始,計算父子索引時略有改變
*******************************************************************/
int ExtractMin(vector<int>& v)
{
	int r = v[0];
	int n = v.size();
	v[0] = v[n - 1];
	v.pop_back();
	--n;
	int i = 0;
	int j = 2 * i + 1;	//左子索引 
	while (j < n) {	//存在左子 
		if (j < n - 1 && v[j] > v[j + 1])	//如果右子存在且較小
			++j;

		if (v[i] < v[j])
			break;
		else {
			int temp = v[i];
			v[i] = v[j];
			v[j] = temp;
			i = j;
			j = 2 * i + 1;
		}
	}
	return r;
}
/******************************************************************
	堆排序(HeapSort)
	先通過n次插入方式建堆,再n次提取堆頂元素排序
	時間複雜度:O(nlogn)
*******************************************************************/
void HeapSort(vector<int>& v)
{
	int n = v.size();
	vector<int> t;
	for (int i = 0; i < n; ++i)
		HeapInsert(t, v[i]);	//建堆
	for (int i = 0; i < n; ++i)
		v[i] = ExtractMin(t);	//提取堆頂元素
	return;
}

基於比較的排序算法的時間複雜度下界

基於比較的排序算法的時間複雜度下界:\Omega (nlogn)。(證明:決策樹的高度)

 

最快的排序算法

Which algorithm is the best in practice?

圖3 運行效果

三種排序算法的運行時間對比可知:實際中,快速排序的用時最短,但與STL中實現的差距很大,可優化的空間還很大。 

 

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