Java實現堆的封裝,進行插入,調整,刪除堆頂以完成堆排序例項
簡介
堆對於排序演算法是一個比較常用的資料結構,下面我就使用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方法內稍加修改即可(也就是將值的代銷變換關係進行調整)。這樣就同樣能實現最小堆的建立和相關的操作了。
程式碼中可能存在不太恰當地地方,希望大家予以批評指正,期待與你們共同進步!