數據結構之優先級隊列

系列文章目錄

數據結構之ArrayList_arraylist o(1) o(n)-CSDN博客

數據結構之LinkedList-CSDN博客

數據結構之棧-CSDN博客

數據結構之隊列-CSDN博客

數據結構之二叉樹-CSDN博客


目錄

系列文章目錄

前言

一、優先級隊列和堆

二、堆的模擬實現

1. 堆的創建

2. 計算建堆的時間復雜度

3. 堆的插入

4. 堆的刪除

三、優先級隊列源碼

1. 構造方法

2. 擴容機制

3. 比較機制

四、topK問題


前言

本文主要介紹了優先級隊列和堆的實現原理。首先講解了堆的基本概念,包括大根堆和小根堆的存儲結構和父子節點關系。然后詳細講解了堆的模擬實現過程,包括創建堆、插入元素和刪除元素的操作步驟及時間復雜度分析。接著分析了Java中PriorityQueue的源碼實現,包括構造方法、擴容機制和比較機制。最后討論了topK問題的解決方案,提出使用大根堆/小根堆來高效獲取前k個最小/最大元素的算法思路。文章通過代碼示例展示了如何實現這些數據結構,并分析了相關操作的時間復雜度。


一、優先級隊列和堆

優先級隊列的兩個基本操作:返回高優先級對象,添加新的對象;

PriorityQueue 底層使用了堆這種數據結構,堆是在完全二叉樹的基礎上進行了一些調整;

堆是按照完全二叉樹的順序存儲(層序遍歷)的方式存儲;

小根堆:孩子節點都比根節點大;

大根堆:孩子節點都比根節點小;

如果根節點的下標是 i,左孩子節點的下標為 2 * i + 1,右孩子節點的下標為 2 * i + 2;

如果孩子節點的下標是 i,根節點的下標為 (i - 1)/ 2;

二、堆的模擬實現

1. 堆的創建

思路(以大根堆為例):

從后往前遍歷每一棵子樹,如果根節點的值比左右孩子的節點的值小,就將左右孩子節點的最大值和根節點的值交換;

交換完成后,這棵樹的左右子樹有可能出現孩子節點的值比根節點的值大的情況,因此需要將已經交換的孩子節點再次作為根節點,判斷根節點和左右孩子節點的值,直至交換完最后一棵子樹;

createHeap(): void 創建堆,從后往前遍歷每一棵子樹,向下調整根節點值;

shiftDown(int parent, int usedSize): void 計算孩子節點下標,如果孩子節點的值大于根節點的值,向下調整根節點的值;

public class Heap {private int[] elem;private int usedSize;public Heap(){this.elem = new int[10];}public Heap(int[] array){int n = array.length;this.elem = new int[n];for(int i = 0; i < array.length; i++){this.elem[i] = array[i];this.usedSize++;}}public void createHeap(){for(int parent = (this.usedSize - 1 - 1) / 2; parent >= 0; parent--){shiftDown(parent, this.usedSize);}}private  void shiftDown(int parent, int usedSize){int child = parent * 2 + 1;while(child < usedSize){if(child + 1 < usedSize && this.elem[child] < this.elem[child + 1]){child++;}if(this.elem[parent] < this.elem[child]){swap(parent, child);parent = child;child = parent * 2 + 1;}}}private void swap(int i, int j){int tmp = this.elem[i];this.elem[i] = this.elem[j];this.elem[j] = tmp;}
}

2. 計算建堆的時間復雜度

假設有 n 個節點,那么堆的高度為:h = log2^(n + 1);

計算節點的個數:從第一層到最后一層分別為?2^0, 2^1, 2^2,..., 2^(h-3), 2^(h-2), 2^(h-1);

計算要調整的高度:最壞情況下,從第一層到倒數第二層都需要調整,最后一層不需要調整,調整的高度分別是 h - 1, h - 2, h - 3, ..., 2, 1, 0;

因此需要花費的時間為:

T(n) = 2^0 * (h - 1) + 2^1 * (h - 2) + 2^2 * (h - 3) + 2^3 * (h - 4) + ... + 2^(h - 3) * 2?+ 2^(h - 2) * 1;

2 * T(n) = 2^1 * (h - 1) + 2^2 * (h - 2) + 2^3 * (h - 3) + ... + 2^(h - 2) * 2 + 2^(h - 1) * 1;

使用錯位相減計算:

T(n) = -2^0 * (h - 1) + 2^1 + 2^2 + 2^3 + ... + 2^(h - 3) + 2^(h - 2) + 2^(h - 1);

T(n) = 2^0 + 2^1 + 2^2 + 2^3 + ... + 2^(h - 3) + 2^(h - 2) + 2^(h - 1) - h;

使用等比數列求和公式:

T(n) = 1 * (1 - 2^h) / (1 - 2)? - h = 2^h?- 1 - h;

將 h = log2^(n + 1) 帶入公式:T(n) = n - log2^(n + 1);

當 n 足夠的大情況下,log2^(n + 1)相比于 n 是可以忽略的,因此建堆的時間復雜度為 O(N);

3. 堆的插入

思路:

先在最后一個位置放上插入的元素,再將這個元素向上調整;

向上調整時,該節點作為 child 節點和根節點發生了交換,這個節點又會作為新的 child 節點,可能仍然需要繼續向上調整,因此需要循環直至 child 作為整個完全二叉樹的根節點;

如果沒有發生交換,說明已經是一個大根堆了,則跳出循環即可;

offer(int val): void 向堆中添加元素;

shiftUp(int child): void 如果根節點的值比孩子節點的值小,向上調整孩子節點的值;

    public void offer(int val){if(isFull()){this.elem = Arrays.copyOf(this.elem,  2 * this.elem.length);}this.elem[this.usedSize] = val;this.usedSize++;shiftUp(this.usedSize - 1);}private boolean isFull(){return this.usedSize == this.elem.length;}private void shiftUp(int child){while(child != 0){int parent = (child - 1) / 2;if(this.elem[child] > this.elem[parent]){swap(parent, child);}else{break;}child = parent;}}

4. 堆的刪除

思路:

將根節點和最后一個位置的元素進行交換,再將根節點位置的元素向下調整;

poll(): int 彈出堆頂元素;

    public int poll(){if(isEmpty()){return -1;}swap(0, this.usedSize - 1);this.usedSize--;shiftDown(0, this.usedSize);return this.elem[this.usedSize];}public boolean isEmpty(){return this.usedSize == 0;}

三、優先級隊列源碼

PriorityQueue 默認是小根堆;

PriorityQueue 中放置的元素必須能夠比較大小,否則會拋出 ClassCastException 異常;

PriorityQueue 中不能放置 null 對象,否則會拋出空指針異常;

插入和刪除元素的時間復雜度為 O(logN);

1. 構造方法

兩個構造方法本質上都是調用 :

public PriorityQueue(int initialCapacity,Comparator<? super E> comparator)

第一個參數為初始化容量,第二個參數為比較器;

默認的初始化容量 DEFAULT_INITIAL_CAPCITY 為 11;

public class PriorityQueue<E> extends AbstractQueue<E>implements java.io.Serializable {private static final long serialVersionUID = -7720805057305804111L;private static final int DEFAULT_INITIAL_CAPACITY = 11;transient Object[] queue; // non-private to simplify nested class accessprivate int size = 0;private final Comparator<? super E> comparator;transient int modCount = 0; // non-private to simplify nested class accesspublic PriorityQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}public PriorityQueue(int initialCapacity) {this(initialCapacity, null);}public PriorityQueue(Comparator<? super E> comparator) {this(DEFAULT_INITIAL_CAPACITY, comparator);}public PriorityQueue(int initialCapacity,Comparator<? super E> comparator) {// Note: This restriction of at least one is not actually needed,// but continues for 1.5 compatibilityif (initialCapacity < 1)throw new IllegalArgumentException();this.queue = new Object[initialCapacity];this.comparator = comparator;}
}

2. 擴容機制

MAX_ARRAY_SIZE 最大的數組容量為 Integer.MAX_VALUE - 8;

grow(int minCapacity): void 擴容:

當前的容量小于 64 字節:當前容量 + 當前容量 + 2 字節;

當前容量大于等于 64 字節:當前容量 * 1.5;

如果擴容后的容量大于?MAX_ARRAY_SIZE,調用 hugeCapacity(int minCapacity): int

hugeCapacity(int minCapacity): int 大容量擴容機制:

如果所需的最小容量溢出了(變成負數),則拋出異常;

如果所需的最小容量大于?MAX_ARRAY_SIZE,擴容為 Integer.MAX_VALUE

如果所需的最小容量小于等于?MAX_ARRAY_SIZE,擴容為??MAX_ARRAY_SIZE

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** Increases the capacity of the array.** @param minCapacity the desired minimum capacity*/private void grow(int minCapacity) {int oldCapacity = queue.length;// Double size if small; else grow by 50%int newCapacity = oldCapacity + ((oldCapacity < 64) ?(oldCapacity + 2) :(oldCapacity >> 1));// overflow-conscious codeif (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);queue = Arrays.copyOf(queue, newCapacity);}private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}

3. 比較機制

offer(E e): boolean 向堆中添加元素,如果堆中沒有元素,直接添加到 0 下標,如果堆中有元素,調用 siftUp() 方法;

siftUp(int k, E x): void 向上調整,如果傳了比較器,調用 siftUpUsingComparator() 方法,否則調用 siftUpComparable() 方法;

siftUpUsingComparator(int k, E x): void 使用比較器,向上調整;

計算根節點下標,將根節點下標的元素保存在變量 e 里面;

假設比較器是 a.compareTo(b):

使用比較器(調用比較器的 compare() 方法)比較 x 和 e,如果大于等于 0(x 大于等于根節點的值),跳出循環,把 x 放到堆的下一個位置,此時建立的是一個小根堆;

如果小于 0(x 的值比根節點小),把 e 放到下一個位置,向上調整,直到 x 比某一個根節點大或者已經到 0 下標的位置,把 x 放到這個位置,此時建立的是一個小根堆;

因此比較器傳 a.compareTo(b),建立的就是小根堆;

反之,比較器傳 b.compareTo(b),建立的就是大根堆;

    public boolean offer(E e) {if (e == null)throw new NullPointerException();modCount++;int i = size;if (i >= queue.length)grow(i + 1);size = i + 1;if (i == 0)queue[0] = e;elsesiftUp(i, e);return true;}private void siftUp(int k, E x) {if (comparator != null)siftUpUsingComparator(k, x);elsesiftUpComparable(k, x);}private void siftUpUsingComparator(int k, E x) {while (k > 0) {int parent = (k - 1) >>> 1;Object e = queue[parent];if (comparator.compare(x, (E) e) >= 0)break;queue[k] = e;k = parent;}queue[k] = x;}private void siftUpComparable(int k, E x) {Comparable<? super E> key = (Comparable<? super E>) x;while (k > 0) {int parent = (k - 1) >>> 1;Object e = queue[parent];if (key.compareTo((E) e) >= 0)break;queue[k] = e;k = parent;}queue[k] = key;}

四、topK問題

以前 k 個最小的元素為例:

使用優先級隊列進行排序:

    public int[] smallestK(int[] arr, int k) {PriorityQueue<Integer> heap = new PriorityQueue<>();for(int x : arr) heap.offer(x);int[] ret = new int[k];for(int i = 0; i < k; i++) {ret[i] = heap.poll();}return ret;}

如果數組中元素非常多,優先級隊列可能存不下,并且這樣做是效率不高的;

推薦的做法:

使用數組中前 k 個數據建立一個容量為 k 的大根堆(堆頂元素是 k 個元素中最大的);

遍歷后續的元素,如果元素小于堆頂元素,那么堆頂元素一定不是前 k 個最小的元素之一,堆頂元素出堆,新元素入堆;

    public int[] smallestK(int[] arr, int k) {int[] ret = new int[k];if(k == 0) return ret;PriorityQueue<Integer> heap = new PriorityQueue<>((a, b) -> b - a);for(int i = 0; i < k; i++) heap.offer(arr[i]);int n = arr.length;for(int i = k; i < n; i++){if(arr[i] < heap.peek()){heap.poll();heap.offer(arr[i]);}}for(int i = 0; i < k; i++){ret[i] = heap.poll();}return ret;}

找前 k 小的元素,建大根堆;找前 k 大的元素,建小根堆;

堆中元素就是前 k 小或者大的元素,如果是找第 k 小或者第 k 大元素,直接返回堆頂元素即可;?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/86150.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86150.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86150.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【版本控制教程】如何使用Unreal Engine 5 + UE源代碼控制(Perforce P4)

本文來源perforce.com&#xff0c;由Perforce中國授權合作伙伴——龍智翻譯整理&#xff0c;旨在為國內用戶提供一份實用、易懂的Unreal Engine 5Perforce P4的中文使用指南。希望能為UE開發者、設計師和美術小伙伴們的版本控制實踐提供有力支持~ Unreal Engine 5 是一款尖端的…

opensingleComDialog方法解析優化

下面是對 opensingleComDialog 方法的詳細解析&#xff0c;并給出優化建議和優化后的代碼。 方法解析 作用 opensingleComDialog(index) 方法用于在輸入框失去焦點時&#xff08;blur 事件&#xff09;自動根據輸入內容進行唯一性查詢&#xff0c;如果查到唯一結果則自動填充…

css 實現1個像素在不同分辨率屏幕上畫網格線

實現網格線繪制&#xff0c;要考慮畫布style尺寸和畫布像素大小的縮放關系 單像素繪制主要出現的問題是會模糊&#xff0c;從像素角度看就是出現繪制兩個像素&#xff0c;實際就是要做偏移 核心就是&#xff1a;按物理像素繪制&#xff0c;首先要對齊物理像素&#xff0c;計算…

深度圖聚類DGC—Paper Notes

目錄 Unsupervised Deep Embedding for Clustering Analysis (DEC 2016)Attributed Graph Clustering: A Deep Attentional Embedding Approach (DAEGC 2019)Structural Deep Clustering Network (SDCN 2020)Contrastive Multi-View Representation Learning on Graphs (MVG…

獲取YARN application 應用列表的幾種方法

目錄 1. 使用YARN命令行工具 2. 通過REST API獲取 YARN 提供了獲取YARN集群上運行的應用列表,以下是幾種常見方法: 1. 使用YARN命令行工具 最直接的方式是使用YARN提供的命令行工具: yarn application -list 上述命令會顯示所有正在運行的應用。 如果要查看所有應用(…

前端如何下載 ‘Content-Type‘: ‘application/octet-stream‘ 的文件

前言 在前端開發中&#xff0c;經常會遇到需要從后端接口下載文件的需求。當后端返回的響應頭中 Content-Type 為 application/octet-stream 時&#xff0c;表示這是一個二進制流文件&#xff0c;瀏覽器無法直接展示&#xff0c;需要前端處理后下載到本地。本文將詳細介紹前端…

咨詢顧問進階——顧問公司戰略咨詢分析模板【附全文閱讀】

該戰略咨詢分析模板圍繞企業戰略分析展開&#xff0c;先從總體思考戰略分析的目的與方法&#xff0c;接著探討企業及戰略定義、戰略地位等。外部環境分析通過 PEST、五種競爭力等模型&#xff0c;分析環境、行業、市場等情況以發現機會與威脅&#xff1b;內部環境分析從資源、核…

寶塔服務器調優工具 1.1(Opcache優化)

第一步&#xff1a;寶塔服務器調優工具 1.1&#xff08;按照下面的參數填寫&#xff09; 第二步&#xff1a;路徑/www/server/php/80/etc/php.ini 搜索jit jit1235 其中1235根據服務器情況修改 第三步&#xff1a;路徑/www/server/php/80/etc/php-cli.ini 搜索 jit1235 其中…

React Native【詳解】動畫

基礎動畫的實現流程 使用支持動畫的組件 <Animated.Viewstyle{[{opacity: fadeAnim, // 綁定透明度動畫值},]}><Text>動畫元素</Text></Animated.View>Animated.View&#xff1a;用于創建動畫容器&#xff0c;支持所有 View 的屬性。Animated.Te…

如何輕松地將照片從 iPhone 傳輸到計算機

如果您的照片占據了 iPhone 上最多的存儲空間&#xff0c;為什么不將照片從 iPhone 傳輸到電腦呢&#xff1f;您可能想要這樣做&#xff0c;但不知道如何開始&#xff1f;如果是這樣&#xff0c;那么本指南就是您所需要的。我們分享了 6 種方法以及步驟詳細信息。您可以按照一種…

操作系統之內存管理(王道)

本篇博客依據王道、與我的筆記而寫&#xff0c;講解了內存的基礎知識、內存管理的概念、進程的映像、連續分配管理方式、動態分區分配算法、基本分頁存儲管理、基本地址變換機構、TLB快表、兩級頁表、基本分段存儲管理方式、段頁式存儲管理方式、虛擬內存、請求分頁管理方式、頁…

C++11 std::thread 多線程編程詳解

C++11 標準首次將多線程支持引入語言標準庫,其中最核心的部分就是 <thread> 頭文件中的 std::thread 類。 ?? 一、基本概念 什么是線程? 線程是操作系統調度 CPU 時間的基本單位。一個進程中可以有多個線程,它們共享進程的資源(如內存、堆棧),但擁有各自獨立的…

設置vscode使用eslint

在 Visual Studio Code (VSCode) 中設置 ESLint 是一個很好的方式來確保代碼質量和一致性。以下是詳細的步驟&#xff1a; 1. 安裝 ESLint 擴展 打開 VSCode。點擊左側的擴展圖標&#xff08;四邊形圖標&#xff09;。在搜索框中輸入 ESLint。找到由 dbaeumer 提供的 ESLint …

.NET 生態中主流的前后端生產級框架

文章目錄 **1. 后端框架&#xff08;Backend Frameworks&#xff09;****(1) ASP.NET Core**&#xff08;微軟官方&#xff0c;主流選擇&#xff09;**(2) ABP Framework**&#xff08;企業級應用開發框架&#xff09; **2. 前端框架&#xff08;Frontend Frameworks&#xff0…

Spring Cloud Alibaba整合Sentinel指南

目錄 一、Sentinel核心功能概述 1. 控制臺安裝 2. 項目依賴配置 三、詳細整合步驟 1. 基礎配置 2. 資源定義與保護 3. 與OpenFeign整合 四、常見問題解決方案 五、最佳實踐案例 1. 流量控制場景 2. 熔斷降級場景 3. 熱點參數限流 六、高級功能 Spring Cloud Aliba…

Win10+PHPStudy 8.1完美運行CRMEB開源商城(附性能優化配置)

環境配置 下載phpstudy https://www.xp.cn/ 安裝完成之后打開&#xff0c;在軟件管理中安裝 nginx mysql 5.7 php 7.4 創建站點 填寫域名&#xff0c;根目錄選擇到public文件夾下 創建完成之后&#xff0c;點擊右側管理&#xff0c;選擇偽靜態 location / { if (!-e $request…

康謀方案 | ARXML 規則下 ECU 總線通訊與 ADTF 測試方案

目錄 一、引言 二、汽車電子控制系統 三、ECU開發流程中總線通訊&#xff1a;ARXML 規則下的標準化協作 四、ADTF&#xff1a;汽車數據與時間觸發框架&#xff08;Automotive Data and Time-Triggered Framework&#xff09; 五、應用案例 六、結語 一、引言 隨著汽車新…

常見JavaScript 代理模式應用場景解析

常見JavaScript 代理模式應用場景解析 在 JavaScript 開發中&#xff0c;代理模式&#xff08;Proxy Pattern&#xff09; 是一種強大的設計模式&#xff0c;它允許我們通過創建一個“代理”來控制對目標對象的訪問。通過代理&#xff0c;我們可以攔截并增強對象的行為&#x…

暴雨信創電腦代理商成功中標長沙市中醫康復醫院

6月25日&#xff0c;國內科技產業領軍企業暴雨信息傳來喜訊&#xff0c;其信創電腦成功中標長沙市中醫康復醫院信息化設備采購項目。此次中標&#xff0c;不僅彰顯了暴雨信息在信創領域的技術實力和產品優勢&#xff0c;也為長沙市中醫康復醫院的信息化建設注入了新的活力。 長…

ZYNQ PL高速采集AD7606數據與QT動態顯示全解析

從硬件設計到軟件優化,打造工業級數據采集系統 在工業自動化、醫療儀器等領域,高速多通道數據采集系統至關重要。本文手把手教你基于Xilinx ZYNQ平臺,實現8通道200kSPS高速采集**,并通過QT實現60fps動態波形顯示。突破性采用五級流水采集架構和GPU加速渲染,解決傳統方案的…