摘要:我理解的數(shù)據(jù)結(jié)構(gòu)七堆和優(yōu)先隊(duì)列一堆堆的基礎(chǔ)堆也是一顆樹(shù)堆最為主流的一種實(shí)現(xiàn)方式二叉堆二叉堆是一顆完全二叉樹(shù)完全二叉樹(shù)完全二叉樹(shù)是效率很高的數(shù)據(jù)結(jié)構(gòu),完全二叉樹(shù)是由滿二叉樹(shù)而引出來(lái)的。
我理解的數(shù)據(jù)結(jié)構(gòu)(七)—— 堆和優(yōu)先隊(duì)列(Heap And PriorityQueue) 一、堆
1.堆的基礎(chǔ)
堆也是一顆樹(shù)
堆最為主流的一種實(shí)現(xiàn)方式:二叉堆
二叉堆是一顆完全二叉樹(shù)
2.完全二叉樹(shù)
完全二叉樹(shù)是效率很高的數(shù)據(jù)結(jié)構(gòu),完全二叉樹(shù)是由滿二叉樹(shù)而引出來(lái)的。對(duì)于深度為K的,有n個(gè)結(jié)點(diǎn)的二叉樹(shù),當(dāng)且僅當(dāng)其每一個(gè)結(jié)點(diǎn)都與深度為K的滿二叉樹(shù)中編號(hào)從1至n的結(jié)點(diǎn)一一對(duì)應(yīng)時(shí)稱之為完全二叉樹(shù)。
(通俗來(lái)說(shuō):完全二叉樹(shù)不一定是滿二叉樹(shù),當(dāng)一層已滿容納不下新的節(jié)點(diǎn)時(shí),新的一層從左至右來(lái)盛放新節(jié)點(diǎn),缺失的節(jié)點(diǎn)一定在右側(cè))
最大堆:堆中某個(gè)節(jié)點(diǎn)的值總是不大于其父節(jié)點(diǎn)的值(相應(yīng)的,可以定義最小堆)
3.用數(shù)組存儲(chǔ)二叉堆
4.基礎(chǔ)代碼實(shí)現(xiàn)
這里的ArrayNew是我之前實(shí)現(xiàn)的數(shù)組:數(shù)組代碼
public class Heap> { private ArrayNew data; public Heap(int capacity) { data = new ArrayNew<>(capacity); } public Heap() { data = new ArrayNew<>(); } // 返回堆中的元素個(gè)數(shù) public int size() { return data.getSize(); } // 堆中是否包含元素 public boolean isEmpty() { return data.isEmpty(); } // 父節(jié)點(diǎn)的索引 private int parent(int index) { if (index == 0) { throw new IllegalArgumentException("index-0 doesn"t have parent"); } return (index - 1) / 2; } // 左子節(jié)點(diǎn)的索引 private int leftChild(int index) { return 2 * index + 1; } // 右子節(jié)點(diǎn)的索引 private int rightChild(int index) { return 2 * index + 2; } }
5.添加元素(sift up)
步驟:
在最后一層的最后添加這個(gè)元素,如果是滿樹(shù),則在新的一層最左端添加
與其父節(jié)點(diǎn)做比較,如果父節(jié)點(diǎn)小于當(dāng)前元素的節(jié)點(diǎn),置換位置
以此類推,直到比較至根節(jié)點(diǎn)
// 添加元素 public void add(E e) { data.addLast(e); siftUp(data.getSize() - 1); } // 上浮 private void siftUp(int index) { // 添加的元素大于父節(jié)點(diǎn)的元素 while (index > 0 && data.get(index).compareTo(data.get(parent(index))) > 0) { data.swap(index, parent(index)); index = parent(index); } }
6.取出元素(sift down)
步驟:
最后一個(gè)節(jié)點(diǎn)與根節(jié)點(diǎn)交換,取出末尾節(jié)點(diǎn),這樣整體樹(shù)結(jié)構(gòu)不會(huì)改變,只是位置不對(duì)
根節(jié)點(diǎn)與子節(jié)點(diǎn)的元素做比較,如果比子節(jié)點(diǎn)的最大的節(jié)點(diǎn)元素小,則置換位置
以此類推,直至比子節(jié)點(diǎn)的元素都大
// 查看堆中的最大值 public E findMax() { if (data.isEmpty()) { throw new IllegalArgumentException("can"t find Max in empty heap"); } return data.get(0); } // 取出堆中的最大值 public E extractMax() { E ret = data.get(0); data.swap(0, data.getSize() - 1); data.removeLast(); siftDown(0); return ret; } // 下沉 private void siftDown(int index) { while (leftChild(index) < data.getSize()) { // 有子節(jié)點(diǎn)(左子節(jié)點(diǎn)沒(méi)有越界) int j = leftChild(index); // 有右子節(jié)點(diǎn),并且右節(jié)點(diǎn)元素大于左節(jié)點(diǎn)元素 if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) { j = j + 1; } // 此時(shí),data[j]就是左右子節(jié)點(diǎn)的最大節(jié)點(diǎn)值 if (data.get(j).compareTo(data.get(index)) <= 0) { break; } data.swap(index, j); index = j; } }
7.Heapify和replace
replace(取出堆中的最大元素,再放入一個(gè)新的元素)
實(shí)現(xiàn):可以先extractMax再add,但是這樣會(huì)有兩次O(logn)操作
優(yōu)化:可以將堆頂元素替換以后再siftDown,這樣只有一次O(logn)操作
// 取出堆中的最大元素,并替換成元素e,重新siftDown public E replace(E e) { E ret = data.get(0); data.set(0, e); siftDown(0); return ret; }
heapify(將任意數(shù)組整理成堆的形狀)
實(shí)現(xiàn):將n個(gè)元素逐個(gè)插入到一個(gè)空堆中,算法復(fù)雜度是O(logn)
優(yōu)化:heapify(算法復(fù)雜度是O(n))
將任意一個(gè)數(shù)組看成完全二叉樹(shù)(盡管元素的位置不對(duì))
找到最后一個(gè)非葉子節(jié)點(diǎn)(最后一個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn))
從最后一個(gè)非葉子節(jié)點(diǎn)倒著不斷的對(duì)每個(gè)節(jié)點(diǎn)siftDown就可以了
// heapify public Heap(E[] arr) { data = new ArrayNew<>(arr); for (int i = parent(data.getSize() - 1); i > 0; i--) { siftDown(i); } }
8. 復(fù)雜度分析
因?yàn)槎训娜〕龊吞砑訌?fù)雜度都是O(logn),所以堆的性能是很高的。
操作 | 時(shí)間復(fù)雜度 |
---|---|
add | O(logn) |
extractMax | O(logn) |
1.優(yōu)先隊(duì)列基礎(chǔ)
普通隊(duì)列:先進(jìn)先出,后進(jìn)后出
優(yōu)先隊(duì)列:出隊(duì)順序和入隊(duì)順序無(wú)關(guān),和優(yōu)先級(jí)有關(guān)
2.隊(duì)列接口
public interface Queue{ int getSize(); boolean isEmpty(); void enqueue(E e); E dequeue(); // 查看隊(duì)首元素 E getFront(); }
3.基于堆的優(yōu)先隊(duì)列代碼實(shí)現(xiàn)
public class priorityQueue> implements Queue { Heap data; public priorityQueue() { data = new Heap<>(); } @Override public int getSize() { return data.size(); } @Override public boolean isEmpty() { return data.isEmpty(); } @Override public void enqueue(E e) { data.add(e); } @Override public E dequeue() { return data.extractMax(); } @Override public E getFront() { return data.findMax(); } }
4.LeetCode中有關(guān)優(yōu)先隊(duì)列的問(wèn)題
347. 前K個(gè)高頻元素
題目:347. 前K個(gè)高頻元素
描述:給定一個(gè)非空的整數(shù)數(shù)組,返回其中出現(xiàn)頻率前 k 高的元素。
例子:
示例 1: 輸入: nums = [1,1,1,2,2,3], k = 2 輸出: [1,2] 示例 2: 輸入: nums = [1], k = 1 輸出: [1]
解決代碼:
import java.util.ArrayList; import java.util.List; import java.util.TreeMap; // 只需要在`Solution`這個(gè)類中引入所需要的類即可 // 所有的類都可以在之前的博客中找到 public class Solution { private class Freq implements Comparable{ public int e, freq; public Freq(int e, int freq) { this.e = e; this.freq = freq; } @Override public int compareTo(Freq another) { if (this.freq < another.freq) { return 1; } else if (this.freq > another.freq) { return -1; } else { return 0; } } } public List topKFrequent(int[] nums, int k) { TreeMap map = new TreeMap<>(); for (int num : nums) { if (map.containsKey(num)) { map.put(num, map.get(num) + 1); } else { map.put(num, 1); } } PriorityQueue pq = new PriorityQueue<>(); for (int key : map.keySet()) { if (pq.getSize() < k) { pq.enqueue(new Freq(key, map.get(key))); } else if (map.get(key) > pq.getFront().freq) { pq.dequeue(); pq.enqueue(new Freq(key, map.get(key))); } } ArrayList list = new ArrayList<>(); while (!pq.isEmpty()) { list.add(pq.dequeue().e); } return list; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71800.html
摘要:我理解的數(shù)據(jù)結(jié)構(gòu)七堆和優(yōu)先隊(duì)列一堆堆的基礎(chǔ)堆也是一顆樹(shù)堆最為主流的一種實(shí)現(xiàn)方式二叉堆二叉堆是一顆完全二叉樹(shù)完全二叉樹(shù)完全二叉樹(shù)是效率很高的數(shù)據(jù)結(jié)構(gòu),完全二叉樹(shù)是由滿二叉樹(shù)而引出來(lái)的。 我理解的數(shù)據(jù)結(jié)構(gòu)(七)—— 堆和優(yōu)先隊(duì)列(Heap And PriorityQueue) 一、堆 1.堆的基礎(chǔ) 堆也是一顆樹(shù) 堆最為主流的一種實(shí)現(xiàn)方式:二叉堆 二叉堆是一顆完全二叉樹(shù) 2.完全二叉樹(shù) ...
摘要:一個(gè)常見(jiàn)的例子就是優(yōu)先隊(duì)列,還有排序算法之一的堆排序。另外我們還將學(xué)習(xí)堆排序,并將使用實(shí)現(xiàn)堆。堆排序在堆排序中,我們需要用給定的值構(gòu)建一個(gè)一個(gè)堆。偽代碼如下從上面的偽代碼可以看到,堆排序的第一步就是構(gòu)建一個(gè)堆。 堆是什么? 堆是基于樹(shù)抽象數(shù)據(jù)類型的一種特殊的數(shù)據(jù)結(jié)構(gòu),用于許多算法和數(shù)據(jù)結(jié)構(gòu)中。一個(gè)常見(jiàn)的例子就是優(yōu)先隊(duì)列,還有排序算法之一的堆排序。這篇文章我們將討論堆的屬性、不同類型的堆...
摘要:項(xiàng)目地址中內(nèi)置的庫(kù)和分別提供了堆和優(yōu)先隊(duì)列結(jié)構(gòu),其中優(yōu)先隊(duì)列本身也是基于實(shí)現(xiàn)的,因此我們這次重點(diǎn)看一下。堆可以用于實(shí)現(xiàn)調(diào)度器例見(jiàn)之協(xié)程,更常用的是優(yōu)先隊(duì)列例如。 項(xiàng)目地址:https://git.io/pytips Python 中內(nèi)置的 heapq 庫(kù)和 queue 分別提供了堆和優(yōu)先隊(duì)列結(jié)構(gòu),其中優(yōu)先隊(duì)列 queue.PriorityQueue 本身也是基于 heapq 實(shí)現(xiàn)的,因...
摘要:注意因?yàn)槎阎惺擎湵砉?jié)點(diǎn),我們?cè)诔跏蓟褧r(shí)還要新建一個(gè)的類。代碼初始化大小為的堆拿出堆頂元素將堆頂元素的下一個(gè)加入堆中 Merge Two Sorted Lists 最新更新請(qǐng)見(jiàn):https://yanjia.me/zh/2019/01/... Merge two sorted linked lists and return it as a new list. The new list...
摘要:堆分為大根堆和小根堆,大根堆是父節(jié)點(diǎn)大于左右子節(jié)點(diǎn),并且左右子樹(shù)也滿足該性質(zhì)的完全二叉樹(shù)。其中表示向下取整。則一直指向,一直指向,因?yàn)槲覀冊(cè)谡{(diào)整堆結(jié)構(gòu)中實(shí)際調(diào)整的是索引數(shù)組,而不會(huì)改變真實(shí)存放數(shù)據(jù)的數(shù)組。以上就是實(shí)現(xiàn)對(duì)和索引堆的具體方式。 堆是一棵完全二叉樹(shù)。堆分為大根堆和小根堆,大根堆是父節(jié)點(diǎn)大于左右子節(jié)點(diǎn),并且左右子樹(shù)也滿足該性質(zhì)的完全二叉樹(shù)。小根堆相反??梢岳枚褋?lái)實(shí)現(xiàn)優(yōu)先隊(duì)列。...
閱讀 2506·2021-09-28 09:36
閱讀 1508·2021-09-22 15:33
閱讀 3646·2019-08-30 15:44
閱讀 1754·2019-08-29 13:14
閱讀 3141·2019-08-29 11:17
閱讀 1455·2019-08-29 11:03
閱讀 2916·2019-08-26 17:10
閱讀 691·2019-08-26 12:13