Heapsort 和 priority queue

1、二叉堆含義及屬性:

堆(heap)亦被稱爲:優先隊列(priority queue),是計算機科學中一類特殊的數據結構的統稱。堆一般是一個能夠被看作一棵徹底二叉樹的數組對象。在隊列中,調度程序反覆提取隊列中第一個做業並運行,於是實際狀況中某些時間較短的任務將等待很長時間才能結束,或者某些不短小,但具備重要性的做業,一樣應當具備優先權。堆即爲解決此類問題設計的一種數據結構。同垃圾收集存儲的堆含義不一樣。html


表示堆的數組A有兩個屬性:node

A.length : 表明A數組的元素個數;ios

A.heapsize : 表明A數組中 屬於堆元素個數。有時候(排序時),數組A的部分元素不屬於堆。剛開始建堆的是偶,A.heapsize = A.length,排序時,每次從堆頂取出最大值,A.heapsize遞減,直至排序完成.算法


下圖是一個建好後的二叉堆:api


從圖中可知,已知某節點的索引值i,能夠輕鬆獲取其對應父節點,左,右子節點的索引值。有:數組

Parent(i)
    return i/2; 或者 return i >> 1;
Left(i)
    return i*2; 或者 return i << 1;
Right(i)
    return i*2+1; 或者 return (i << 1) + 1;

二叉堆分兩種: 最大堆,最小堆,均遵循堆屬性。最大堆,每一個節點i知足:數據結構

A[Parent(i)] ≥ A[i]

最小堆與之相反,每一個節點i知足:函數

A[Parent(i)] ≤ A[i]

    

    堆排序算法,咱們用的是最大堆,由於最小堆用於實現優先隊列(priority queues).二叉堆的其餘屬性同二叉樹屬性相似,如節點高度,樹高度等。如n個元素的徹底二叉樹,樹高爲lgn, 故n個元素的二叉堆高度爲lgn.優化


2、保持堆屬性:

保持堆屬性,或者稱"大堆化", 關鍵函數是Max-Heapify, 構造二叉堆,以及二叉堆排序均須要調用該函數,如從無序數組構造二叉堆,就須要使全部節點"大堆化".ui

Max-Heapify 運行時間爲O(h),h爲對應節點高度.

僞代碼分析:

到line 8,已經肯定A[i], A[i].left, A[i].right 三者中最大值。若是最大值是根節點A[i],知足大堆屬性,不需修改;反之, 則需把根節點同子節點中最大節點交換,同時由於最大子節點替換成比其值小的父節點,須要確保以該子節點爲父節點的子樹是否知足大堆屬性,因此須要用largest 做爲新i遞歸調用Max-Heapify.下圖是一個 Max-Heapify 處理實例:


3、建堆

咱們自底向上的用Max-Heapify 將無序數組A[1...n]轉換成一個最大堆。由於二叉堆是完美二叉樹,可知子序列A[(n/2+1)...n]中的元素均是葉子節點,每一個均可以當作是一個元素的堆, 默認就知足堆屬性。

BUILD-MAX-HEAP 對樹中剩餘全部非葉子節點調用 Max-Heapify,以維持堆屬性。 

BUILD-MAX-HEAP運行時間爲O(n)。


下圖是無序數組建堆示例圖:


4、堆排序

接下來就是堆排序的實現了,經過BUILD-MAX-HEAP 創建大堆後,最大的元素在root,即A[0].能夠先提取出該最大值,HeapSort首先把A[0]同A[n-1]交換,而後經過減少A.heap_size 來將最後一個元素a[n-1]移出堆。這樣咱們就獲取了一個最大值。同時能夠知道除了新的根節點,其餘節點原來均保持堆屬性,爲恢復堆屬性,咱們調用Max-Heapify(A,0),以讓以A[0]爲根節點的堆保持堆屬性.接下來重複該過程,每次移除最大節點(root節點),能夠完成排序操做。

下圖是構建大堆後,堆排序的示例圖:

HeapSort 運行時間爲O(nlgn).有堆排序的平均時間複雜度爲O(nlgn),空間複雜度爲O(1)。


5、優先級隊列(priority queue)

    做爲排序算法,實際中快排比堆排序更加有效。但二叉堆有別的用處,如高效的優先級隊列。同二叉堆同樣,優先級隊列也分兩種: 最大優先級隊列,最小優先級隊列。

    優先級隊列做爲管理一組數據集合S的數據結構,支持如下操做:

  1. Insert(S,x) 將新元素插入集合S中。

  2. Maximum(S) 返回集合S中最大元素。

  3. Extract-Max(S) 從集合S中移除最大元素,並返回。

  4. Increase-Key(S,x,key) 將集合S中x位置元素值替換成key,這裏假定新key值不能小於x原來位置值,主要用來Insert時調用,新增空節點,並賦予新key值。


    最大優先級隊列用於在分時系統中進行做業調度。隊列對將要執行的做業和做業優先級作記錄。當一個做業完成或者中斷了,用Extract-Max 從全部待運行做業中,取出最高優先級做業。任什麼時候候加入新做業,可使用Insert 加入隊列中。

    相應的,最小優先級隊列支持Insert,Minimum,Extract-Min,Decrease-Key等操做。主要用於事件驅動模擬器中。


一、Heap-Extract-Max 僞代碼以下,相似於Heapsort for循環部分:

Heap-Extract-Max 運行時間爲O(lgn).


二、Heap-increase-key 僞代碼以下,由於把key值賦予(如新建立的空)i節點,可能會破壞堆屬性,全部line 4的while循環用於恢復堆屬性。 

Heap-increase-key 運行時間爲O(lgn).


三、Max-Heap-insert僞代碼以下:

Max-Heap-insert 運行時間爲O(lgn). 可知,n個節點的二叉堆,優先級隊列全部操做時間均爲O(lng).


6、二叉堆其餘應用:

一、C++中的STL 已經實現了優先隊列 priority_queue, 網上也不少文章介紹如何使用。

二、哈夫曼編碼。

三、一些任務調度算法。

四、一個修改版的堆排:每次不是將堆底的元素拿到上面去,而是直接比較堆頂(最大)元素的兩個兒子,即選出次大的元素。因爲這兩個兒子之間的大小關係是很不肯定的,二者都很大,說很差哪一個更大哪一個更小,因此此次比較的兩個結果就是機率均等的了。具體參考這裏


code:由於使用了C++類,破壞了堆排序的原地性,可是原理不變,同時代碼中也有提示~

/*************************************************************************
* heap sort algorithm and priority queue .
* Date :    28/07/2014
* author : alex xiao
* tips : 
* 1. Two different way to build a binary heap(priority queue), max_heap_insert and 
*     build_max_heap. no need call together.
* 2. heap_extract_max can't call with heap_sort at the same time, as m_heap_size decrease.
* 3. heap sort no need use C++ class, destory in place property. you can replace m_array with
*     array, replace m_length with array_length.
*************************************************************************/
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;

template<typename T>
class binary_heap
{
public:
	binary_heap() : m_array(NULL), m_length(0), m_max_size(0) m_heap_size(0)
	{}
	binary_heap(int length);
	~binary_heap(void)
	{
		delete [] m_array;
	}
	void build_max_heap(T * array, int array_length);
	void heap_sort(T * array, int array_length);
	void maxHeapify(T * array, int n);
	void heap_dump(void);

	int heap_maximum(void);
	int heap_extract_max(void);
	int heap_increase_key(int i, T key);
	int max_heap_insert(T key);

private:
	T* m_array;
	int m_length;
	int m_max_size;
	int m_heap_size;
};


int main(void)
{
	int i = 0;
	int array[10] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
	int array_length = sizeof(array)/sizeof(array[0]);

	binary_heap<int> bh(array_length);
	for(i = 0; i < array_length; i++)
	{
		bh.max_heap_insert(array[i]);
	}

	cout << "Before sort : " << endl;
	bh.heap_dump();

	bh.heap_sort(array, array_length);

	return 0;
}

template<typename T>
binary_heap<T>::binary_heap(int length)
{
	m_array = new T[length];
	m_length = 0;
	m_heap_size = 0;
	m_max_size = length;
}

/******************************************************************************
* runs in O(lgn) time, sorts an array in place.
* tips: heap sort no need use C++ class, destory in place property. you can replace m_array with
*     array, replace m_length with array_length.
******************************************************************************/
template<typename T>
void binary_heap<T>::heap_sort(T * array, int array_length)
{
	int i = 0;
	int heap_size = m_length;
	build_max_heap(m_array, m_length);

	cout << "After build_max_heap : " << endl;
	heap_dump();

	for(i = m_length-1; i>=0; i--)//數組中下標從0  -  n-1
	{
		int temp = m_array[0];
		m_array[0] = m_array[i];
		m_array[i] = temp;
		m_heap_size--;
		maxHeapify(m_array, 1);//在堆中,堆頂元素下標從1開始
	}

	cout << "After Sort : " << endl;
	heap_dump();
}

/******************************************************************************
* runs in linear time, produces a max-heap from an unordered input array
******************************************************************************/
template<typename T>
void binary_heap<T>::build_max_heap(T * array, int array_length)
{
	int i = 0;
	m_heap_size = array_length;

	for(i = array_length/2 ; i>=1 ; i--)//注意i的取值,堆的高度從1  -  n/2
	{
		maxHeapify(array, i);
	}
}

/******************************************************************************
* runs in O(lgn) time, the key to maintaining the max-heap property.
******************************************************************************/
template<typename T>
void binary_heap<T>::maxHeapify(T * array, int temp)
{
	int largest = 0;//以temp爲頂點的子樹的堆頂
	int l = temp << 1;//求以temp爲頂點的子樹左兒子
	int r = (temp << 1) + 1;//求以temp爲頂點的子樹右兒子

	if(l <= m_heap_size && array[l-1] > array[temp-1])//首先判斷左兒子是否存在,即l<=heap_size
	{
		largest = l;
	}
	else
	{
		largest = temp;
	}

	if(r <= m_heap_size && array[r-1] > array[largest-1])//首先判斷右兒子是否存在,即r<=heap_size
	{
		largest = r;
	}

	if(largest != temp)
	{
		int t = array[temp-1];
		array[temp-1] = array[largest-1];
		array[largest-1] = t;
		maxHeapify(array, largest);//調整爲大頂堆
	}
}

/******************************************************************************
* returns the element of A with the largest key.
******************************************************************************/
template<typename T>
int binary_heap<T>::heap_maximum(void)
{
	if(m_length < 1)
	{
		cout << "array underflow !" << endl;
		return -1;
	}

	return m_array[0];
}

/******************************************************************************
* removes and returns the element of A with the largest key.
* Roughly same with heap_sort if extract all nodes. 
* Can't call with heap_sort at the same time, as m_heap_size will decrease.
******************************************************************************/
template<typename T>
int binary_heap<T>::heap_extract_max(void)
{
	int max = 0;

	if(m_heap_size < 1)
	{
		cout << "array underflow !" << endl;
		return -1;
	}

	max = m_array[0];
	m_array[0] = m_array[m_heap_size -1];
	m_heap_size--;
	maxHeapify(m_array, 0);

	return max;
}

/******************************************************************************
* increases the value of element i's key to the new value key, which is assumed to be at least 
* as large as i's current key value.
* repeatedly compares an element to its parent, exchanging their keys and continuing if the 
* element's key is larger, and terminating if the element's key is smaller, since the max-heap 
* property now holds.
* roughly same with maxHeapify.
******************************************************************************/
template<typename T>
int binary_heap<T>::heap_increase_key(int i, T key)
{
	int tmp = 0;
	int parent = i >> 1;

	if(key < m_array[i-1])
	{
		cout << "new key is smaller than current key." << endl;
		return 0;
	}

	m_array[i-1] = key;
	while( (i >= 1) && (parent >= 1) && (m_array[parent-1] < m_array[i-1]) )
	{
		tmp = m_array[i-1];
		m_array[i-1] = m_array[parent-1];
		m_array[parent-1] = tmp;

		i = parent;
		parent = i >> 1;
	}

	return 0;
}

/******************************************************************************
* INSERT operation.The procedure first expands the max-heap by adding to the tree a new leaf 
* whose key is -1. Then it calls HEAP-INCREASE-KEY to set the key of this new node to its 
* correct value and maintain the max-heap property.
* roughly same with build_max_heap.
******************************************************************************/
template<typename T>
int binary_heap<T>::max_heap_insert(T key)
{
	m_length++;
	m_heap_size++;

	if(m_max_size < m_length)
	{
		cout << "max heap is overflow ~" << endl;
		return -1;
	}

	m_array[m_heap_size-1] = -1;
	heap_increase_key(m_heap_size, key);

	return 0;
}

/******************************************************************************
* just for test, dump array infor
******************************************************************************/
template<typename T>
void binary_heap<T>::heap_dump(void)
{
	int i = 0;

	for(i = 0; i < m_length; i++)
	{
		cout << m_array[i] << "  ";
	}

	cout << endl;
}


待優化的地方有:

一、在進行堆排序時,每次取出堆頂元素,用最後一個元素替換成新堆頂,這時新堆頂明顯會小於以前堆頂的兩個子節點,因此是必然須要調整堆,會浪費很多時間,能夠優化成從原來堆頂兩個子節點中選取最大值做爲新堆頂。

有任何問題,還請不吝指出謝謝~


reference:

Introduction to algorithms(算法導論第三版)

http://zh.wikipedia.org/wiki/%E5%A0%86%E6%8E%92%E5%BA%8F

http://zh.wikipedia.org/wiki/%E5%A0%86_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)

http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/

相關文章
相關標籤/搜索