堆排序思路及非遞歸Java實現

 

參考左程雲的視頻

 

 

1.完全二叉樹的概念

在瞭解堆排序的最開始,需要明白什麼是完全二叉樹

對於這樣一棵用編號代表節點的樹,若這棵樹的節點嚴格按照圖中的順序填充(不必填滿),即稱爲完全二叉樹

也就是說,除了最後一層之外的每一層都被完全填滿,而最後一層的所有節點,都需要保持從左到右的順序

以上面這個圖舉例:若去掉節點12 13 14 15,該樹滿足完全二叉樹

但是若去掉節點8 其他不動,則不滿足

簡單的說就是從左到右的排列中,不允許出現 “插隊”的節點

 

2.大根堆的概念

首先我們要有一棵完全二叉樹,這棵完全二叉樹需要滿足以下情況:

在該完全二叉樹的任意一棵子樹中,父節點都是這棵子樹的最大值

仍用上圖舉例(圖中的數字代表序號 而不代表實際值)

在1-15號節點的樹中 1號節點應是1-15號所有節點的最大值

這棵子樹中 2號節點的值也應該是最大值

中,4號節點的值也應該是最大值

其他節點間的大小關係不作任何約束

這樣一棵 最大值爲父節點的完全二叉樹 稱爲大根堆

相反爲小根堆

 

3.由數組來“建立”大根堆

 

現在我們有數組 5 7 0 6 8

 

我們要將它搭成一個大根堆,那麼首先要將它變換成一棵完全二叉樹

這裏有一個非常重要的概念:

這棵完全二叉樹並不是真實存在的,它只是我們腦海中的排列方式

我們常見的二叉樹是這種形式來表現的:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

而此時我們需要搭建的完全二叉樹並不是這樣,它的本體仍然是數組,也就是說,樹中不同節點需要用數組下標來表示

我們仍然按照下標從0到4的順序,來填滿這棵完全二叉樹

假設父節點的下標爲 i 

那麼它的左子節點爲  2*i+1

它的右子節點爲  2*i+2

對於某個節點,它的父節點的下標即爲  (i-1)/2

 

節點間的對應關係清楚之後 我們發現這樣一棵完全二叉樹,並不符合大根堆的概念,是因爲我們並沒有按照規則來填滿完全二叉樹

正確的步驟是:

第一步:填入第一個數字 5

第二步:填入第二個數字7

此時7>5 不符合大根堆性質 我們將5與7的位置交換

注意:交換實際是發生在數組中的

之後數組變爲  7  5  0  6  8

樹變爲

第三步:填入第三個數字0

不需要交換

第四步:填入第四個數字6

需要將5和6交換 交換前數組爲:7  5  0  6  8  交換後數組即爲 7 6 0 5 8

最後一步:將8填入

8填入之後需要將6與8交換 交換前數組爲:7 6 0 5 8  交換後數組爲:7  8  0  5  6

但是交換後發現 7和8也需要交換 其實每一次交換完之後 都需要將換上去的父節點 與這個換上去的父節點的父節點進行一次對比

交換前數組:7 8 0 5 6

交換後數組: 8  7  0  5  6

這樣 由數組建立大根堆的步驟就完成了

用代碼來實現也非常簡單:

傳入數組arr 對他的每個元素進行“填入”

for(int i=0;i<arr.length;i++)
{
    heapInsert(arr,i);
}
public static void heapInsert(int[] arr,int index)
{
    //直到當前節點不再大於它的父節點
    while(arr[index]>arr[(index-1)/2])
    {
        swap(arr,index,(index-1)/2);
        index=(index-1)/2;
    }
}

 

4.由大根堆來完成排序

建完大根堆之後的數組爲  8  7  0  5  6

樹中的形式爲:

由於大根堆中父節點最大的性質,此時8一定是數組中最大的元素

由於整棵樹的父節點,在數組中的下標一定是0 所以下標爲0的這個元素即爲數組中的最大值

接下來將這個最大值,與最後一個元素交換

在這個例子中,即將8與6交換

交換後的數組爲  6  7  0  5  8

交換後:數組的最後一個數即爲數組中的最大元素,此時這個數不再參與接下來的排序

那麼如何讓最後的元素不再參與接下來的排序?

在排除之前

在生成大根堆時,是如何判斷X位置沒有元素的?

是因爲X位置 在數組中的下標爲5 而我們數組實際最大下標只到4 數組越界 所以X位置沒有值

那麼我們在將8排除後,同樣可以設置一個指針,這個指針初始指向數組實際的最大下標 即爲4

排除8後,這個指針前移一位  指向3 這個指針就是是否越界的標誌 指向3後,相當於8被排除了

此時數爲上圖

在每一次排除一個元素後,樹都應該做一次檢查,檢查是否符合大根堆的性質,若不符合則進行變換

在上圖中,元素6換到下標0位置後,顯然小於7 ,不符合大根堆性質

此時應該進行交換,通俗的做法是:

從下標0的節點開始,即從整棵樹的父節點開始

若這個父節點有左孩子,那麼去看他是否有右孩子,在父節點/左孩子/右孩子 三者中(可能不存在孩子)選擇出最大的元素的下標

若這個最大元素的下標就是父節點的下標 說明父節點是最大值 不需要調整

其他情況則交換

交換後繼續向下判斷是否符合大根堆性質 直到某個元素沒有左孩子(沒有左孩子就沒有右孩子)

本次調整完成

此時7爲最大值 與數組最後一個元素5交換 並排除  指針前移一位  指向2 數組爲 5 6  0       7  8

樹爲:

之後將5  6  0進行調整 調整後爲 6  5  0

調整完成  交換6與0 排除6  指針前移一位  指向1

此時數組爲0 5        6 7  8  此時看似排序已經完成 但是指針下標並沒有指向0 所以繼續

樹爲

調整後爲 

交換5 與 0 排除 5  此時指針下標指向0 排序完成

 

完整代碼

import java.util.List;

public class Main
{

    public static void main(String[] args)
    {
        int[] arr={5,7,0,6,8};
        heapSort(arr);
        for(int i=0;i<arr.length;i++)
        {
            System.out.println(arr[i]);
        }
    }
    public static void heapSort(int[] arr)
    {
        if(arr==null||arr.length<2)
        {
            return;
        }
        for(int i=0;i<arr.length;i++)
        {
            heapInsert(arr,i);
        }
        int size=arr.length;
        swap(arr,0,--size);
        while(size>0)
        {
            heapify(arr,0,size);
            swap(arr,0,--size);
        }
    }

    public static void heapInsert(int[] arr,int index)
    {
        //建立大根堆  每個元素往上走
        while(arr[index]>arr[(index-1)/2])
        {
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }

    public static void heapify(int[] arr,int index,int size)
    {
        int left=index*2+1;
        //有左孩子時
        while(left<size)
        {
            //在左孩子和右孩子中選出較大者
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //返回當前三個節點中最大的下標
            largest=arr[largest]>arr[index] ? largest : index;
            //若最大者是自己 則不用調整
            if(largest==index)
                break;
            //某個孩子大於我 所以交換
            swap(arr,largest,index);
            //每個元素都往下走
            index=largest;
            left=index*2+1;
        }
    }

    public static void swap(int[] nums,int i,int j)
    {
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章