1. 程式人生 > >資料結構--堆的實現(上)

資料結構--堆的實現(上)

1,堆是什麼?

堆的邏輯結構是一顆完全二叉樹,但物理結構是順序表(一維陣列)。同時,此處的堆不要與JAVA記憶體分配中的堆記憶體混淆。這裡討論的是資料結構中的堆。

2,陣列實現堆的優勢及特點

由於堆從邏輯上看是一顆完全二叉樹,因此可以按照層序遍歷的順序將元素放入一維陣列中。注意為了方便,陣列的元素存放從索引 1 處開始(不是0)。採用陣列來存放就很容易地找到某個結點 i 的雙親結點(i/2),孩子結點(左孩子:2i,右孩子:2i+1)

3,基於陣列的堆的實現需要哪些結構?

private T[] heap;//用來儲存堆元素的陣列
private int lastIndex;//最後一個元素的索引
private static final int DEFAULT_INITIAL_CAPACITY = 25;

首先需要一個一維陣列heap來儲存堆中的元素;其次,需要lastIndex標記堆中最後一個元素的索引,這樣也知道了堆中存放了多少個元素;最後,需要一個final靜態變數定義預設構造堆的大小。

4,JAVA中基於一維陣列的堆的實現具體程式碼分析

①建立堆,假設有N個元素,需要將這N個元素構建大頂堆,有兩種方法來建立堆。一種是通過add()方法,另一種是通過reheap()方法。現在分別討論如下:

對於add方法:當要向堆中新增新元素時,呼叫該方法完成新增元素的操作。那麼對這N個元素逐一呼叫add方法,就可以將這N個元素構造成大頂堆,此時的時間複雜度為O(nlogn)。add方法的程式碼如下:

public void add(T newEntry) {
        lastIndex++;
        if(lastIndex >= heap.length)
            doubleArray();//若堆空間不足,則堆大小加倍
        int newIndex = lastIndex;//從最後一個元素開始逐漸向上與父結點比較
        int parentIndex = newIndex / 2;
        heap[0] = newEntry;//哨兵
        while(newEntry.compareTo(heap[parentIndex]) > 0){
            heap[newIndex] 
= heap[parentIndex]; newIndex = parentIndex; parentIndex = newIndex / 2; } heap[newIndex] = newEntry; }

假設樹中有n個元素,則完全二叉樹高為logn,呼叫add方法的時間複雜度為O(logn)。向堆中插入新元素的具體操作如下:首先將元素陣列的最後一個位置,然後從該位置起向上調整,直至到根結點為止。

1

如圖,當插入新的紅色結點時,首先將它放在堆的末尾,然後進行再次堆調整(紅色箭頭所指)。

對於使用reheap方法來建立堆:N個元素邏輯上組成一顆完全二叉樹,從它的最後一個非葉子結點開始,逐漸向前調整,直至調整到根結點。對於每個被調整的結點,將以該結點為根的子樹調整成一個(子)堆。假設有8個元素的陣列,將將會從第4個元素起,開始進行堆調整(呼叫reheap方法),直至調整到第 1 個元素為止。該方法建堆的時間複雜度為O(n)

reheap方法的程式碼如下:

/*
     * @Task:將樹根為rootIndex的半堆調整為新的堆,半堆:樹的左右子樹都是堆
     * @param rootIndex 以rootIndex為根的子樹
     */
    private void reheap(int rootIndex){
        boolean done = false;//標記堆調整是否完成
        T orphan = heap[rootIndex];
        int largeChildIndex = 2 * rootIndex;//預設左孩子的值較大
        //堆調整基於以largeChildIndex為根的子樹進行
        while(!done && (largeChildIndex <= lastIndex)){
            //largeChildIndex 標記rootIndex的左右孩子中較大的孩子
            int leftChildIndex = largeChildIndex;//預設左孩子的值較大
            int rightChildIndex = leftChildIndex + 1;
            //右孩子也存在,比較左右孩子
            if(rightChildIndex <= lastIndex && (heap[largeChildIndex].compareTo(heap[rightChildIndex] )< 0))
                largeChildIndex = rightChildIndex;
        //    System.out.println(heap[largeChildIndex]);//這裡有問題。。使用建構函式建立時reheap。。。。。
            if(orphan.compareTo(heap[largeChildIndex]) < 0){
                heap[rootIndex] = heap[largeChildIndex];
                rootIndex = largeChildIndex;
                largeChildIndex = 2 * rootIndex ;//總是預設左孩子的值較大
            }
            else//以rootIndex為根的子樹已經構成堆了
                done = true;
        }
        heap[rootIndex] = orphan;
    }

3

2

第4個元素就是最後一個非葉子結點。(紅色箭頭表示需要進行堆調整的結點)

兩種建堆方法的比較:

add方法合適於動態建堆,也就是說,來一個元素,呼叫add方法一次,再來一個元素,再呼叫add方法……直至構造了一個N個元素的堆。而對於reheap方法,它是先給定了N個元素,這N個元素表示成一顆完全二叉樹的形式,然後從樹的最後一個非葉子結點開始,依次往前調整,進而構造堆。

add方法的調整與reheap方法的調整是不相同的。add方法的整個調整過程如下:該元素被新增到了陣列最後一個位置lastIndex,然後,lastIndex與 (lastIndex / 2) 比較……即它總是與它的雙親結點比較。這一個元素調整完後,再來下一個元素,同樣先將它放到陣列最後,再與它的雙親比較……調整的方向是自下而上的。

reheap方法的調整過程如下:如上圖,第4號結點與它的孩子(第8號結點)比較,進行調整,使之滿足堆的定義。再接著是第3號結點與它的兩個孩子比較,進行調整。再接著是第2號結點與它的孩子比較(第4,5號結點),若有必要還得與第8號結點比較,……調整的方向是自上而下的。(上面已經提到,即 以從第4號結點開始,對該結點為根的子樹進行調整,將該子樹調整成一個堆)。

5,堆排序演算法的實現

對於一個排序演算法而言,首先得有一組可比較的資料拿來給你排序。故假設排序演算法需要一個裝有待排序資料的一維陣列 arr。這裡就有兩種方法來實現排序:

❶:根據待排序的資料(一維陣列 arr)建立一個堆,由於這裡可以採用第二種建堆方法(reheap方法),故建堆的時間複雜度為O(n);空間複雜度也為O(n),因為建立的堆本質上是個一維陣列,它就是由 n 個待排序資料組成的。

ArrayMaxHeap<Integer> heap = new ArrayMaxHeap<Integer>(arr);

然後,從 heap 中刪除元素時,將刪除的元素按順序放回到陣列arr中,直至將heap中的元素刪除完畢後全部放回到陣列arr中後,陣列arr就變成了有序的了。(堆的性質保證了每次從堆中刪除元素時總是刪除堆中最大的元素(即最大堆的堆頂元素))。由於每次從堆中刪除元素的時間複雜度為O(logn) ,故整個堆排序的時間複雜度為O(nlogn)、空間複雜度為O(n)。

❷: 第二種堆排序的實現方法如下:

還是根據給定的一維陣列arr 通過反覆呼叫reheap方法來建立堆。但是,是在arr上建立堆,而不是新開闢一個數組來建立堆。這樣,使得整個排序過程的空間複雜度為O(1)

由於是直接在 陣列arr 上建立堆,故堆建立的索引是從0開始,而不是從1開始了。

在arr陣列上建堆後,arr陣列中元素的順序就是符合堆定義的順序了(完全二叉樹中父結點的值比孩子結點的值要大)且第一個元素為最大元素;因為第一個元素為堆頂元素。因此,可以把 陣列arr 中的第一個元素與最後一個元素交換,這樣 arr 的前 n-1 個元素就構成了一個半堆(樹根的左右子樹都是堆稱為半堆),這樣就可以進行reheap操作將前n-1個元素重新調整成堆。

下圖就是一個半堆,根結點20的左右子樹都是堆,但整個完全二叉樹不是堆。

12

接著,又將第一個元素與倒數第二個元素交換,剩下的前n-2個元素構成了一個半堆,又進行reheap操作將之調整為堆……反覆執行上述步驟即可將arr排序。

由於該方法是直接在arr上建堆並排序的。故空間複雜度為O(1),而每次執行reheap方法的時間複雜度為O(logn),共有n個元素,需要執行n次reheap,故整個堆排序的時間複雜度為O(nlogn),空間複雜度為O(1)。

關於時間複雜度的分析 有個小問題:第一次reheap方法需要調整的半堆有n-1個元素,第二次reheap方法調整的半堆有n-2個元素……,也就是說,每次reheap所需要執行的調整次數是越來越少的。但總的時間複雜度還是O(nlogn)了。

相關推薦

資料結構-實現優先佇列(java)

佇列的特點是先進先出。通常都把佇列比喻成排隊買東西,大家都很守秩序,先排隊的人就先買東西。但是優先佇列有所不同,它不遵循先進先出的規則,而是根據佇列中元素的優先權,優先權最大的先被取出。這就很像堆的特徵:總是移除優先順序最高的根節點。 重點:優先順序佇列,是要看優先順序的,

資料結構--實現()

1,堆是什麼? 堆的邏輯結構是一顆完全二叉樹,但物理結構是順序表(一維陣列)。同時,此處的堆不要與JAVA記憶體分配中的堆記憶體混淆。這裡討論的是資料結構中的堆。 2,陣列實現堆的優勢及特點 由於堆從邏輯上看是一顆完全二叉樹,因此可以按照層序遍歷的順序將元素放入一維陣列中。注意為了方便,陣列的元素存

的python實現及其應用 資料結構--實現之深入分析

堆的概念 優先佇列(priority queue)是一種特殊的佇列,取出元素的順序是按照元素的優先權(關鍵字)大小,而不是進入佇列的順序,堆就是一種優先佇列的實現。堆一般是由陣列實現的,邏輯上堆可以被看做一個完全二叉樹(除底層元素外是完全充滿的,且底層元素是從左到右排列的)。 堆分為最大堆和最小堆,最大堆

資料結構&&heap&priority_queue&實現

目錄 什麼是堆? 大根堆 小根堆 堆的操作 什麼是堆? 堆是一種資料結構,可以用來實現優先佇列 大根堆 大根堆,顧名思義就是根節點最大。我們先用小根堆的建堆過程學習堆的思想。 小根堆 下圖為小根堆建堆過程 堆的操作 上浮 下沉 插入 彈出 取頂 堆排序 STL heap 所在庫 #include

資料結構--實現之深入分析

一,介紹 以前在學習堆時,寫了兩篇文章:資料結構--堆的實現(上)   和   資料結構--堆的實現(下),  感覺對堆的認識還是不夠。本文主要分析資料結構 堆(討論小頂堆)的基本操作的一些細節,比如 insert(插入)操作 和 deleteMin(刪除堆頂元素)操作的實現細節、分析建堆的時間複雜度、堆的

資料結構--實現(下)

1,堆作為優先順序佇列的應用 對於普通佇列而言,具有的性質為FIFO,只要實現在隊頭刪除元素,在隊尾插入元素即可。因此,這種佇列的優先順序可視為按 時間到達 的順序來衡量優先順序的。到達得越早,優先順序越高,就優先出佇列被排程。 更一般地,元素 不能單純地按時間到來的先後來分優先順序(或者說插入的順序),

資料結構實現以及的面試題

堆 堆的特點 堆有大堆和小堆之分 小堆: ①任意結點的關鍵碼均小於等於他的左右孩子的關鍵碼 ②位於堆頂的結點的關鍵碼最小 ③從根結點到每個結點的路徑上陣列元素組成的序列都是遞增的 大堆: ①任意結點的關鍵碼均大於等於他的左右孩子的關鍵碼

資料結構Java實現(6/11)(二叉&優先佇列)

堆其實也是樹結構(或者說基於樹結構),一般可以用堆實現優先佇列。 二叉堆 堆可以用於實現其他高層資料結構,比如優先佇列 而要實現一個堆,可以藉助二叉樹,其實現稱為: 二叉堆 (使用二叉樹表示的堆)。 但是二叉堆,需要滿足一些特殊性質: 其一、二叉堆一定是一棵完全二叉樹 (完全二叉樹可以用陣列表示,見下面)

使用C#實現資料結構

一、 堆的介紹:   堆是用來排序的,通常是一個可以被看做一棵樹的陣列物件。堆滿足已下特性:   1. 堆中某個節點的值總是不大於或不小於其父節點的值   任意節點的值小於(或大於)它的所有後裔,所以最小元(或最大元)在堆的根節點上(堆序性)。堆有大根堆和小根堆,將根節點最大的堆叫做最大堆或大根堆,根節點最小

基本資料結構――的基本概念及其操作

     轉載自:https://www.cnblogs.com/JVxie/p/4859889.html,同時感謝大佬的分析       在我剛聽到堆這個名詞的時候,我認為它是一堆東西的集合       但其實吧它是利用

redis 系列5 資料結構之字典()

原文: redis 系列5 資料結構之字典(上) 一. 概述   字典又稱符號表(symbol table),關聯陣列(associative array), 對映(map),是一種用於儲存鍵值對(key-value pair)的抽象資料結構。在字典中,一個key和一個value進行關聯稱為鍵值對。在字典

關於資料結構 棧 樹 以及記憶體分配中的

在現如今的教材中 關於棧,堆,樹等概念比較模糊 正確的解釋如下 棧是一種資料表 操作滿足先進後出(類似木桶) 【標準解釋:只能從表的固定一端對資料進行插入與刪除操作,另一端封死。開頭的一端為棧頂,封死的一端為棧底】 樹: 樹的邏輯結構:樹中任何結點都可以有零個或多個直接後繼節點,但至

資料結構實現一個棧要求實現Push(出棧)Pop(入棧)Min(返回最小值)的時間 複雜度為O(1)

文章目錄 思路 MinStack.h MinStack.c Test.c 棧的基本實現: https://blog.csdn.net/weixin_41892460/article/details/8297385

資料結構————(TopK、

堆是資料結構的一種,可用於排序和海量資料處理中的TopK問題 堆的邏輯結構:是一顆完全二叉樹,由於是是一顆完全二叉樹我們就可通過陣列來實現他的儲存方式。 上面是一顆完全二叉樹,分別為樹狀儲存、陣列儲存。 堆的性質: 1.堆分為大堆和小堆。 2.大堆的對頂大於它的左子樹和右子樹(小堆相

Java HashMap涉及的資料結構實現

提供的功能 基於雜湊表實現的Map; 非執行緒安全的Map實現; 鍵和值都可以為null(因為有處理null的情形); 基本操作get()和put()的時間消耗是固定的; 資料儲存結構會隨著HashMap的數量而變換成不同的資料結構。 涉及到的概念 預設初始化容量 最大容量 預設的負載係數(load f

C# 利用執行緒安全資料結構BlockingCollection實現多執行緒

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Danny.Infrastructure.Helper; names

C 資料結構

引言 - 資料結構堆   堆結構都很耳熟, 從堆排序到優先順序佇列, 我們總會看見它的身影. 相關的資料太多了, 堆 - https://zh.wikipedia.org/wiki/%E5%A0%86%E7%A9%8D 無數漂亮的圖片接二連三, 但目前沒搜到一個工程中可以舒服用的程式碼庫. 本文由此痛點

圖的鄰接矩陣資料結構程式碼實現

鄰接 矩陣(Adjacency Matrix)是表示頂點之間相鄰關係的矩陣。設G=(V,E)是一個圖,其中V={v1,v2,…,vn} [1]   。G的鄰接矩陣是一個具有下列性質的n階方陣: ①對 無向圖而言,鄰接矩陣一定是對稱的,而

8皇后以及N皇后演算法探究,回溯演算法的JAVA實現,非遞迴,資料結構“棧”實現

是使用遞迴方法實現回溯演算法的,在第一次使用二維矩陣的情況下,又做了一次改一維的優化 但是演算法效率仍然差強人意,因為使用遞迴函式的緣故 下面提供另一種回溯演算法的實現,使用資料結構”棧“來模擬,遞迴函式的手工實現,因為我們知道計算機在處理遞迴時的本質就是棧 時間複雜度是一樣的,空間

面試常考的資料結構Java實現

1、線性表 2、線性連結串列 3、棧 4、佇列 5、串 6、陣列 7、廣義表 8、樹和二叉樹 二叉樹:每個結點至多隻有兩棵子樹(即二叉樹中不存在度大於2的結點),並且,二叉樹的子樹有左右之分,其次序不能任意顛倒。 二叉樹的性質:   性質1:在二叉樹的第