1. 程式人生 > >【ADT】第六章 堆—優先佇列

【ADT】第六章 堆—優先佇列

之前在連結串列ADT中涉及到了佇列,LInkedList通過雙鏈表實現了佇列先進先出的功能
此次提出一個優先佇列的概念,它依舊滿足佇列一端入隊一端出隊的原則,唯一的區別在於出列的工作是找出、返回並刪除優先佇列中的最小值
優先佇列中的操作僅僅限於插入和最小值,不能排序、也不能find(非最小值)
一次insert(入隊)的平均為O(1),刪除最小元(出隊)平均時間O(logN)

對優先佇列的實現:

1 不使用連結串列:連結串列在表頭以O(1)執行插入操作,遍歷連結串列刪除最小元將花費O(N)的時間
2 不使用二叉查詢樹:二叉查詢樹插入刪除都花費O(logN)的時間,但是如果刪除最小元必在左子樹,每次都刪除左子樹會破壞樹的平衡,但是最重要的是優先佇列操作僅僅對最小值的操作,二叉樹提供的確實對整個數值的排序,因此使用二叉樹有些大材小用。

為了實現優先佇列,使用堆ADT

1 二叉堆

二叉堆普遍使用於優先佇列的實現

1.1 二叉堆結構

二叉堆是一棵被完全填滿的二叉樹,這就意味著只有底層可能存在單節點,其他上層都是兩個子節點

二叉堆

如此我們可知二叉堆的性質:

1 一棵高為h的二叉堆的節點總數量為 2 h ~ 2 h+1-1,深為i(自上向下第i層)父節點數量為2i
2 一顆節點數為N的二叉堆的高位O(logN)
3 我們設定根節點的下標為1,則對於下標為i的元素其左兒子下標為2i , 右兒子下標為 2i+1,父節點下標為 i/2;如果我們設定根節點的下標為0,則對於下標為i的元素其左兒子下標為2i+1 , 右兒子下標為 2i+2,父節點下標為 (i-1)/2
4 我們設定根節點下標為1,則所有父節點的取值範圍 [1 , N/2];如果我們設定根節點下標為0,則所有父節點的取值範圍 [0 , N-1/2]

除此之外由於我們想快速找出最小元,應該讓最小元處於根位置上(如果找最大元,就讓最大元在根位置上),考慮到任意一個子樹都是一個二叉堆,我們有堆排性質:

5 為快速找出最小元,要求對二叉堆中的每一個節點X,X的值應當小於或等於自己子節點的值(如果要找最大值,則大於等於)

依據5條性質,實現二叉堆的優先佇列的操作,首先規定二叉堆的根節點下標由1開始,使用一個數組容器容納二叉堆中的節點

1.2 插入

1.2.1 實現

插入元素T
因為基於陣列的實現,所以不需要查詢,一開始要插入的位置就是陣列[length+1]的節點A位置
在此節點A上,如果父節點的值小於T,則可以直接插入
如果父節點的值大於T,則說明T應該上移到較上層的位置,此時將父節點移動到A的位置
再次基於這個父節點,比較它的父節點是否小於T。。。直到找到一個節點O,O的父節點小於T,或者O的父節點為null,此時O即為元素T的位置
(對於根的父節點我們可以用下標控制,下標>=1)

這個過程叫做 上濾
我個人對上濾的理解:上層結構是滿足條件的,從底層向上遍歷,每次和父節點作比較

    public void enqueue(T t) {
        //從下角標1 開始,父節點 i/2
        //入隊,上濾
        // 上濾:從底層向上層查詢適合t的位置,找父節點
        //如果t的父節點小於t 直接插入
        //否則把t的父節點拉下來,在t的父節點的位置上查詢
        this.size++;//先加1 這時size表示的是底層要插入的空穴i
        int i = this.size;
        for(this.heap[0] = t;i >= 1; i = i/2) { // i是父節點,向上範圍 >=1
            if(((T)this.heap[i/2]).compareTo(t) > 0){ //父節點比較大,下移
                this.heap[i] = this.heap[i/2];
            }else{
                break;
            }
        }
        this.heap[i] = t;
    }

1.2.2 時間複雜度

一次insert的平均為O(1)
最差情況:插入元素是最小值,應該上濾到堆頂,經過logN層(堆的高)比較,時間複雜度為O(logN)

1.3 刪除最小元

1.3.1 實現

出隊的操作就是刪除最小元,最小元位於堆頂,即陣列下標為1的元素
將根刪除之後要重新調整結構,同時一個元素被刪除,佇列size-1,那麼原來在陣列[size]的元素必然要移動(即最後一個元素)
既然如此,我們令陣列[1]=陣列[size],將刪除問題轉換為 為陣列[1]堆頂元素T不符合性質,為其尋找合適位置 的問題

首先在比較元素T和根的子節點大小,如果元素T比較小,那麼這個位置就是T的位置
如果元素T比較大,在子節點之間選擇最小的元素將他賦值到根節點,此時有一個子節點的位置空閒
繼續比較T和這個子節點的子節點的大小,還是和上面情況一樣,如果T小,那麼這個位置就是元素T的位置
如果T大,在子節點之間選擇一個較小者代替父節點,空出較小子節點位置。。。。
繼續比較,直至T比子節點的值小,或T的子節點為null
(子節點為null,同樣就是葉節點,用陣列下標控制,2i<=size)
這個過程叫做下濾

我個人對上濾的理解:下層結構是滿足條件的,從堆頂向下遍歷,每次和子節點作比較

 private void downSort(int index) {
        // index位置的元素不符合二叉堆特性, 下濾查詢適合他的位置並排序
        //下濾,從頂層向下查詢適合當前元素的位置,找子節點
        //下層是符合排序的
        // i的子節點 2i 2i+1
        // 如果i的子節點比i要小,把最小的子節點上移,在子節點的位置繼續向下查詢 直到i的子節點不比i小
        T t = (T)this.heap[index];//拿到這個元素
        //有兩個子節點,先找最小的子節點
        int i = index;
        int child ;//第一個子節點
        for(; i <= this.size / 2; i = child){ // i是父節點,向下 i <= size/2
            child = 2 * i;
            if(child != this.size && ((T)this.heap[child]).compareTo((T)this.heap[child+1]) > 0) { //找最小的子節點,如果只有一個節點那就是2i
                child++;
            }
            if(((T)this.heap[child]).compareTo(t) < 0) {
                //把child上移
                this.heap[i] = this.heap[child];
            }else{
                break; //否則跳出,子節點大,i就是要放的位置
            }
        }
        this.heap[i] = t;
    }

如上定義了一個通用方法,用來對index位置的元素尋找它的正確位置,刪除操作就變成了

    public T dequeue() {
        //出隊,返回刪除最小元素(root),把最後一個元素拿到root處,下濾排序
        T min = (T)this.heap[1];
        this.heap[1] = this.heap[this.size];
        this.size--;
        downSort(1);
        System.out.println(min);
        return min;
    }

1.3.2 時間複雜度

刪除最小元最差情況:放在根的元素需要下濾到最底層,經過logN層比較,時間複雜度O(logN)
而實際上,被放到根上的元素幾乎都下濾到最底層(它來自的那一層)
因此平均時間O(logN)

1.4 構建堆

構建堆,是在建構函式時接收一組陣列,將這組陣列作為堆的元素構建

首先我們想到的是將這組N個元素一一insert,每個inset平均花費O(1),那構建N個元素的堆平均花費O(N)

一般情況下的思路為:將N項以任意順序放入樹中,從最大的父節點開始上濾 執行 下濾操作

    public void buildHeap(T[] list) {
        //利用下濾,對每個i位置上不滿足序列的排序
        // 下濾要求下層是排好序的
        // 所以要從底到頂
        this.heap = new Object[this.size+DEFALUT_HEAP_SIZE];
        int i = 1;
        for(T t : list){
            this.heap[i++] = t;
        }
        for(int j = this.size/2; j >= 1; j--) {// 父節點開始--
            downSort(j);
        }
    }

父節點的取值範圍為[1,size/2],從最後一個父節點開始一一向上遍歷,每一個父節點執行在此節點上的下濾操作,為當前的父節點尋找合適的插入位置,直至根節點完成,二叉堆也就構建完成
此操作的時間複雜度的求解:
根節點操作O(logN) * 1
第一層父節點(O(logN)-1) * 2
……
第i層父節點 (O(logN)-i) * 2i
由此求和為O(N)

構建堆的時間複雜度為O(N)

1.5 擴充套件 d-堆

d-堆是對二叉堆的簡單擴充套件,它的規則與二叉堆一樣,只是所有的父節點都有d個兒子
二叉堆也可以叫做2-堆

在這裡對時間複雜度分析:
d-堆比二叉堆要淺,d-堆的高度為O(logdN)
因此一次insert的平均執行時間為O(logdN)
但是對於刪除操作,雖然堆變淺了,但是原先只需要在兩個子節點之間找最小值,比較一次;現在需要在d個子節點之間找最小值,比較d-1次
刪除操作平均花費O(d logdN),依舊為O(logN)層次

對d的討論:
d的改變對父節點、子節點的位置發生影響:(第一個節點下標為1)
第i個節點,它的第一個子節點就是i*d-d+1,最後一個子節點為i*d+1
第i個節點,它的父節點為(i-2+d)/d

為找到父子節點,會經過複雜的計算,尤其是除法運算佔據較大的執行時間,這樣會增大執行時間
除非d是2的冪,可以通過移位運算實現除法

那麼d-堆一般的應用場景:
在優先佇列太大以至於不能完全裝入主存時,可以考慮使用d-堆降低深度

2 JavaCollection實現

在Java1.5之後才實現了對優先佇列的支援:泛型類PriorityQueue

在該類中呼叫
add方法實現入隊
element實現返回最小元素但不刪除
remove實現出隊,返回並刪除最小元素

3 堆的合併

除了不能排序、不能find之外,堆ADT的一個明顯缺點是:很難將兩個堆ADT合併成一個堆ADT(這種合併操作merge)

為了實現堆的合併,引進堆ADT的優化結構:左式堆、斜堆、二項佇列

3.1 左式堆

3.2 斜堆

3.3 二項佇列

相關推薦

ADT 優先佇列

之前在連結串列ADT中涉及到了佇列,LInkedList通過雙鏈表實現了佇列先進先出的功能 此次提出一個優先佇列的概念,它依舊滿足佇列一端入隊一端出隊的原則,唯一的區別在於出列的工作是找出、返回並刪除優先佇列中的最小值 優先佇列中的操作僅僅限於插入和最小值,

練習題--有返回值的函式(Think Python)

增量式開發(incremental development): 這個過程的核心如下: 一定要用一個能工作的程式來開始,每次逐漸新增一些細小增補。在任何時候遇到錯誤,都應該弄明白錯誤的位置。 用一些變數來儲存中間值,這樣你可以顯示一下這些值,來檢查一下。 程式一旦能

組合語言——課後總結

1.在程式碼段中使用資料 assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h mov bx,0 mov ax,0 movcx,9 s:addax,cs:[bx] add bx,

ADT

樹,大部分操作的執行時間平均為O(log N) 樹是按照大小順序儲存的,不可重複的集合 Collection中基於二叉查詢樹實現了TreeSet和TreeMap類 二叉樹是基於排序的,因此需要比較,二叉樹中儲存的物件需要實現Comparable介面

算法導論排序

兩個 高度 位置 思想 n) 隊列 sigma 復雜 max 基本過程: 1、保持最大堆的性質:假設兩個子堆都滿足,只需要根節點依次換下去,復雜度O(lg n) 2、初始化堆:後半段都是葉子,在前半段從後往前,依次執行上述最大堆性質的操作,名義復雜度是O(n lg n),

軟件構造第二節 可維護的設計模式

派生 ural bridge lose 復用 部分 sed spa line 第六章第二節 可維護的設計模式 Outline 創造性模式:Creational patterns 工廠模式(Factory Pattern) 抽象工廠模式(Abstract Factory

軟件構造三節 面向可維護的構造技術

pre 協議 判斷 regex 格式 png ria 不包含 有一個 第六章第三節 面向可維護的構造技術 學了這麽多OO設計模式,不外乎都是 delegation + subtying,萬變不離其宗。 除了OO,還有什麽其他能夠提升軟件可維護性的構造技術?——本節從委派+子

.NET Core項目實戰-統一認證平臺 網關篇-自定義客戶端授權

localhost 寫入 warn seo 接口 後端 配置 rect when 【.NET Core項目實戰-統一認證平臺】開篇及目錄索引 上篇文章我們介紹了網關使用Redis進行緩存,並介紹了如何進行緩存實現,緩存信息清理接口的使用。本篇我們將介紹如何實現網關自定義客

.NET Core專案實戰-統一認證平臺 閘道器篇-自定義客戶端授權

原文: 【.NET Core專案實戰-統一認證平臺】第六章 閘道器篇-自定義客戶端授權 【.NET Core專案實戰-統一認證平臺】開篇及目錄索引 上篇文章我們介紹了閘道器使用Redis進行快取,並介紹瞭如何進行快取實現,快取資訊清理介面的使用。本篇我們將介紹如何實現閘道器自定義客戶端授權,實現可以

資料庫視訊 資料查詢和管理

一、簡單的SELECT語句 語法格式: SELECT [ALL|DISTINCT] select_list [INTO new_table] FROM table_source [WHERE search_conditions] [GROUP

計算機組成原理 中央處理器

一、主要內容: 組成原理知識點彙總與複習 授課:sunnyACT張思鵬(中城投絲路@180科技)       二、學習參考: sunnyACT張思鵬(中城投絲路@180科技)xmind使用參考: 必備工具|三分鐘帶

演算法筆記:C++標準模板庫(STL)介紹

【演算法筆記】第六章:C++標準模板庫(STL)介紹 標籤(空格分隔):【演算法筆記】 第六章:C++標準模板庫(STL)介紹 第六章:C++標準模板庫(STL)介紹 6.1 vector的常見用法詳解

SpringCloud Greenwich版本:智慧路由(zuul)

一、SpringCloud版本 本文介紹的Springboot版本為2.1.1.RELEASE,SpringCloud版本為Greenwich.RC1,JDK版本為1.8,整合環境為IntelliJ IDEA 二、zuul介紹 路由在微服務體系結構的一個組成部分。例如,/可以對映

高效能MySQL查詢效能優化 查詢優化器侷限

剛才誤關了瀏覽器,啊~~~ 6.5MySQL查詢優化器侷限性 6.5.1關聯子查詢 where子查詢實現的非常糟糕,最糟一類where包含in 優化: exists等效改寫: 或使用group_concat()在in中構造由逗號分隔的列表:【源】    

openshift 學習筆記 持續整合與部署

一. 部署 jenkins 服務下載並匯入jenkins-ephemeral-template模板# oc create -f https://raw.githubusercontent.com/ope

c++自學 記憶體高階話題

第1節  new、delete進一步認識 一、綜述與回顧:第一章第4節、第四章第2節 二、從new說起 int *p1 = new int; //初值隨機 int *p2 = new int(); //初值給0 1、new物件時加括號與否的區別 int

Java程式設計思想筆記-訪問許可權控制

要學會把變動的程式碼與保持不變的程式碼區分開來。 如果有必要,你儘可能將一切方法都定為private。 非public類在其它包中是訪問不到的。 所有預設包的類都是屬於同一個包,儘管它們在不同的資料夾下面。 private,只允許本類所有物件可訪問,其他任何類

學習筆記 訪問許可權控制

訪問許可權控制的作用 簡化客戶端程式設計師對於類庫檔案的理解,更便於對於該類的使用。不會觸及一些類設計者不希望他們觸及的部分。 便於類設計者更改類方法的實現(類內部的工作原理)。 6.1 包:庫單元 使用import關鍵字,匯入一個或多個類。 使

軟件工程 面向對象方法

執行 註意 csdn groupadd 存在 地方 ica 軟件 可執行 用戶權限的相關命令: 權限類型: 01 讀 read r 4 02 寫 write w 2 03 執行 excute x 1 組權限: 開發組:將所有開發人員添加到一個組中,這個組中所有人

Beta 次Daily Scrum Meeting

接下來 url 研究 工作 val 模塊 ref dai 團隊 【Beta】 第六次Daily Scrum Meeting 一、本次會議為第六次meeting會議 二、時間:10:00AM—10:20AM 地點:禹州樓 三、會議站立式照片 四、今日任務安