數據結構與算法:堆

朋友們大家好啊,本篇文章來到堆的內容,堆是一種完全二叉樹,再介紹堆之前,我們首先對樹進行講解

樹與堆

  • 1.樹的介紹
    • 1.1節點的分類
  • 2.樹的存儲結構
  • 3.二叉樹的概念和結構
    • 3.1 二叉樹的特點
    • 3.2 特殊的二叉樹
    • 3.3二叉樹的存儲結構
  • 4.堆的介紹和實現
    • 4.1 堆的實現,初始化與銷毀
    • 4.2插入元素與向上調整
      • 4.2.1堆向上調整
      • 4.2.2堆的建立
      • 4.2.3 堆元素的刪除和向下調整
    • 4.3 獲取堆頂元素與堆的數據個數
    • 4.4判斷堆是否為空

1.樹的介紹

在這里插入圖片描述

樹是一種非線性的數據結構,它是由n(n>=0)個有限結點組成一個具有層次關系的集合,n=0時成為空樹,當n>1時,其余結點可分為m(m>0)個互不相交的有限集T1、T2、……、Tm,其中每一個集合本身又是一棵樹,并且稱為根的子樹(SubTree)。

  • 有一個特殊的結點,稱為根結點(A),根節點沒有前驅結點。n>0 時根結點是唯一的,不可能存在多個根節點
  • 每棵子樹的根結點有且只有一個前驅可以有0個或多個后繼

注意:樹形結構中,子樹之間不能有交集,否則就不是樹形結構
在這里插入圖片描述
這兩種情況就是錯誤的

1.1節點的分類

樹的結點包含一個數據元素及若干指向其子樹的分支。結點擁有的子樹數稱為結點的度(Degree)度為0的結點稱為葉結點(Leaf)或終端結點度不為0的結點稱為非終端結點或分支結點。除根結點之外,分支結點也稱為內部結點。樹的度是樹內各結點的度的最大值。如圖所示,這棵樹結點的度的最大值是結點D的度為3,所以樹的度為3
在這里插入圖片描述

結點的子樹的根稱為該結點的孩子,相應地,該結點稱為孩子的雙親同一個雙親的孩子之間互稱兄弟。結點的祖先是從根到該結點所經分支上的所有結點。

在這里插入圖片描述
結點的層次(Level)從根開始定義起,根為第一層,根的孩子為第二層。若某結點在第L層,則其子樹的根就在第L+1層。其雙親在同一層的結點互為堂兄弟。顯然 D、E、F是堂兄弟。樹中結點的最大層次稱為樹的深度(Depth)或高度,當前樹的深度為4。

2.樹的存儲結構

提到存儲結構,我們會想到兩種:順序存儲和鏈式存儲
先來看看順序存儲結構,用一段地址連續的存儲單元依次存儲線性表的數據元素。這對于線性表來說是很自然的

樹中某個結點的孩子可以有多個,這就意味著,無論按何種順序將樹中所有結點存儲到數組中,結點的存儲位置都無法直接反映邏輯關系,你想想看,數據元素挨個的存儲,誰是誰的雙親,誰是誰的孩子呢?簡單的順序存儲結構是不能滿足樹的實現要求的。

樹有很多種表示方式如:雙親表示法,孩子表示法、孩子雙親表示法以及孩子兄弟表示法等。我們這里就簡單的了解其中最常用的孩子兄弟表示法

任意一棵樹,它的結點的第一個孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我們設置兩個指針,分別指向該結點的第一個孩子和此結點的右兄弟。
在這里插入圖片描述

其中data是數據域,firstchild為指針域,存儲該結點的第一個孩子結點的存儲地址,rightsib是指針域,存儲該結點的右兄弟結點的存儲地址。

typedef int DataType;
struct Node
{struct Node* firstchild; // 第一個孩子結點struct Node* rightsib; // 指向其下一個兄弟結點DataType data; // 結點中的數據域
};

在這里插入圖片描述

3.二叉樹的概念和結構

二叉樹(Binary Tree)是n(n≥0)個結點的有限集合,該集合或者為空集(稱為空二叉樹),或者由一個根結點和兩棵互不相交的、分別稱為根結點的左子樹和右子樹的二叉樹組成。
在這里插入圖片描述

3.1 二叉樹的特點

  • 每個結點最多有兩棵子樹,所以二叉樹中不存在度大于2的結點。注意不是只有兩棵子樹,而是最多有。沒有子樹或者有一棵子樹都是可以的。
  • 左子樹和右子樹是有順序的,次序不能任意顛倒。
  • 即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹。

二叉樹具有五種基本情況:
在這里插入圖片描述

3.2 特殊的二叉樹

在這里插入圖片描述

  • 滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。一個樹的層數為K,且節點總數為2k-1,則它就是滿二叉樹
    單是每個結點都存在左右子樹,不能算是滿二叉樹,還必須要所有的葉子都在同一層上,這就做到了整棵樹的平衡。

  • 完全二叉樹:完全二叉樹是效率很高的數據結構,完全二叉樹是由滿二叉樹而引出來的。對于深度為K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為K的滿二叉樹中編號從1至n的結點一一對應時稱之為完全二叉樹。 要注意的是滿二叉樹是一種特殊的完全二叉樹。

完全二叉樹的特點:

  • (1)葉子結點只能出現在最下兩層。
  • (2)最下層的葉子一定集中在左部連續位置。
  • (3)倒數二層,若有葉子結點,一定都在右部連續位置
  • (4)如果結點度為1,則該結點只有左孩子,即不存在只有右子樹的情況
  • (5)同樣結點數的二叉樹,完全二叉樹的深度最小

完全二叉樹的性質

  1. 若規定根節點的層數為1,則一棵非空二叉樹的第i層上最多有 2i-1 個結點
  2. 若規定根節點的層數為1,則深度為h的二叉樹的最大結點數是 2h-1
  3. 對任何一棵二叉樹, 如果度為0其葉結點個數為n0,度為2的分支結點個數為n2,則有n0=n2+1
  4. 具有n個節點的完全二叉樹的深度為[log2n]+1

對于具有n個結點的完全二叉樹,如果按照從上至下從左至右的數組順序對所有節點從0開始編號,則對于序號為i的結點有:
5. 若i>0,i位置節點的雙親序號(i-1)/2;i=0,i為根節點編號,無雙親節點
6. 若2i+1<n,左孩子序號:2i+1,2i+1>=n否則無左孩子
7. 若2i+2<n,右孩子序號:2i+2,2i+2>=n否則無右孩子

3.3二叉樹的存儲結構

前面我們已經談到了樹的存儲結構,并且談到順序存儲對樹這種一對多的關系結構實現起來是比較困難的。但是二叉樹是一種特殊的樹,由于它的特殊性,使得用順序存儲結構也可以實現。
二叉樹的順序存儲結構就是用一維數組存儲二叉樹中的結點,并且結點的存儲位置,也就是數組的下標要能體現結點之間的邏輯關系,比如雙親與孩子的關系,左右兄弟的關系等
在這里插入圖片描述
將這棵二叉樹存入到數組中,相應的下標對應其同樣的位置
在這里插入圖片描述
在這里插入圖片描述
考慮一種極端的情況,一棵深度為k的右斜樹,它只有k個結點,卻需要分配2k一1個存儲單元空間,這顯然是對存儲空間的浪費,如圖:
在這里插入圖片描述

所以,順序存儲結構一般只用于完全二叉樹

4.堆的介紹和實現

堆是一棵完全二叉樹,堆中的每一個節點都滿足堆性質,也就是每個節點的值都必須大于(或等于)或小于(或等于)其子節點的值。根據這個性質,堆可以分為兩種類型:

  • 大堆:在大堆中,每個父節點的值都大于或等于其子節點的值。因此,堆的根節點(即堆頂)包含了堆中的最大值
  • 小堆:在小堆中,每個父節點的值都小于或等于其子節點的值。因此,堆的根節點包含了堆中的最小值

下面是一個小堆的結構:

       1/   \3     6/ \   / \5  9  8   13

在這個小堆中:

  • 根節點1是最小的元素。
  • 每個子節點3, 6的值都大于等于它們的父節點1的值。
  • 這個性質適用于堆的所有層:例如,節點5, 9, 8, 13的值都大于等于它們各自的父節點3, 6的值。

這個小堆對應數組存儲結構為1 3 6 5 9 8 13

下面是一個大堆的結構:

       13/    \9      8/ \    / \5  3   6   1

對應數組結構為13 9 8 5 3 6 1

堆的樹形結構只是一種抽象的概念,在實際的物理存儲上,堆通常是以數組的形式來實現的

4.1 堆的實現,初始化與銷毀

堆的成立是數組數據不斷調整的過程,這里我們創建出堆的框架:

typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}Heap;

初始化

void HeapInit(Heap* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}

初始化堆數據數組的指針為 NULL。這意味著堆開始時沒有分配任何內存用于存儲元素。通常,在第一次向堆中添加元素時,程序會根據需要分配內存

銷毀

void HeapDestroy(Heap* php)
{assert(php);free(php->a);php->size = 0;php->capacity = 0;
}

free 函數釋放堆結構中動態分配的數組 a 所占用的內存。php->a 是指向堆中元素數組的指針,在堆初始化或元素添加過程中,會通過 malloc、realloc 等動態內存分配函數分配內存。釋放這塊內存是防止內存泄露的重要步驟。釋放后,這塊內存不應再被訪問

4.2插入元素與向上調整

void HeapPush(Heap* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->capacity = newcapacity;php->a = tmp;}php->a[php->size] = x;php->size++;Ajustup(php->a, php->size - 1);
}

首先判斷php不為空,再進行擴容,這個擴容在前面有多次提到
最主要的是下面的Ajustup函數

4.2.1堆向上調整

我們這里以小堆為例進行講解:

當向堆中插入一個新元素后,為了維持小頂堆的性質(即父節點的值始終小于等于其子節點的值),可能需要進行元素的向上調整)。下面詳細說明這個過程:

  1. 當一個新元素被加入到堆中時,它首先被放置在堆的末尾(即作為樹的最底層的最右側的葉子節點),以保持完全二叉樹的形狀。
  2. 比較新節點與其父節點的值:插入的新元素可能會破壞小頂堆的性質,此時需要將新元素與其父節點進行比較。對于數組中的節點 i(假設索引從0開始),其父節點的位置是 (i - 1) / 2注意這里全是整數值比如下標為2的元素,它的父節點就為0
  3. 如果新元素的值小于其父節點的值,那么就需要交換這兩個節點的值,因為在小頂堆中父節點應當是小于或等于子節點的值
  4. 向上遞歸:繼續將現在的節點位置(原父節點的位置,因為已經交換)與新的父節點進行比較,如果還是小新的父節點的值,繼續交換。這一過程一直進行,直到新元素到達根節點,或新元素大于或等于其父節點的值。

在這里插入圖片描述
接下來我們用函數實現

void Ajustup(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (parent - 1) / 2;}elsebreak;}
}
  • 對于給定的子節點索引child,其父節點的索引計算為(child - 1) / 2
  • 循環條件:while (child > 0)循環確保我們不會嘗試移動根節點(因為根節點的索引為0,沒有父節點)。循環繼續執行,只要當前節點的索引大于0。
  • 完成交換后,更新child變量為原父節點的索引,因為交換后當前元素已經移動到了父節點的位置。然后,對新的child值重新計算parent索引,繼紺執行可能的進一步交換
  • 循環終止條件:如果當前節點的值不小于其父節點的值(即堆的性質得到了滿足),循環終止,else break;執行

補充Swap函數:

void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2; *p2 = tmp;
}

有了這個調整函數,我們就可以建堆了

4.2.2堆的建立

通過調用Ajustup函數,逐步把輸入數組a轉換成一個小堆

我們在主函數中進行測試
在這里插入圖片描述

在這里插入圖片描述
這個經驗證確實是一個小堆

4.2.3 堆元素的刪除和向下調整

堆默認規定,要刪除根節點的數據

堆頂存放最小值,刪除后,為了滿足小堆的性質,接下來根節點存儲的為次小值

  • 由于堆是以數組的形式存儲的,堆頂元素就是數組的第一個元素。刪除堆頂元素后,需要保持堆的完整性和順序特性

  • 將堆的最后一個元素移動到堆頂:為了保持結構性質,堆的最后一個元素被移動到堆頂位置。這是因為在二叉堆中,我們希望維護一個完全二叉樹的結構。使用最后一個元素來替代被刪除的元素是一種簡單且有效的方法,它保證了樹的結構完整性。

  • 移動最后一個元素到堆頂后,這個新的堆頂元素可能會破壞堆的順序性質。為了恢復堆的性質,需要執行下沉操作。具體步驟如下:

    • 比較新的堆頂元素與其子節點。
    • 如果在最小堆中,新的堆頂元素比其子節點大,則它需要與其最小的子節點交換位置; 在最大堆中,如果新的堆頂元素比其子節點小,則它需要與其最大的子節點交換位置。
    • 重復這個比較和交換過程,直至新的堆頂元素被移至正確的位置,也就是說,它不再比任何一個子節點大(在最小堆中)或小(在最大堆中)
void HeapPop(Heap* php)
{assert(php);assert(php->size > 0);Swap(&php->a[0], &php->a[php->size - 1]);php->size--;Ajustdown(php->a,php->size,0);
}

在這里插入圖片描述
向下調整函數

void Ajustdown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child<size){if (child + 1 < size && a[child + 1] < a[child])//防止只有左孩子而越界{child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}

我們需要找小一點的孩子進行交換

  1. 子節點選擇:計算左子節點的索引(child = parent * 2 + 1)。在二叉堆中,給定父節點索引為i的情況下,左子節點的索引為2*i + 1右子節點的索引為2*i + 2。開始時,我們先考慮左子節點。
  2. while循環:確保當前考慮的子節點索引沒有超出數組的界限,如果有兩個節點,判斷右節點是否小于左節點,如果小,child++,后面讓右孩子與父節點交換
  3. 更新parent索引為當前child的索引,繼續向下遍歷堆。更新child索引為新parent索引的左子節點,準備進行下一輪的比較。
  4. 結束循環:如果子節點的值不小于父節點的值,說明當前父節點的位置適當,堆的性質得以維持,此時循環可以終止

對于每次AdjustDown調用,最壞情況下需要進行的比較和交換次數與堆的高度成正比,即O(log n)

AdjustDown操作的時間復雜度是O(log n)

4.3 獲取堆頂元素與堆的數據個數

HPDataType HeapTop(Heap* php)
{assert(php);assert(php->size > 0);return php->a[0];
}
int HeapSize(Heap* php)
{assert(php);return php->size;
}

4.4判斷堆是否為空

bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

如果是空,返回true,不是則返回false

本節內容到此結束,感謝大家觀看!!!

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

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

相關文章

Acwing---1460. 我在哪?

我在哪&#xff1f; 1.題目2.基本思想3.代碼實現 1.題目 農夫約翰出門沿著馬路散步&#xff0c;但是他現在發現自己可能迷路了&#xff01; 沿路有一排共 N N N 個農場。 不幸的是農場并沒有編號&#xff0c;這使得約翰難以分辨他在這條路上所處的位置。 然而&#xff0c;…

Mybatis | 動態SQL

目錄: 動態SQL中的 “元素” :\<if>元素\<choose>、\<when>、\<otherwise>元素\<where>、\<trim>元素\<set>元素\<foreach>元素\<bind>元素 作者簡介 &#xff1a;一只大皮卡丘&#xff0c;計算機專業學生&#xff0c;正…

單細胞Seurat - 降維與細胞標記(4)

本系列持續更新Seurat單細胞分析教程&#xff0c;歡迎關注&#xff01; 非線形降維 Seurat 提供了幾種非線性降維技術&#xff0c;例如 tSNE 和 UMAP&#xff0c;來可視化和探索這些數據集。這些算法的目標是學習數據集中的底層結構&#xff0c;以便將相似的細胞放在低維空間中…

__vueParentComponent和__vue__獲取dom元素上的vue實例

vue2: 使用__vue__ const el document.querySelector(.xxx); const vueInstance el.__vue__;vue3: 使用 __vueParentComponent const el document.querySelector(.xxx); const vueInstance el.__vueParentComponent;

Python錯題集-4:NameError:(變量名錯誤)

1問題描述 Traceback (most recent call last): File "D:\pycharm\projects\1-可視化學習\8.3更改小提琴圖的中位數、均值、顏色等.py", line 8, in <module> violin_parts plt.violinplot(data, showmediansTrue, showmeansTrue) …

代碼隨想錄算法訓練營第四十四天 完全背包 、零錢兌換 II 、組合總和 Ⅳ

代碼隨想錄算法訓練營第四十四天 | 完全背包 、零錢兌換 II 、組合總和 Ⅳ 完全背包 題目鏈接&#xff1a;題目頁面 (kamacoder.com) 解釋一、01背包 一維 &#xff1a;為什么要倒序遍歷背包&#xff1f; 首先要明白二維數組的遞推過程&#xff0c;然后才能看懂二維變一維的…

【MATLAB源碼-第150期】基于matlab的開普勒優化算法(KOA)機器人柵格路徑規劃,輸出做短路徑圖和適應度曲線。

操作環境&#xff1a; MATLAB 2022a 1、算法描述 開普勒優化算法&#xff08;Kepler Optimization Algorithm, KOA&#xff09;是一個虛構的、靈感來自天文學的優化算法&#xff0c;它借鑒了開普勒行星運動定律的概念來設計。在這個構想中&#xff0c;算法模仿行星圍繞太陽的…

項目風險:測試大佬結合實例告訴你如何應對!

項目有風險 今天下午15點&#xff0c;團隊成員D向他的主管Z反饋他測試的項目有風險&#xff1a;項目在測試周期內&#xff0c;但在用例評審時發現有一處功能邏輯有爭議&#xff0c;需要產品經理跟業務方確認&#xff0c;可能出現的情況有&#xff1a; 1 不變更需求&#xff0…

【技巧】SpringCloud Gateway實現多子域(單個應用開放多個端口)

0. 目錄 1. 需求背景2. 實現3. 額外 - 其它Servlet容器實現3.1 Undertow3.2 Tomcat 4. 相關 1. 需求背景 瀏覽器針對單個網站地址(ipport)存在“6個請求”限制&#xff1b;通過多子域配置可以突破這個限制&#xff0c;增加網站的響應效率&#xff0c;尤其是針對三維服務這類大…

【深入了解設計模式】組合設計模式

組合設計模式 組合模式是一種結構型設計模式&#xff0c;它允許你將對象組合成樹狀結構來表現“整體-部分”關系。組合模式使得客戶端可以統一對待單個對象和組合對象&#xff0c;從而使得代碼更加靈活和易于擴展。 概述 ? 對于這個圖片肯定會非常熟悉&#xff0c;上圖我們可…

Carla自動駕駛仿真九:車輛變道路徑規劃

文章目錄 前言一、關鍵函數二、完整代碼效果 前言 本文介紹一種在carla中比較簡單的變道路徑規劃方法&#xff0c;主要核心是調用carla的GlobalRoutePlanner模塊和PID控制模塊實現變道&#xff0c;大體的框架如下圖所示。 一、關鍵函數 1、get_spawn_point(),該函數根據指定r…

c語言字符串函數之strcpy函數,strnpy函數

strcpy函數 語法格式 strcpy(字符數組1,字符串2&#xff09; 它的作用是把字符串2復制到字符數組1里面 #include<stdio.h> #include<string.h> int main() {char c[]"河南";char d[]"安徽";char d[];printf("%s\n",strcpy(c,d));…

力扣hot100題解(python版41-43題)

41、二叉樹的層序遍歷 給你二叉樹的根節點 root &#xff0c;返回其節點值的 層序遍歷 。 &#xff08;即逐層地&#xff0c;從左到右訪問所有節點&#xff09;。 示例 1&#xff1a; 輸入&#xff1a;root [3,9,20,null,null,15,7] 輸出&#xff1a;[[3],[9,20],[15,7]]示例…

【C語言結構體】用戶自定義類型--結構體,結構體傳參,位段,聯合體和枚舉【圖文詳解】

歡迎來CILMY23的博客喔&#xff0c;本篇為【C語言結構體】用戶自定義類型--結構體&#xff0c;結構體傳參&#xff0c;位段&#xff0c;聯合體和枚舉【圖文詳解】&#xff0c;感謝觀看&#xff0c;支持的可以給個一鍵三連&#xff0c;點贊關注收藏。 前言 上一篇&#xff08;ht…

GO—函數

Go 語言支持普通函數、匿名函數和閉包&#xff0c;從設計上對函數進行了優化和改進&#xff0c;讓函數使用起來更加方便。 Go 語言的函數屬于“一等公民”&#xff08;first-class&#xff09;&#xff0c;也就是說&#xff1a; 函數本身可以作為值進行傳遞。支持匿名函數和閉…

Leetcode.2369 檢查數組是否存在有效劃分

題目鏈接 Leetcode.2369 檢查數組是否存在有效劃分 rating : 1780 題目描述 給你一個下標從 0 0 0 開始的整數數組 n u m s nums nums &#xff0c;你必須將數組劃分為一個或多個 連續 子數組。 如果獲得的這些子數組中每個都能滿足下述條件 之一 &#xff0c;則可以稱其為…

推薦6款SSH遠程連接工具

1、Xshell 介紹&#xff1a; xshell是一個非常強大的安全終端模擬軟件&#xff0c;它支持SSH1, SSH2, 以及Windows平臺的TELNET 協議。Xshell可以在Windows界面下用來訪問遠端不同系統下的服務器&#xff0c;從而比較好的達到遠程控制終端的目的。 業界最強大的SSH客戶機 官…

數據分析-Pandas數據的直方圖探查

數據分析-Pandas數據的直方圖探查 數據分析和處理中&#xff0c;難免會遇到各種數據&#xff0c;那么數據呈現怎樣的規律呢&#xff1f;不管金融數據&#xff0c;風控數據&#xff0c;營銷數據等等&#xff0c;莫不如此。如何通過圖示展示數據的規律&#xff1f; 數據表&…

農產品質量追溯系統—功能介紹(2)

儲藏管理 儲藏信息管理對需要儲藏的農產品,記錄儲藏的相關信息,如儲藏開始時間、存放倉庫、操作人員、儲藏原因等; 倉庫信息管理物流管理 物流公司管理對相關的物流公司信息進行登記,以便于管理和追溯; 車輛管理

我的秋招數據分析崗面經分享(京東,美團,阿里,拼多多,vivo,滴滴)

節前&#xff0c;我們社群組織了一場技術&面試討論會&#xff0c;邀請了一些互聯網大廠同學、參加社招和校招面試的同學&#xff0c;針對新手如何入門數據分析、機器學習算法、該如何備戰面試、面試常考點分享等熱門話題進行了深入的討論。 基于社群的討論&#xff0c;今天…