演算法學習(三)堆排序

2021-08-27 16:52:37 字數 2923 閱讀 7313

要弄清楚堆排序,我們首先要懂得以下兩點:

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.如何將乙個交換後的無序區調整為大根堆?由於進行元素交換前,無序區是乙個大根堆,即左子樹和右子樹都是大根堆,所以根節點變化後左右子樹仍然都是大根堆...