《堆的詳解:結構、操作及堆排序算法》

目錄

一.堆的概念與結構

1.1 堆的概念

1.2 堆性質:?

1.3 堆的結構定義

?二.堆的初始化和銷毀

2.1 堆的初始化:

2.2 堆的銷毀:

三.堆的插入數據(含向上調整算法的實現)?

3.1 插入邏輯

3.2?插入函數

3.3?向上調整算法

三. 堆的刪除數據(含向下調整算法)

3.1 刪除邏輯

3.2 向下調整算法

3.3 刪除棧頂

四.堆的取堆頂?

五.堆排序的實現

5.1 借助數據結構實現排序

5.2?真正的堆排序算法


一.堆的概念與結構

堆(Heap)是一種特殊的完全二叉樹,同時也是一種高效的數據結構,主要用于實現優先隊列等場景。其核心特性是節點之間的數值關系滿足嚴格的堆序性,具體可分為大堆(大頂堆)?和小堆(小頂堆)

1.1 堆的概念

堆的本質
堆是一棵完全二叉樹(結構特性),且每個節點的值必須滿足:

  • 若為大堆:每個節點的值大于等于其左右子節點的值(根節點是最大值)。
  • 若為小堆:每個節點的值小于等于其左右子節點的值(根節點是最小值)。

1.2 堆性質:?

  • 堆中某個結點的值總是不大于或者不小于其父結點的值
  • 堆總是一顆完全二叉樹
  • 堆頂是最值(最大值或最小值)

1.3 堆的結構定義

// 堆的結構體定義(大堆)
typedef int HPDataType;  // 堆中元素的類型
typedef struct Heap {HPDataType* arr;       // 存儲堆元素的數組int size;            // 當前堆中元素的個數int capacity;        // 堆的最大容量
} Heap;

?二.堆的初始化和銷毀

2.1 堆的初始化:

// 初始化空堆
void HeapInit(Heap* hp) {if (hp == NULL) {return;  // 處理空指針}hp->arr = NULL;hp->size = 0;hp->capacity = 0;
}

2.2 堆的銷毀:

銷毀之前先檢查數組為不為空,不為空就釋放掉然后置空,并把size和capacity賦值為0

//銷毀
void HPDestory(HP* php)
{assert(php);if (php->arr)free(php->arr);php->arr = NULL;php->size = php->capacity = 0;
}

三.堆的插入數據(含向上調整算法的實現)?

首先我們需要知道插入數據的邏輯 然后通過向上調整的方法來實現

向上調整算法

向上調整(也稱 “上浮”)的核心是:新插入的元素從底部逐步向上移動,與父節點比較,若不滿足堆序則交換,直到找到合適位置或到達根節點。

3.1 插入邏輯

  1. 空間檢查:若堆已滿,需擴容(類似動態數組擴容)。
  2. 添加元素:將新元素插入到堆的末尾(數組的最后一個位置)。
  3. 向上調整:從新元素位置開始,與父節點比較并交換,直至滿足堆序性(大堆:子節點≤父節點;小堆:子節點≥父節點)。

如下圖:

3.2?插入函數

首先插入時 我們需要判斷空間是否足夠 若不夠的話就需要進行增容 代碼如下

//往堆里面插入數據
void HPPush(HP* php, HPDataType x)
{//檢查空間是否足夠//不夠就擴容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr,newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}php->arr = tmp;php->capacity = newcapacity;}//夠就直接插入php->arr[php->size++] = x;//向上調整AdjustUp(php->arr, php->size - 1);//因為前面size++了,所有傳的是size-1
}

3.3?向上調整算法

//交換
void swap(int* a, int* b)
{int temp = *a;*a = *b;*b = temp;
}
//向上調整
void AdjustUp(HPDataType*arr, int child)
{assert(arr);int parent = (child - 1) / 2;while (child > 0){//大堆:>//小堆:<if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);child = parent;parent= (child - 1) / 2;}else{break;}}
}

大堆為例,步驟如下:

  • 設新元素索引為child,其父節點索引為parent = (child - 1) / 2(數組從 0 開始)。
  • a[child] > a[parent](大堆條件),交換兩者,更新child = parent,繼續向上比較。
  • 重復上述步驟,直到child = 0(根節點)或a[child] ≤ a[parent]

三. 堆的刪除數據(含向下調整算法)

堆的刪除操作通常指刪除堆頂元素(根節點,即最大值或最小值),并通過向下調整算法重新維護堆的特性。以下是具體實現:

3.1 刪除邏輯

  1. 替換堆頂:用堆的最后一個元素覆蓋堆頂元素(避免直接刪除堆頂導致結構破壞)。
  2. 縮減規模:堆的元素數量減 1(邏輯上移除最后一個元素)。
  3. 向下調整:從新的堆頂開始,與左右子節點中符合堆序的節點交換,直至滿足堆的特性。

如下圖

3.2 向下調整算法

向下調整(也稱 “下沉”)的核心是:將替換后的堆頂元素逐步向下移動,與左右子節點中更符合堆序的節點(大堆選較大者,小堆選較小者)交換,直到找到合適位置或成為葉子節點。

大堆為例,步驟如下:

  • 設當前節點索引為parent,左子節點索引為child = 2 * parent + 1(數組從 0 開始)。
  • 若右子節點存在且大于左子節點,更新child為右子節點索引。
  • a[parent] < a[child](大堆條件),交換兩者,更新parent = child,繼續向下比較。
  • 重復上述步驟,直到child超出堆的范圍或a[parent] ≥ a[child]
//向下調整
void AdjustDown(HPDataType* arr, int parent, int n)
{assert(arr);int child = 2 * parent + 1;while (child < n){//child+1也小于n//后面的小堆就是取小的,大堆取大的孩子if (child + 1 < n && arr[child] < arr[child + 1]){child++;}//大堆:>   小堆:<if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}

3.3 刪除棧頂

首先我們需要一個判斷是否為空的函數

//判空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}

刪除棧頂函數

//刪除(堆頂操作)
void HPPop(HP* php)
{assert(!HPEmpty(php));//首尾交換swap(&php->arr[0], &php->arr[php->size - 1]);php->size--;//向下調整AdjustDown(php->arr, 0, php->size);
}

先判斷不為空,然后交換首尾,直接--size刪掉,再通過向下調整算法把刪除一個數據后的堆重新調整成大堆。?

四.堆的取堆頂?

首先判斷是否為空 若不為空則直接返回棧頂元素arr[0]

//取堆頂
HPDataType HPTop(HP* php)
{assert(!HPEmpty(php));return php->arr[0];
}

五.堆排序的實現

這里向大家介紹兩種方式 但其實第一種并不是真正意義上的排序 只是達到了排序的目的而已

下面介紹第一種

5.1 借助數據結構實現排序

#include"Heap.h"//堆排序--這不是真正的堆排序,而是利用了堆這個數據結構來實現的排序
void HeapSort1(int* arr, int n)
{HP sp;               // 定義一個堆結構體變量HPInit(&sp);         // 初始化堆(創建空堆)// 1. 將數組所有元素插入堆中for (int i = 0; i < n; i++){HPPush(&sp, arr[i]);  // 插入元素,內部會通過向上調整維護堆序}// 2. 從堆中提取元素,重構數組int i = 0;while (!HPEmpty(&sp))    // 當堆不為空時{HPDataType top = HPTop(&sp);  // 獲取堆頂元素(最小值或最大值)arr[i++] = top;               // 將堆頂元素存入原數組,依次填充HPPop(&sp);                   // 刪除堆頂元素,內部通過向下調整維護堆序}
}
int main()
{int arr[6] = { 30,56,25,15,70,10 };printf("排序之前:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);}printf("\n");HeapSort1(arr, 6);printf("排序之后:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

核心思路

  1. 借助堆存儲元素:將待排序數組的所有元素依次插入堆中(構建堆)。
  2. 提取最值并重構數組:反復從堆頂提取最小值(或最大值),按順序存入原數組,最終得到有序數組。

為什么說 “這不是真正的堆排序”?

真正的堆排序(如之前實現的HeapSort)是原地排序,直接在原數組上通過構建堆和調整堆實現排序,不需要額外的堆結構,空間復雜度為O(1)

而這段代碼的本質是:

  1. 額外創建了一個堆(HP sp),需要O(n)的額外空間存儲元素;
  2. 排序過程依賴于堆的插入和刪除操作,本質是 “利用堆的特性進行排序”,而非嚴格意義上的原地堆排序。

5.2?真正的堆排序算法

通過構建大堆并反復提取最大值來實現數組的升序排序。其核心特點是原地排序(無需額外空間存儲堆),充分利用堆的特性高效完成排序。以下是詳細解釋:

一、核心思路

  1. 構建大堆:將待排序數組轉換為大堆(每個父節點的值 ≥ 子節點的值),此時堆頂(數組首位)是最大值。

  2. 排序過程

    • 交換堆頂(最大值)與當前堆的最后一個元素,將最大值放到數組末尾(最終位置)。

    • 縮小堆的范圍(排除已排好的末尾元素),對新堆頂執行向下調整,重新維護大堆特性。

    • 重復上述步驟,直到所有元素排序完成。

二、代碼逐段解析

1.?HeapSort 函數(排序核心)

void HeapSort(int* arr, int n)
{// 1. 構建大堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, i, n);  // 從最后一個非葉子節點開始向下調整}// 2. 執行排序int end = n - 1;  // 標記當前堆的最后一個元素索引while (end > 0){// 交換堆頂(最大值)與當前堆的最后一個元素swap(&arr[0], &arr[end]);// 縮小堆范圍(end減1),對新堆頂執行向下調整,重建大堆AdjustDown(arr, 0, end);end--;  // 下一次排序的堆范圍更小}
}

(1)構建大堆

  • 關鍵代碼for (int i = (n - 1 - 1) / 2; i >= 0; i--)

  • 計算最后一個非葉子節點的索引(n-1-1)/2(等價于?(n-2)/2)。

  • 數組從 0 開始,最后一個元素索引為?n-1,其父親節點索引為?(n-1-1)/2
  • 從下往上調整:從最后一個非葉子節點開始,依次向上對每個節點執行?AdjustDown(向下調整),最終將整個數組轉換為大堆。

  • 為什么從非葉子節點開始?
    葉子節點沒有子節點,無需調整;非葉子節點可能破壞堆序,需通過向下調整使其子樹滿足大堆特性。

(2)排序過程

  • 交換堆頂與末尾元素swap(&arr[0], &arr[end])
    大堆的堆頂(arr[0])是當前堆中的最大值,交換后最大值被放到數組末尾(arr[end]),即它的最終位置。

  • 向下調整重建大堆AdjustDown(arr, 0, end)
    交換后堆頂元素可能破壞堆序,需從堆頂開始向下調整,但此時堆的范圍已縮小為?[0, end-1]end?位置已排好序)。

  • 循環縮小范圍end--
    每次循環后,已排序的元素增加一個,堆的范圍持續縮小,直到?end=0(所有元素排序完成)。

2.?main 函數(測試邏輯)

int main()
{int arr[6] = { 30,56,25,15,70,10 };  // 待排序數組printf("排序之前:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);  // 輸出:30 56 25 15 70 10}printf("\n");HeapSort(arr, 6);  // 調用堆排序函數printf("排序之后:\n");for (int i = 0; i < 6; i++){printf("%d ", arr[i]);  // 輸出:10 15 25 30 56 70(升序)}printf("\n");return 0;
}
  • 測試數組初始為?[30,56,25,15,70,10],經過堆排序后變為升序數組?[10,15,25,30,56,70]

三、關鍵輔助函數說明

  1. AdjustDown(向下調整算法)
    作用:當某個節點破壞大堆特性時,通過與左右子節點中較大的一個交換,逐步向下移動,直至子樹重新滿足大堆特性。
    (代碼中未顯示實現,但核心邏輯是:選擇左右子節點的最大值,與父節點比較,不滿足大堆則交換并繼續調整。)

  2. swap(交換函數)
    作用:交換兩個元素的值,用于將堆頂最大值移動到數組末尾。

四、為什么大堆能實現升序排序?

  • 大堆的堆頂始終是當前堆中的最大值,每次將最大值放到數組末尾,相當于 “從后往前” 依次確定最大元素的位置。

  • 經過?n-1?次交換和調整后,數組從前往后依次遞增,最終實現升序。

五、算法特性

  • 時間復雜度O(n+logn)(構建堆?O(n)?+ 排序階段?O(nlog+n))。

  • 空間復雜度O(1)(原地排序,僅需常數級額外空間)。

  • 不穩定性:交換過程可能改變相等元素的相對順序(例如?[2, 2, 1]?排序后可能變為?[1, 2, 2],但兩個?2?的原始順序可能改變)。

總結

這段代碼是標準的堆排序實現,通過 “構建大堆→交換堆頂與末尾→調整堆” 的循環,高效完成升序排序。其原地排序的特性和?O(n+log n)?的時間復雜度,使其在大規模數據排序中表現優異。

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

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

相關文章

深入解析 Kubernetes 中的 Service 資源:為應用提供穩定的網絡訪問

什么是 Kubernetes 中的 Service&#xff1f; 在現代微服務架構中&#xff0c;服務之間的通信和負載均衡是至關重要的。尤其是在 Kubernetes 環境中&#xff0c;由于 Pod 是動態創建和銷毀的&#xff0c;如何為一組 Pod 提供穩定的訪問入口&#xff0c;成為了架構設計中的一個關…

使用Samba網絡磁盤作為MacOS時間機器的遠程備份磁盤

最近考慮MacOS系統升級&#xff0c;所以需要做磁盤備份&#xff0c;MacOS里有個備份磁盤很方便的工具&#xff1a;時間機器&#xff0c;可以自動定期備份磁盤&#xff0c;但是一般需要一個大點的移動硬盤插在macbook上選擇其為備份磁盤&#xff0c;可惜我并沒有移動硬盤&#x…

智能頭盔實時監控系統設計與實現

智能頭盔實時監控系統設計與實現 源碼 https://gitee.com/intostars/csdn-demo/tree/master/src/views/smartHelmet 預覽 一、功能概述 智能頭盔實時監控系統是基于Vue 3和TypeScript開發的一套用于遠程監控和控制智能頭盔設備的前端應用模塊。該系統通過WebSocket與后端服務…

Docker 學習筆記(八):容器運行時工具實踐及 OpenStack 部署基礎

容器管理工具Containerd nerdctl 實踐 nerdctl管理存儲 nerdctl命令創建容器的時候&#xff0c;可以使用-v選項將本地目錄掛載給容器實現數據持久化 示例&#xff1a; [rootlocalhost ~]# mkdir /data [rootlocalhost ~]# nerdctl run -d -v /data:/data busybox -- sleep infi…

Unity鍵盤控制角色運動

以下是一個完整的Unity角色移動和跳躍腳本,支持WASD或方向鍵移動: 使用說明 確保組件設置正確: 確保您的游戲對象有一個CharacterController組件 如果沒有,可以通過菜單 "Component -> Physics -> Character Controller" 添加 相機設置: 確保場景中有一…

linux 宏 DEVICE_ATTR

理解 DEVICE_ATTR DEVICE_ATTR 是 Linux 內核中用于創建設備屬性的宏&#xff0c;通常用于 sysfs 文件系統。通過 sysfs&#xff0c;用戶空間的程序可以讀取或修改內核中的設備屬性。DEVICE_ATTR 宏定義在 <linux/device.h> 頭文件中&#xff0c;用于聲明和定義一個設備屬…

MCP模型上下文協議以及交互流程

1. MCP 是什么全稱&#xff1a;Model Context Protocol定位&#xff1a;讓大語言模型&#xff08;LLM&#xff09;能在“上下文”之外&#xff0c;按統一格式訪問外部數據、調用插件、持久化狀態。動機&#xff1a;以前每家框架&#xff08;LangChain、LlamaIndex 等&#xff0…

MySQLTransactionRollbackException

問題描述mysql部署1主3從&#xff0c;昨天發現主庫有大量報警錯誤&#xff1a;Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ; Deadlock found when trying to get lock; …

Redis環境搭建指南:Windows/Linux/Docker多場景安裝與配置

Redis環境搭建指南&#xff1a;Windows/Linux/Docker多場景安裝與配置 1. Redis安裝方式概覽 1.1 安裝方式對比 安裝方式適用場景優點缺點難度Windows直接安裝開發調試安裝簡單&#xff0c;Windows兼容好性能不如Linux&#xff0c;生產不推薦?Linux源碼編譯生產環境性能最佳…

leetcode.80刪除有序數組中的重復項2

題目描述 給你一個有序數組 nums &#xff0c;請你 原地 刪除重復出現的元素&#xff0c;使得出現次數超過兩次的元素只出現兩次 &#xff0c;返回刪除后數組的新長度。 不要使用額外的數組空間&#xff0c;你必須在 原地 修改輸入數組 并在使用 O(1) 額外空間的條件下完成。…

運動卡新手入門及常見問題處理

1.新手入門1.1 插卡打開包裝&#xff0c;拿出PCI板卡&#xff0c;如下圖&#xff1a;打開電腦機箱蓋&#xff0c;找到PCI插槽&#xff0c;如下圖&#xff08;紅色框部分是PCI槽&#xff0c;有些主板上PCI槽是白色或其他顏色&#xff09;&#xff1a;插入板卡&#xff0c;如下圖…

PRINCE2與PMP項目管理體系對比

在全球范圍內&#xff0c;PRINCE2與PMP是兩大最具影響力的項目管理體系。PRINCE2注重流程和治理結構&#xff0c;強調“控制”與“規范”&#xff1b;而PMP基于PMBOK指南&#xff0c;強調知識體系和方法論的全面性&#xff0c;更關注“工具”與“實踐”。 不同體系的側重點&…

在UniApp跨平臺開發中實現相機自定義濾鏡的鏈式處理架構

以下是進階方案&#xff1a;架構核心設計分層結構$$Pipeline Capture \otimes Filter_1 \otimes Filter_2 \otimes \cdots \otimes Filter_n \otimes Render$$ 其中&#xff1a;$\otimes$ 表示鏈式處理操作符$Capture$ 為原始圖像采集層$Filter_n$ 為可插拔濾鏡單元$Render$ 為…

Mark5 穿越機電調深度解析:設計、選型、控制與實戰(下)

TIM_SetCompare3 (TIM1, T0 + T1 + T2); // W+? break;? case 3:? // U - 導通,V - 導通,W + 導通? TIM_SetCompare1 (TIM1, T0); // U-? TIM_SetCompare2 (TIM1, T0); // V-? TIM_SetCompare3 (TIM1, T0 + T1 + T2); // W+? break;? case 4:? // U - 導通…

背包問題從入門到入土

我在這里介紹4種常見的背包問題&#xff0c;這里我想按易 --> 難程度從01背包&#xff0c;完全背包&#xff0c;分組背包&#xff0c;多重背包的順序介紹。&#xff08;封面附在最后&#xff09;一&#xff0c;01背包問題&#xff08;后面三個背包問題的基礎&#xff09;01背…

Leetcode 18 java

?????1???????141. 環形鏈表1 題目 ?????1???????141. 環形鏈表 給你一個鏈表的頭節點 head &#xff0c;判斷鏈表中是否有環。 如果鏈表中有某個節點&#xff0c;可以通過連續跟蹤 next 指針再次到達&#xff0c;則鏈表中存在環。 為了表示給定鏈表…

Linux 正則表達式詳解(基礎 + 擴展 + 實操)

Linux 正則表達式詳解&#xff08;基礎 擴展 實操&#xff09; 正則表達式&#xff08;Regular Expression&#xff0c;簡稱 RE&#xff09;是 Linux 文本處理的核心工具&#xff0c;用于定義字符匹配模式&#xff0c;配合 grep、sed、awk 等工具可實現文本過濾、查找、替換等…

Json-rpc通信項目(基于C++ Jsoncpp muduo庫)

一、介紹RPC RPC&#xff08;Remote Procedure Call&#xff09;遠程過程調用&#xff0c;一種通過網絡從遠程計算器上請求服務&#xff0c;而不需要了解底層網絡通信細節&#xff0c;RPC可以使用多種網絡協議進行通信&#xff0c;并且在TCP/IP網絡四層模型中跨越了傳輸層和應…

RL【9】:Policy Gradient

系列文章目錄 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

Redis是什么?一篇講透它的定位、特點與應用場景

Redis是什么&#xff1f;一篇講透它的定位、特點與應用場景 1. Redis的定義與核心概念 1.1 什么是Redis&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09; 是一個開源的、基于內存的數據結構存儲系統&#xff0c;可以用作數據庫、緩存和消息代理。Redis由…