數據結構與算法-排序(六)堆排序(Heap Sort)

摘要java

堆排序須要用到一種數據結構,大頂堆。大頂堆是一種二叉樹結構,本質是父節點的數大於它的左右子節點的數,左右子節點的大小順序不限制,也就是根節點是最大的值。數據結構

這裏就是不斷的將大頂堆的根節點的元素和尾部元素交換,交換到大頂堆沒有能夠被交換的元素爲止。後面再說大頂堆的邏輯。code

邏輯

首先將序列經過大頂堆排序。而後不斷的從堆中取出頂部元素放在尾部,直到大頂堆元素爲空。排序

流程

  1. 對序列進行原地建堆操做
  2. 重複下面操做,直到堆元素數量爲 1
    1. 交換堆頂元素與尾元素
    2. 堆的元素數量減 1
    3. 對 0 位置進行 1 次 自下而上的下濾

下面在代碼中解釋原地建堆自下而上的下濾這兩個詞的邏輯。索引

實現

首先進行原地建堆。原地建堆是先將序列按照大頂堆的排序邏輯處理序列。element

大頂堆的序列邏輯是父節點的值大於它的左右子節點的值,能夠想象成一個二叉樹。這裏的原地排序用到了siftDown方法,並且在循環中只循環到序列一半數量,爲何?這個在下面看siftDown方法時詳細探究一下。class

// 原地建堆
// 自下而上的下濾
heapSize = array.length;
for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
	siftDown(i);
}

交換堆頂和尾部元素,而後將須要比較的序列元素數量減小1,並將要進行比較的序列再使用siftDown方法過濾,保持序列的大頂堆的性質。而後繼續開始的交換,直到能夠比較的序列數量爲 1 就截止。二叉樹

while (heapSize > 1) {
	// 交換堆頂元素和尾部元素
	swap(0, --heapSize);
		
	// 對 0 位置進行 siftDown(恢復堆的性質)
	siftDown(0);
}

大頂堆的 siftDown 方法

這裏來探究一下siftDown(下濾)。循環

二叉樹的父節點和子節點的關係符合這樣的公式方法

  • leftChilder = partner * 2 + 1
  • rightChilder = parnter * 2 + 1 + 1
  • half (葉子)節點的數量是總節點數量的 1/2

siftDown 方法主要是將 index 位置上的元素放在合適的位置上。那麼什麼位置是合適的位置呢

依據大頂堆的父節點值大於左右子節點的值的性質來看,只要是保證 index 位置的元素大於它的左右子節點就好。

看下面代碼,若是 index < half 才進行循環比較,那麼就有一個問題,index >= half 爲何不用比較

這就要提到很巧妙的點,首先看大頂堆的性質,左右子節點沒有具體順序的要求,其次子節點的值小於父節點。那麼就能夠依據二叉樹的葉子節點性質,若是index的位置是在葉子節點位置,那麼就原本比它的父節點要小,就不用比較(這個是創建在序列原本符合大頂堆的順序,出現一個位置的元素有變化時進行的過濾處理)。

這也是上面的原地排序中,只從一半的位置開始,是由於從這個位置開始,確定會給它的子節點比較,過濾出大的,並放在合適位置。

代碼中有三個巧妙的點

  1. 循環從序列的一半位置開始比較,若是位置不在前半部分,就不進行比較,這個在上面分析過
  2. 在比較的時候,獲取到它左右子節點中最大的節點比較。在獲取右子節點的時候看右子節點是否存在rightIndex<heapSize。由於大頂堆是符合徹底二叉樹的(儘可能往左子樹安排元素)。
  3. 說是二叉樹,可是沒有實際的節點,仍是一個線性序列,經過公式來獲取左右子樹的位置,這個就是心中有樹,沒有樹也是樹
/*
 * 讓 index 位置的元素下濾
 */
private void siftDown(int index) {
	E element = array[index];

	int half = heapSize >> 1; // 取出非葉子節點
	// 第一個葉子結點的索引 == 非葉子節點的數量
	// 必須保證 index 是非葉子節點
	while (index < half) {
		// index 的節點有2種狀況
		// 一、只有左子節點
		// 二、同時有左右子節點
			
		// 默認左子節點跟它進行比較
		int childIndex = (index << 1) + 1;
		E child = array[childIndex];
		// 右子節點
		int rightIndex = childIndex + 1;
		if (rightIndex < heapSize && cmp(array[rightIndex], child) > 0) {
			child = array[ childIndex = rightIndex];
		}
			
		if (cmp(child, element) < 0) break;
			
		// 將子節點存放到index位置
		array[index] = child;
		// 從新設置 index
		index = childIndex;
	}
	array[index] = element;
}

時間和空間複雜度

  • 最好、平均時間複雜度:O(nlogn)
  • 最壞時間複雜度:O((nlogn)
  • 空間複雜度:O(1)
  • 屬於不穩定排序

題外話

此次的排序用到了二叉樹大頂堆的一些知識,可能看下來有諸多疑問,這裏就先請諸位看官有個印象,後續我會分享二叉樹的知識,而後在回過頭來看堆排序,會讓你思路大開。

相關文章
相關標籤/搜索