要弄清楚堆排序,我們首先要懂得以下兩點:
1)邏輯上的結構,怎麼樣才是乙個堆。
2)儲存上的結構,乙個堆儲存起來的結構是怎麼樣的。
一般來講,堆排序中的「堆」指的是二叉堆,一種完全二叉樹的結構,每個父結點最多只有兩個子結點,且滿足兩點:
1)父結點總是大於(小於)其兩個子結點(大於的,我們叫最大堆,小於的,我們叫最小堆)。
2)父結點的左右子樹也都滿足上面1)的規則,也就是說其左右子樹也是乙個最大堆或者是最小堆。
樹結構,我們可以看下圖:
一般來說,演算法中提到的堆結構都是用陣列來儲存的,所以上面的這個二叉樹放在陣列中就是
從上面我們可以看到,按順序從上到下,從左到右儲存,比如「1」存在a[0],則其左子結點則存在a[2*0 + 1]=a[1]的位置上,其右子結點則存在a[2*0 + 2] = a[2]的位置上,而"2"的左結點是4,是存在a[2*1+1] = a[3]的位置上,其右結點5,則是在跟著的位置上,所以我們可以發現如果父結點的位置是i,則其左右子結點的位置分別2*i + 1 和 2*i + 2,這其實是因為完全二叉樹就是2倍2倍往下變大的。
int lchild = 2 * i + 1;
int rchild = 2 * i + 2;
不過要注意,這只是將根節點放在a[0]的位置上,如果將根節點放在a[1]上,則左結點應該是a[2*1],而右結點則是a[2*1 + 1] = a[3]了,也就是說左右子結點的公式就變了:
int lchild = 2 * i;
int rchild = 2 * i + 1;
接下來,我們來看一下堆排序的原理。
一開始,其實陣列是無序的,如下面一堆資料:
它是無序的,如果把它當成乙個堆,它的結構就是下面這樣,
它即不是最大堆,也不是最小堆。在這裡,假設我們把它調節成最大堆(最小堆),
1)那麼它的根結點一定是要比下面所有的子結點都大的,這樣,我們就可以拿出這堆資料中最大的那個數,比如上圖中的57。
2)我們將這個最大數從這個堆頂移走,然後再重新調整成最大堆,於是我們又跑到第1)步了,這樣,我們又拿到乙個最大數,把這個數拿走,我們將剩下的值繼續調整成最大堆。這樣,一次又一次,我們就能夠順序地將這堆資料由大到小地給拿出來了,而這其實就是乙個排序過程,也就是堆排序。
而第一步,我們就要先把這個無序的陣列給調整成乙個最大堆的結構,調整過程如下圖:
1)從第乙個非葉子結點開始,也即是7開始,我們比較它跟其左右子結點的值,將三者中最大的值給放到頂上,所以就把7跟23給互換了位置,由於子結點已經沒有子結點了,所以第乙個就結束了。
2)那麼就輪到其前面乙個結點,也即是12了,同樣的將其跟57互換位置,也結束了。
3)輪到9了,比較9跟其子結點的值,發現23比9還要大,這時候把23換上調,把9往下放,由於9還有子結點,所以就要繼續往下比較,所以就把10給放上來,而9就沉到底下了,然後這一次結束。
4)那麼就輪到根結點11了,比較發現,57比它要大,就把它跟57互換位置,而11調下來之後,其還有子結點,就要繼續比較,而44比11大,所以44往上放,而11就繼續往下放。
當所有非葉子結點都調整完了之後,建堆這個操作也就結束了,這個時候可以現,57,這個陣列中最大的值已經給放到堆頂了。
而此時在陣列中的結構也相對應地變了,如下:
這樣,我們就能夠把57跟9互換一下位置,然後拿陣列前面8個數值再去繼續調整,這是為了讓要調整的陣列永遠是從0開始,改變的只是陣列的長度,一直到最後調整的陣列長度為1,就說明調整結束了,而這個時候,陣列也已經就是有序的了。
利用最大堆,我們就可以把陣列從小到大排,反之,利用最小堆,就可以把陣列從大到小排。
下面就是根據這個原理而實現的堆排序的**了:
package com.lms;
/** *
* @author linmiansheng
* @date 2014-02-28
* */
public class heapsort
if(rchild < size && a[rchild] > a[tmp])
if(tmp != i)
} }public static void buildheap(int a,int size) }
public static void heapsort(int a) }
public static void main(string args);
helper.printarray(a);
heapsort(a);
helper.printarray(a);
}}
同樣的,我們也像前面 演算法學習(二)快速排序(上)
int a = helper.generaterandomnumbers(20000000, 100000000);
long start = system.currenttimemillis();
heapsort(a);
long end = system.currenttimemillis();
long duration = end - start;
helper.print("duration = " + duration + "\n");
而所花的時間,如下:
duration = 24573
很明顯,對於大資料量來說,其排序速度不如快速排序。 基礎演算法學習之 三 堆排序
分為兩步,建堆與維持堆的性質,首先我們要先理解堆是什麼東西.堆其實就是乙個完全二叉樹,我們可以使用順序表儲存乙個二叉樹,如下圖所示來儲存 其中分為最大堆最小堆,而最大堆就是上頭大,下頭小 最小堆則反之.明白了堆的定義我們就可以開始學習堆排序了,堆排序其實也是分為有序區與無序區,其中無序區就是我們建好...
排序演算法 三 堆排序
1.heap.class package cn.sort.heap 堆排序實現 公升序排序 author ly public class heap system.out.println 向下調整 先拿左右子樹進行比較,看誰比較大,然後再拿大的與父結點進行比較,若孩子結點比較大,則大孩子與父結點交換,...
選擇排序(三) 堆排序
這樣,還剩下兩個問題 1.如何將乙個交換後的無序區調整為大根堆 2.如何在排序之前建立那個初始的大根堆。而第二個問題是可以通過第乙個問題的解決而解決的。1.如何將乙個交換後的無序區調整為大根堆?由於進行元素交換前,無序區是乙個大根堆,即左子樹和右子樹都是大根堆,所以根節點變化後左右子樹仍然都是大根堆...