【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 地點:禹州樓 三、會議站立式照片 四、今日任務安