簡介
堆對於排序演算法是一個比較常用的資料結構,下面我就使用Java語言來實現這一演算法
首先,我們需要知道堆的資料結構的形式,其實就是一個特殊的二叉樹。但是這個二叉樹有一定的特點,除了是完全二叉樹以外,對於最大堆而言,堆頂元素的值是最大的,而且對於堆的每一個子樹也是一個小一號的最大堆;同樣對於最小堆,性質相反就可以了。
我以最大堆為例:
要實現堆的初始化操作,就是先按照給定的元素建立一棵完全二叉樹,然後從末尾節點進行不斷地調整的過程。調整的原則是:比較要進行放置的當前節點與其父節點的數值的大小,若要進行放置的當前節點的值小於其父節點,那麼當前節點所在位置符合最大堆的定義,要進行放置的當前節點放在此處是比較合適的;如果要進行放置的當前節點的值大於其父節點的值,那說明放在當前節點是不合適的,那麼就需要將當前節點的值與其父節點的值進行交換,然後原父節點變為新的要進行放置的當前節點。迴圈比較;終止條件就是當前節點沒有父節點,但此時的調整也許並沒有結束,我們只需要讓堆頂元素為要插入的值即可。至此,最大堆的插入和調整過程結束。
代碼如下:
public boolean insert(int x){ if(currentSize==MAXSIZE){ System.out.println("Sorry,this heap is full!"); return false; } //如果堆不滿的話 currentSize++; int flag=currentSize-1; while(flag>0){ int parent=(flag-1)/2; if(heap[parent]>x){ heap[flag]=x; return true; }else{ heap[flag]=heap[parent]; flag=parent; } } heap[0]=x; return true; }
siftDown過程:給定一個節點的位置,對其進行調整,使之符合最大堆的定義,這個過程就是我們要實現的過程。調整原則如下:
對於當前節點i而言,其孩子節點的下標滿足左節點為2*i+1,右節點為2*i+2;在進行調整的過程中,只需要比較當前節點與其子節點中最大的節點進行調整即可。具體的代碼邏輯可在代碼中看到:
public void siftDown(int flag){ int want=flag; int x=heap[flag]; while(want<currentSize){ int lChild=2*want+1; int rChild=2*want+2; int MAXChildNumber; if(lChild>currentSize){ //沒有孩子節點 heap[want]=x; }else{ //有兩個孩子節點 if(lChild<currentSize){ MAXChildNumber=heap[lChild]>heap[rChild]?lChild:rChild; }else{ MAXChildNumber=lChild; } if(heap[MAXChildNumber]<x){ heap[want]=x;return; }else{ heap[want]=heap[MAXChildNumber]; want=MAXChildNumber; } } } }
堆頂元素的刪除,我們對堆的操作基本桑就是為了獲得這個堆的最值,那麼毫無疑問,堆頂元素就是我們要研究的對象。下面是代碼邏輯:
public int deleteTop(){ if(currentSize<0){ System.out.println("Sorry, this heap is empty!"); return -1; } int target=heap[0]; int substitute=heap[currentSize-1]; this.currentSize--; heap[0]=substitute; siftDown(0); return target; }
下面是詳細的代碼
package test.maxHeap;public class MaxHeap { private int []heap ; private int currentSize; private static int MAXSIZE ; public MaxHeap(int n){ heap=new int[n]; currentSize=0; MAXSIZE=n; } public boolean insert(int x){ if(currentSize==MAXSIZE){ System.out.println("Sorry,this heap is full!"); return false; } //如果堆不滿的話 currentSize++; int flag=currentSize-1; while(flag>0){ int parent=(flag-1)/2; if(heap[parent]>x){ heap[flag]=x; return true; }else{ heap[flag]=heap[parent]; flag=parent; } } heap[0]=x; return true; } public void siftDown(int flag){ int want=flag; int x=heap[flag]; while(want<currentSize){ int lChild=2*want+1; int rChild=2*want+2; int MAXChildNumber; if(lChild>currentSize){ //沒有孩子節點 heap[want]=x; }else{ //有兩個孩子節點 if(lChild<currentSize){ MAXChildNumber=heap[lChild]>heap[rChild]?lChild:rChild; }else{ MAXChildNumber=lChild; } if(heap[MAXChildNumber]<x){ heap[want]=x;return; }else{ heap[want]=heap[MAXChildNumber]; want=MAXChildNumber; } } } } public int deleteTop(){ if(currentSize<0){ System.out.println("Sorry, this heap is empty!"); return -1; } int target=heap[0]; int substitute=heap[currentSize-1]; this.currentSize--; heap[0]=substitute; siftDown(0); return target; }}
好了,編碼已經完成。下面我們就要檢驗一下是否正確吧。
public class MaxHeapTest { public static void main(String []args){ MaxHeap maxHeap=new MaxHeap(7); for(int i=1;i<=7;i++){ maxHeap.insert(i); } for(int i=0;i<7;i++){ System.out.print(maxHeap.deleteTop()+" "); } System.out.println("\n"); }}
接下來是程式的運行結果:
7 6 5 4 3 2 1 //可見,對於最大堆,刪除堆頂的操作實際上就是完成了對堆的排序任務,也證明了我們的代碼是正確的
總結:
堆的操作很重要,我們更要學會對於堆的應用,這樣的資料結構才能使得程式的運行更加的高效和流暢。對於最小堆,我們只需要在插入方法,sift方法內稍加修改即可(也就是將值的代銷變換關係進行調整)。這樣就同樣能實現最小堆的建立和相關的操作了。
代碼中可能存在不太恰當地地方,希望大家予以批評指正,期待與你們共同進步。