【數據結構 · 初階】- 堆的實現

目錄

一.初始化

二.插入

?三.刪除(堆頂、根)

四.整體代碼

Heap.h

Test.c

Heap.c


我們使用順序結構實現完全二叉樹,也就是堆的實現

以前學的數據結構只是單純的存儲數據。堆除了存儲數據,還有其他的價值——排序。是一個功能性的數據結構

小根堆堆頂的數據一定是最小的,大根堆堆頂的數據一定是最大的
選出最大/最小,再選次大/次小……不斷選最后就幫助排序。還可以解決取前幾,后幾的TOP-K?問題?

我們以建大堆為例。建小堆只需改變 爸 < 娃 即可


一.初始化

下面多次用到交換,將交換分裝成函數

Heap.h

typedef int HPDataTypt;typedef struct Heap
{HPDataTypt* a; // 數組指針,指向要開辟的存儲數據的數組int size; // 當前已存儲的有效數據個數int capacity; // 最大容量
}HP;void HeapInit(HP* php); // 初始化
void HeapDestroy(HP* php); // 銷毀

Heap.c?

void HeapInit(HP* php)
{assert(php);php->a = (HPDataTypt*)malloc(sizeof(HPDataTypt) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataTypt* p1, HPDataTypt* p2)
{HPDataTypt tmp = *p1;*p1 = *p2;*p2 = tmp;
}

php 是指向主函數中,HP(結構體類型)的變量 hp 地址的指針。php 中存放的是 hp 的地址。若為空就說明結構體沒有開好,所以一定不能為空,斷言。

二.插入

堆的底層就是數組,可以插入數據。要把控制數組想象成控制樹。原來是大根堆,插入后,要求還得是堆。
插入前是堆,插入后會影響部分祖先(跟祖先調整)

以大根堆為例,看最簡單的情況:插入20,插入后不影響堆的性質。

? ??

再插入60,插入后要調整。?

為保證父親 > 娃,要交換? ?

娃 還> 父親,繼續交換?父親 > 娃,結束

上面的過程叫 向上調整?,最多調整高度次,時間復雜度:O( log N )。插入一個數據,想讓他再調整成堆只要?log N 次


堆的插入不像鏈表、順序表,不能想往哪插就往哪插,要保持性質。

上面的尾插,如果堆原來是這樣,就不能尾插20?

所以插入單純的叫 Push 就好,因為不是由接口指定在哪個位置插入。


void AdjustUp(HPDataTypt* a, int child) // 從孩子(插入數據)位置向上調整
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]) // 娃 > 爸,往上走,交換{Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break; // 娃 <= 爸,不往上走}}
}void HeapPush(HP* php, HPDataTypt x)
{assert(php);if (php->size == php->capacity){HPDataTypt* tmp = (HPDataTypt*)realloc(php->a, sizeof(HPDataTypt) * php->capacity * 2);if (tmp == NULL){perror("malloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1); // 從孩子(插入數據)位置向上調整
}

?三.刪除(堆頂、根)

堆刪除,刪尾輕松,但無意義。

為什么刪堆頂、根才有意義?老大被干掉了,老二才能冒頭。

堆實現的意義,無論是排序還是 top-k ,本質是在幫我選數,選出最大/最小數

刪除后,也要保證是堆。要把最大的刪掉,怎么搞?


不能挪動刪除(直接刪)!原因:1.效率低下? ?2.父子兄弟關系全亂了


正確方法:(間接刪)?堆頂和最后的元素換一下;--size,使換下去的最后一個(原堆頂)元素失效
1.效率高? ?2.最大程度的保持了父子關系

單看左右子樹依舊是大堆,換上去的原最后元素大概率是比較小的,就要向下調整


看下面的新場景:為保證換了之后父親 > 娃,現在的堆頂(原最后一個元素)要跟大的娃換。

娃中大的 > 爸,把爸換下去?繼續換? ? ?

最壞情況調到葉子結束。物理上是數組,怎么判斷到葉子——沒有娃,怎么判斷沒有娃呢?
把它當做爸,算左娃的下標,如果超出數組范圍就沒娃,所以參數要多給個數組的大小 size,用來判斷 child 是否越界

最壞走高度?log N 次

void AdjustDown(HPDataTypt* a, int n, int parent)
{int child = parent * 2 + 1; // 默認左孩子大,將左孩子定為 childwhile (child < n){// 選出左右孩子中大的那一個if (child + 1 < n && a[child] < a[child + 1]) // 防止無右娃的越界風險{child++; // 如果右孩子大,++后,child 就是右孩子}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]); // 交換堆頂、最后元素php->size--; // 刪除換下來的原堆頂元素AdjustDown(php->a, php->size, 0); // 向下調整,0是開始調整位置的下標// n 是有效數據個數,作為下標,用來判斷 child 是否越界
}

向上調整的前提:除了 child 這個位置,前面的數據構成堆
向下調整的前提:保證左右子樹都是堆

四.整體代碼

Heap.h

typedef int HPDataTypt;typedef struct Heap
{HPDataTypt* a; // 數組指針,指向要開辟的存儲數據的數組int size; // 當前已存儲的有效數據個數int capacity; // 最大容量
}HP;void HeapInit(HP* php); // 初始化
void HeapDestroy(HP* php); // 銷毀void HeapPush(HP* php, HPDataTypt x); // 插入
void HeapPop(HP* php);// 刪除堆頂HPDataTypt HeapTop(HP* php); // 堆頂的數據
bool HeapEmpty(HP* php); // 探空
int HeapSize(HP* php);void AdjustUp(HPDataTypt* a, int child); // 向上調整
void AdjustDown(HPDataTypt* a, int n, int parent); // 向下調整

Test.c

void test1() // 排序
{HP hp;HeapInit(&hp);HeapPush(&hp, 2);HeapPush(&hp, 45);HeapPush(&hp, 76);HeapPush(&hp, 23);HeapPush(&hp, 5654);HeapPush(&hp, 24);HeapPush(&hp, 5);HeapPush(&hp, 242);HeapPush(&hp, 25);while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);// 選老二,必須干掉老大}HeapDestroy(&hp);
}void test2() // top-k
{HP hp;HeapInit(&hp);HeapPush(&hp, 2);HeapPush(&hp, 45);HeapPush(&hp, 76);HeapPush(&hp, 23);HeapPush(&hp, 5654);HeapPush(&hp, 24);HeapPush(&hp, 5);HeapPush(&hp, 242);HeapPush(&hp, 25);HeapPush(&hp, 5);HeapPush(&hp, 5);int k = 0;scanf("%d", &k);while (!HeapEmpty(&hp) && k--){printf("%d ", HeapTop(&hp));HeapPop(&hp);// 選老二,必須干掉老大}HeapDestroy(&hp);
}

Heap.c

void HeapInit(HP* php)
{assert(php);php->a = (HPDataTypt*)malloc(sizeof(HPDataTypt) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataTypt* p1, HPDataTypt* p2)
{HPDataTypt tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataTypt* a, int child) // 從孩子(插入數據)位置向上調整
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]) // 娃 > 爸,往上走,交換{Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break; // 娃 <= 爸,不往上走}}
}void HeapPush(HP* php, HPDataTypt x)
{assert(php);if (php->size == php->capacity){HPDataTypt* tmp = (HPDataTypt*)realloc(php->a, sizeof(HPDataTypt) * php->capacity * 2);if (tmp == NULL){perror("malloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1); // 從孩子(插入數據)位置向上調整
}void AdjustDown(HPDataTypt* a, int n, int parent)
{int child = parent * 2 + 1; // 默認左孩子大,將左孩子定為 childwhile (child < n){// 選出左右孩子中大的那一個if (child + 1 < n && a[child] < a[child + 1]) // 防止無右娃的越界風險{child++; // 如果右孩子大,++后,child 就是右孩子}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]); // 交換堆頂、最后元素php->size--; // 刪除換下來的原堆頂元素AdjustDown(php->a, php->size, 0); // 向下調整,0是開始調整位置的下標// n 是有效數據個數,作為下標,用來判斷 child 是否越界
}HPDataTypt HeapTop(HP* php)
{assert(php);return php->a[0];
}bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}int HeapSize(HP* php)
{assert(php);return php->size;
}void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

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

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

相關文章

qt.tlsbackend.ossl: Failed to load libssl/libcrypto.

我的環境是windows&#xff0c;QT6.3.2&#xff08;msvc2019_64/mingw_64&#xff09; 出錯原因 QT沒有正確加載OpenSSL。 解決過程 1、確保安裝的有openssl。 文章結尾有個注意&#xff0c;是其他方式安裝過openssl&#xff0c;環境變量有&#xff0c;但是QT找不到的問題。…

【Linux】用戶權限

shell命令 1. Linux本質上是一個操作系統&#xff0c;但是一般的用戶不能直接使用它&#xff0c;而是需要通過外殼程序shell&#xff0c;來與Linux內核進行溝通。 2. shell的簡單定義&#xff1a;命令行解釋器。主要包含以下作用&#xff1a; 將使用者的命令翻譯給核心處理。將…

賽靈思 XC7K325T-2FFG900I FPGA Xilinx Kintex?7

XC7K325T-2FFG900I 是 Xilinx Kintex?7 系列中一款工業級 (I) 高性能 FPGA&#xff0c;基于 28 nm HKMG HPL 工藝制程&#xff0c;核心電壓標稱 1.0 V&#xff0c;I/O 電壓可在 0.97 V–1.03 V 之間靈活配置&#xff0c;并可在 –40 C 至 100 C 溫度范圍內穩定運行。該器件提供…

【題解-Acwing】847. 圖中點的層次

題目:847. 圖中點的層次 題目描述 給定一個 n 個點 m 條邊的有向圖,圖中可能存在重邊和自環。 所有邊的長度都是 1,點的編號為 1~n。 請你求出 1 號點到 n 號點的最短距離,如果從 1 號點無法走到 n 號點,輸出 ?1 。 輸入 第一行包含兩個整數 n 和 m。 接下來 m 行…

css圖片設為灰色

使用filter方式將圖片設置為灰色 普通圖片使用&#xff1a;filter: saturate(0); 純白圖片使用&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"width…

【Luogu】動態規劃一

P5414 [YNOI2019] 排序 - 洛谷 思路&#xff1a; 可以想到對于任意一個需要換位置的數字&#xff0c;我們不可能換兩次及以上&#xff0c;那么這題就可以轉化為求一個最大和的最長不遞減子序列&#xff0c;最后的答案就是眾和減去這個最大和 代碼&#xff1a; #include <…

什么是管理思維?

管理思維是指在管理活動中形成的系統性、戰略性和創造性的思考方式&#xff0c;幫助個人或團隊更高效地達成目標。它不僅適用于企業管理&#xff0c;也適用于個人成長、項目執行和復雜問題解決。以下是關于管理思維的核心內容&#xff1a; 一、管理思維的核心特征 1. 系統性思…

利用TCP+多進程技術實現私聊信息

服務器&#xff1a; import socket from multiprocessing import Process from threading import Threaduser_dic {}def send_recv(client_conn, client_addr):while 1:# 接收客戶端發送的消息res client_conn.recv(1024).decode("utf-8")print("客戶端發送…

Hbuilder 上的水印相機實現方案 (vue3 + vite + hbuilder)

效果 思路 通過 live-pusher 這個視頻推流的組件來獲取攝像頭拿到視頻的一幀圖片之后&#xff0c;跳轉到正常的 vue 頁面&#xff0c;通過 canvas 來處理圖片水印 源碼 live-pusher 這個組件必須是 nvue 的 至于什么是 nvue&#xff0c;看這個官方文檔吧 https://uniapp.dcl…

Spark,IDEA編寫Maven項目

IDEA中編寫Maven項目 1.打開IDEA新建項目2.選擇java語言&#xff0c;構建系統選擇Maven 3.IDEA中配置Maven 注&#xff1a;這些文件都是我們老師幫我們在網上找了改動后給我們的&#xff0c;大家可自行在網上查找 編寫代碼測試HDFS連接 1.在之前創建的pom.xml文件中添加下…

初識Redis · C++客戶端set和zset

目錄 前言&#xff1a; set sadd sismember smembers spop scard sinter sinterstore zset zadd zrange zcard zrem zrank zscore 前言&#xff1a; 前文我們已經介紹了string list hash在Redis-plus-plus的使用&#xff0c;本文我們開始介紹set和zset在redis-plus-pl…

sed命令筆記250419

sed命令筆記250419 sed&#xff08;Stream Editor&#xff09;是 Linux/Unix 系統中強大的流編輯器&#xff0c;主要用于對文本進行過濾和轉換&#xff08;按行處理&#xff09;。它支持正則表達式&#xff0c;適合處理文本替換、刪除、插入等操作。以下是 sed 的詳細解析&…

ubuntu-24.04.2-live-server-arm64基于cloud-init實現分區自動擴容(LVM分區模式)

1. 環境 虛擬機鏡像ISO&#xff1a;ubuntu-24.04.2-live-server-arm64.iso 2. 定制cloud-init鏡像 2.1 安裝OS 基于ubuntu-24.04.2-live-server-arm64.iso&#xff0c;通過virt-manager安裝操作系統&#xff0c;語言建議選擇英文&#xff0c;分區選擇基于LVM的自動分區&…

vue3專題1------父組件中更改子組件的屬性

理解 Vue 3 中父組件如何引用子組件的屬性是一個很重要的概念。 這里涉及到 defineExpose 和 ref 這兩個關鍵點。 方法&#xff1a;使用 defineExpose 在子組件中暴露屬性&#xff0c;然后在父組件中使用 ref 獲取子組件實例并訪問暴露的屬性。 下面我將詳細解釋這個過程&…

數據倉庫分層架構解析:從理論到實戰的完整指南??

數據倉庫分層是構建高效數據體系的核心方法論。本文系統闡述ODS、DWD、DWS、ADS四層架構的設計原理&#xff0c;結合電商用戶行為分析場景&#xff0c;詳解各層功能及協作流程&#xff0c;并給出分層設計的原則與避坑指南&#xff0c;幫助讀者掌握分層架構的落地方法。 一、為什…

從零搭建一套前端開發環境

一、基礎環境搭建 1.NVM(Node Version Manager)安裝 簡介 nvm&#xff08;Node Version Manager&#xff09; 是一個用于管理多個 Node.js 版本的工具&#xff0c;允許開發者在同一臺機器上輕松安裝、切換和使用不同版本的 Node.js。它特別適合需要同時維護多個項目&#xff…

計算機組成原理筆記(十六)——4.1基本算術運算的實現

計算機中最基本的算術運算是加法運算&#xff0c;加、減、乘、除運算最終都可以歸結為加法運算。 4.1.1加法器 一、加法器的基本單元 加法器的核心單元是 全加器&#xff08;Full Adder, FA&#xff09;&#xff0c;而所有加法器都由 半加器&#xff08;Half Adder, HA&…

利用Qt創建一個模擬問答系統

界面&#xff1a; 添加了聊天顯示區域&#xff08;QTextEdit&#xff09; 添加了發送按鈕和清空對話按鈕 優化了布局和窗口大小添加了時間戳顯示 2、功能&#xff1a; 支持實時對話可以清空對話歷史 支持按回車發送消息 添加了簡單的關鍵詞匹配響應系統 交互體驗&#x…

神經光子渲染:物理級真實感圖像生成——從麥克斯韋方程到深度學習

一、技術背景與核心突破 2025年&#xff0c;神經光子渲染&#xff08;Photonic Neural Rendering, PNR&#xff09;技術通過物理光學方程與神經輻射場的深度融合&#xff0c;在AIGC檢測工具&#xff08;如GPTDetector 5.0&#xff09;的識別準確率從98%降至12%。該技術突破性地…

Linux中手動安裝7-Zip軟件文檔

7zip位于EPEL源中&#xff0c;如果服務器可以聯網或者配置了本地EPEL源則可以直接安裝 yum install p7zip p7zip-plugins -y對于無法聯網且沒有配置本地EPEL源的服務器&#xff0c;可以通過官網下載安裝包后&#xff0c;上傳至服務器&#xff0c;手動安裝 ## 下載地址&#x…