
目錄
- 前言
- 樹概念及結構
- 樹的表示
- 左孩子右兄弟表示法
- 2.二叉樹概念及結構
- 2.4特殊的二叉樹:
- 二叉樹的性質
- 練習題
- 3.2 堆的概念及結構
- 堆的存盤
- 堆排序
- 堆向下調整演算法
- 建堆時間復雜度分析
- 堆排序的思考,為什么排升序要建大堆
- 利用堆洗掉思想來進行排序
- 堆排序代碼實作
- 堆實作
- 介面宣告
- 介面實作
- 初始化
- 列印
- 銷毀
- 向下調整建堆
- 向上調整
- 堆排序
- 插入值
- 洗掉
- 取資料
- 獲取元素個數
- 判斷空
- TOPK問題
前言
一個(最大)二叉堆是一個具有最大堆特性的完全二叉樹,所以在學習堆之前,我們先來學習一下二叉堆吧

樹概念及結構
1.1樹的概念
樹是一種非線性的資料結構,它是由n(n>=0)個有限結點組成一個具有層次關系的集合,把它叫做樹是因
為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的,
有一個特殊的結點,稱為根結點,根節點沒有前驅結點
除根節點外,其余結點被分成M(M>0)個互不相交的集合T1、T2、……、Tm,其中每一個集合Ti(1<= i
<= m)又是一棵結構與樹類似的子樹,每棵子樹的根結點有且只有一個前驅,可以有0個或多個后繼
因此,樹是遞回定義的,

-
1、節點的度:一個節點含有的子樹的個數稱為該節點的度; 如上圖:A1的為6 .
-
2、葉節點或終端節點:度為0的節點稱為葉節點; 如上圖:B、C、H、I…等節點為葉節點
-
度為0的節點稱為葉節點,沒有孩子的結點就叫做葉子結點或者終端結點,而度不為0或者有孩子結點的就叫做非葉子結點或者非終端結點
-
3、非終端節點或分支節點:度不為0的節點; 如上圖:D、E、F、G…等節點為分支節點
-
有孩子的結點
-
4、雙親節點或父節點:若一個節點含有子節點,則這個節點稱為其子節點的父節點; 如上圖:A是B的父節點
-
5、孩子節點或子節點:一個節點含有的子樹的根節點稱為該節點的子節點; 如上圖:B是A的孩子節點
-
6、兄弟節點:具有相同父節點的節點互稱為兄弟節點; 如上圖:B、C是兄弟節點
-
7、樹的度:一棵樹中,最大的節點的度稱為樹的度; 如上圖:樹的度為6
-
最大的節點是指擁有更多的孩子
-
8、節點的層次:從根開始定義起,根為第1層,根的子節點為第2層,以此類推;
-
9、樹的高度或深度:樹中節點的最大層次; 如上圖:樹的高度為4
-
有些書籍會用0表示樹的第一層,樹的高度從0依次遞增
-
10、堂兄弟節點:雙親在同一層的節點互為堂兄弟;如上圖:H、I互為兄弟節點
-
11、節點的祖先:從根到該節點所經分支上的所有節點;如上圖:A是所有節點的祖先
-
12、子孫:以某節點為根的子樹中任一節點都稱為該節點的子孫,如上圖:所有節點都是A的子孫
-
13、森林:由m(m>0)棵互不相交的樹的集合稱為森林;
-
多棵樹沒有結點重疊的樹,稱之為森林
樹的表示
樹結構相對線性表就比較復雜了,要存盤表示起來就比較麻煩了,實際中樹有很多種表示方式,如:雙親表示法,孩子表示法、孩子兄弟表示法等等,我們這里就簡單的了解其中最常用的孩子兄弟表示法,

左孩子右兄弟表示法
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一個孩子結點
struct Node* _pNextBrother; // 指向其下一個兄弟結點
DataType _data; // 結點中的資料域
};

從樹的結構可以看出無論樹中一個結點有多少個孩子,都可以表示,只需要找到第一個根結點,剩下的結點,都可以用兄弟指標關聯,找到他
2.二叉樹概念及結構
2.1概念
一棵二叉樹是結點的一個有限集合,該集合或者為空,或者是由一個根節點加上兩棵別稱為左子樹和右子樹
的二叉樹組成,
二叉樹的特點:
- 每個結點最多有兩棵子樹,即二叉樹不存在度大于2的結點,
- 二叉樹的子樹有左右之分,其子樹的次序不能顛倒,

2.4特殊的二叉樹:
-
1、滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹,也就是
說,如果一個二叉樹的層數為K,且結點總數是(2^k) -1 ,則它就是滿二叉樹, -
2 、完全二叉樹:完全二叉樹是效率很高的資料結構,完全二叉樹是由滿二叉樹而引出來的,對于深度為K 的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為K的滿二叉樹中編號從1至n的結點一一對 應時稱之為完全二叉樹,
要注意的是滿二叉樹是一種特殊的完全二叉樹,

二叉樹的性質
- 若規定根節點的層數為1,則一棵非空二叉樹的第n層上最多有2^(n-1) 個結點.
- 若規定根節點的層數為1,則深度為h的二叉樹的最大結點數是2^h- 1.
- 對任何一棵二叉樹, 如果度為0其葉結點個數為 n0, 度為2的分支結點個數為 n2,則有n0=n2+1
- 若規定根節點的層數為1,具有n個結點的滿二叉樹的深度,h=Log2(n+1). (ps:Log2(n+1)是log以2為
底,n+1為對數) - 對于具有n個結點的完全二叉樹,如果按照從上至下從左至右的陣列順序對所有節點從0開始編號,則對
于序號為i的結點有:
- 若i>0,i位置節點的雙親序號:(i-1)/2;i=0,i為根節點編號,無雙親節點
- 若2i+1<n,左孩子序號:2i+1,2i+1>=n否則無左孩子
- 若2i+2<n,右孩子序號:2i+2,2i+2>=n否則無右孩子
- 計算一個二叉樹的總結點個數公式:2^k - 1
- 已知一顆滿二叉樹有N個結點,計算出他的高度是多少,公式log(N+1)
完全二叉樹與滿二叉樹的區別
補充:
1、完全二叉樹的前k-1層都是滿的,最后一層可以不滿但是從左往右都是連續的
2、滿二叉樹的第一層是滿的,第k層有2^(k-1)個結點才是滿的
練習題
- 某二叉樹共有 399 個結點,其中有 199 個度為 2 的結點,則該二叉樹中的葉子結點數為(B )
A 不存在這樣的二叉樹
B 200
C 198
D 199
2.下列資料結構中,不適合采用順序存盤結構的是( A)
A 非完全二叉樹
B 堆
C 佇列
D 堆疊
3.在具有 2n 個結點的完全二叉樹中,葉子結點個數為(A )
A n
B n+1
C n-1
D n/2
總結點個數為2n
假設度為0節點有a0個
假設度為1節點有a1個
假設度為2節點有a2個
…
2n = a0 + a1 + a2
求出度為2的結點個數可以用完全二叉樹的性質,度為0的結點個數比度為2的結點個數多1,a0 - 1等價于度為2的結點個數,所以可以得到下面的公式
2n = a0 + a1 + a0 - 1
進一步簡化
2n = 2a0 - 1 + a1
有了這個結論后,可以進一步得到下面的公式
2n = 2a0 - 1 + 1
n = a0
4.一棵完全二叉樹的節點數位為531個,那么這棵樹的高度為( )
A 11
B 10
C 8
D 12
思路:
由于這顆樹是一個完全二叉樹,想要求出這顆樹的結點個數,那就把它當作一個滿二叉樹看待,2^h - 1就是求出一顆滿叉樹的結點個數,但是完全二叉樹最后一層缺少的結點個數是不確定的,那就把它假設為x
假設這個完全二叉樹的高度為h,那么公式就是這樣的
2^h - 1 - x = 531
繼續做的作業是求出x的范圍【1,2^(h - 1) - 1】,這里x的范圍并不能從0開始,如果從0開始那么最后一層就不存在了,并且他是一個滿二叉樹,就失去完全二叉樹的意義了,那么最后一層結點個數的最小值也求出來了,為1
再來看最大值,滿二叉樹的最后一層結點個數-1得到的就是完全二叉樹的最大結點個數,所以完全二叉樹的結點個數最大是2^(h-1)-1
所以x的范圍【1,2^(h - 1) - 1】也是可以得到驗證的,最后只需要將選項套進去就可以得到最接近x范圍的選項了,答案選B
2^h - 1 - x = 531
2^10 - 1 - x = 531
1024 - 1 - 531 = x
x = 492
5.一個具有767個節點的完全二叉樹,其葉子節點個數為()
A 383
B 384
C 385
D 386
已知結點個數,求出度為0的結點個數
假設度為0的結點用a0表示
假設度為1的結點用a1表示
假設度為2的結點用a2表示
767 = a0 + a1 + a2
根據前面的知識,度為0的結點個數比度為2的結點個數多1,所以計算度為2的結點個數就是a0 - 1,進一步得到下面的公式
767 = a0 + a1 + a0 - 1
度為1的結點個數a1的情況分為兩種,要么是0,要么是1
所以可以進一步推斷公式
767 = 2a0 + a1 - 1
左邊的式子是一個奇數,2a0表示的肯定是一個偶數,那么a0必然是0,這樣子2a0這個偶數減去1得到的就是奇數
768 = 2a0
a0 = 384
3.2 堆的概念及結構
堆的性質:
- Min-heap: 父節點的值小于或等于子節點的值;
- Max-heap: 父節點的值大于或等于子節點的值;
- 堆總是一棵完全二叉樹,

堆的存盤
在物理結構中是以陣列的形式存盤的,在邏輯結構上是一個完全二叉樹,實際在學習和使用的時候都是從邏輯結構為出發點,在 這里會有一個規律,可以通過他的父親計算出孩子的下標位置,這是以下公式:
計算左孩子:parent * 2 + 1
計算右孩子:parent * 2 + 2
通過孩子去計算父親的位置:parent = (child - 1) / 2
parent這個變數表示的是樹的第幾層

堆排序
堆向下調整演算法
- 向下調整演算法的前提是:左子樹和右子樹恰好是小堆
- 向下調整演算法的思想是將父親跟孩子比較,如果小的孩子比父親小,則跟父親交換,而且把原來孩子的位置當成父親繼續往下調整,直到走到葉子結點
- 如果小的孩子比父親大,則不需要處理,調整完成,整個樹已經是小堆

void AdjustDown(int *arr,int n, int parent)
{
//默認左孩子小
int child = parent * 2 + 1;
while (child < n)
{
//如果右孩子比左孩子小,那就走到右孩子
if (arr[child + 1] < arr[child])
{
++child;
}
//比較父子之間的大小關系,小的往上換
if ( arr[child] < arr[parent] )
{
Swap(&arr[child],&arr[parent]);
//將原來孩子的位置給父親,繼續算出新的孩子位置
parent = child;
child = parent * 2 + 1;
}
//已經是小堆了,不需要處理
else
{
break;
}
}
}
面對左右子樹不是小堆的情況下,向下調整演算法的優化
實作思路:如果左右子樹不是堆的情況,使用向下調整演算法肯定沒有規律了,但是可以換一個角度考慮,從最后一個父親位置開始把它作為一個子樹,對它進行向下調整后這個子樹就是小堆了,緊接著找到第二個子樹依次…向下調整,最后左子樹與右子樹之間整體一調整就是一個堆了,已經用序列號標記好,將這幾個圈起來的看作是一個子樹,對每一個子樹向下調整,會得到一個小堆,整體一調整會成一個大堆
代碼實作:
//n-1是最后一個下標的位置,
//已知孩子位置求父親位置的公式是parent = (child - 1) / 2
for(int i = (n - 1 - 1) / 2; i >= 0 ; i--)
{
AdjustDown(arr,n,i);
}
建堆時間復雜度分析

堆排序的思考,為什么排升序要建大堆
1、堆排序要建堆,建堆時間復雜度:O(N)
2、建好堆了選數,堆排序的時間復雜度:O(N log N)
假設排升序建小堆,選出最小的數放到第一個位置,緊接著向下調整選出次小的

結論:堆排序排升序建小堆是沒有意義的
那建大堆呢?

從最后一個父親位置開始把它作為一個子樹,對它進行向下調整后這個子樹就是大堆了,緊接著找到第二個子樹依次…向下調整,最后左子樹與右子樹之間整體一調整就是一個大堆了
建好大堆后再堆排序

利用堆洗掉思想來進行排序
思想:
先選出最大的和最后一個元素交換,再向下調整,再不把最后一個數看成是堆里面的,緊接著選出次大的,和最后一個元素交換,再向下調整,不把最后一個元素看作是堆里面的,依次回圈直到只剩最后一個元素了,就可以認為是有序的了
反之如果是排降序呢?那就是建小堆了,選出最小的數與最后一個元素交換,再向下調整,緊接著不把它看作是堆里面的,再選次小,交換,向下調整,不看做堆元素,反反復復直到只剩最后一個元素就可以是降序了
結論
升序:建大堆,從它的效率上來看已經很優了,
降序:建小堆
堆排序代碼實作
//交換
void Swap(int *data1,int *data2)
{
int tmp = *data1;
*data1 = *data2;
*data2 = tmp;
}
//列印
void print(int *arr,int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ",arr[i]);
}
}
void AdjustDown(int *arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
//右孩子比左孩子大
if (child + 1 < n && arr[child + 1] > arr[child])
{
child++;
}
//孩子大于父親就交換,通過孩子的位置去計算父親的位置,再求父親位置
else if (arr[child] > arr[parent])
{
Swap(&arr[child],&arr[parent]);
parent = child;
child = parent * 2 + 1;
}
//已經是小堆了
else
{
break;
}
}
}
void SortHeap(int *arr, int n)
{
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
//堆排
int end = n - 1;
//end > 0,不需要end >= 0 ,只剩最后一個元素就可以看作是有序的了
while (end > 0)
{
Swap(&arr[end],&arr[0]);
AdjustDown(arr,end,0);
//堆洗掉思想
end--;
}
}
int main()
{
int arr[10] = { 27,15,19,18,28,34,65,49,25,37 };
int n = sizeof(arr) / sizeof(arr[0]);
SortHeap(arr, n);
print(arr,n);
return 0;
}
堆實作
介面宣告
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
#include<memory.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType *a;
int size;
int capacity;
}Heap;
//堆的初始化
void HeapInit(Heap *php, HPDataType *a, int n);
//列印
void Heapprint(Heap *php);
//銷毀
void HeapDestroy(Heap *php);
//向下調整
void AdjustDown(int *arr, int n, int parent);
//向上調整
void AdjustUp(int *a, int child);
//堆排序
void SortHeap(int *arr, int n);
//插入
void HeapPush(Heap *php,int data);
//洗掉資料
void HeapPop(Heap *php);
// 取堆頂的資料
HPDataType HeapTop(Heap* php);
// 堆的資料個數
int HeapSize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);
介面實作
#include"Heap.h"
void Swap(int *data1, int *data2)
{
int tmp = *data1;
*data1 = *data2;
*data2 = tmp;
}
//建堆
void AdjustDown(int *arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && arr[child + 1] > arr[child])
{
child++;
}
//交換孩子和父親
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void SortHeap(int *arr, int n)
{
//建堆,從最后一個父節點開始向下調整
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
//堆排
int end = n - 1;
while (end > 0)
{
Swap(&arr[end], &arr[0]);
AdjustDown(arr, end, 0);
end--;
}
}
//堆的初始化
void HeapInit(Heap *php, HPDataType *a, int n)
{
assert(php);
php->a = (HPDataType *)malloc(sizeof(HPDataType) * n);
if (php->a == NULL)
{
perror("HeapInit::malloc");
exit(-1);
}
php->size = php->capacity = n;
//初始化
memcpy(php->a,a,sizeof(HPDataType) * n);
//建堆
int i = 0;
for (i = (php->size - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(php->a,php->size,i);
}
}
//列印
void Heapprint(Heap *php)
{
assert(php);
int i = 0;
int k = 1;
int pos = 0;
for (i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
if (pos % k == 0)
{
printf("\n");
k *= 2;
pos = 0;
}
pos++;
}
printf("\n");
}
//銷毀
void HeapDestroy(Heap *php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
//向上調整
void AdjustUp(int *a,int child)
{
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (a[child] > a[parent])
{
Swap(&a[child],&a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//插入
void HeapPush(Heap *php, int data)
{
assert(php);
//插入值需要考慮庫容
if (php->size == php->capacity)
{
php->a = realloc(php->a, sizeof(int) * php->capacity * 2);
if (php->a == NULL)
{
perror("HeapInsrt::malloc");
exit(-1);
}
//二倍增長
php->capacity *= 2;
}
//尾插
php->a[php->size] = data;
php->size++;
//為了不改變堆的結構,向上調整
AdjustUp(php->a,php->size - 1);
}
//洗掉資料
void HeapPop(Heap *php)
{
assert(php);
assert(php->size > 0);
//交換第一個和最后一個,洗掉元素,向下調整
int end = php->size - 1;
Swap(&php->a[0], &php->a[end]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
// 取堆頂的資料
HPDataType HeapTop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
// 堆的資料個數
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
// 堆的判空
int HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0 ? true : false;
}
初始化
//堆的初始化
void HeapInit(Heap *php, HPDataType *a, int n)
{
assert(php);
php->a = (HPDataType *)malloc(sizeof(HPDataType) * n);
if (php->a == NULL)
{
perror("HeapInit::malloc");
exit(-1);
}
php->size = php->capacity = n;
//初始化
memcpy(php->a,a,sizeof(HPDataType) * n);
//建堆
int i = 0;
for (i = (php->size - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(php->a,php->size,i);
}
}
初始化堆的程序其實就是一個建堆的程序,用陣列中可利用的元素完成建堆,有了這個堆結構后才能做后面的事情
列印
//列印
void Heapprint(Heap *php)
{
assert(php);
int i = 0;
int k = 1;
int pos = 0;
for (i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
if (pos % k == 0)
{
printf("\n");
k *= 2;
pos = 0;
}
pos++;
}
printf("\n");
}
遍歷一遍堆,為了更醒目地呈現堆結構,這里將代碼控制了一下
銷毀
//銷毀
void HeapDestroy(Heap *php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
釋放動態申請的空間
向下調整建堆
//建堆
void AdjustDown(int *arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
//選出左右孩子的大的那一個
if (child + 1 < n && arr[child + 1] > arr[child])
{
child++;
}
//交換孩子和父親
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
//已經是堆了就不需要再調整
else
{
break;
}
}
}
選出左右孩子大的那一個跟父親交換,孩子的位置給父親繼續計算下一個孩子的位置,這里建的是大堆
向上調整
void AdjustUp(int *a,int child)
{
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (a[child] > a[parent])
{
Swap(&a[child],&a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
如果插入一個值后,需要調整推薦的是向上調整方式,如果是重新建堆的話效率太低了,而向上調整演算法只需要調整一條路徑的值,即使可以插入的值會改變原先的堆結構,但是這個演算法的好處是可以不需要重新建堆
堆排序
//堆排序
void SortHeap(int *arr, int n)
{
//建堆,從最后一個父節點開始向下調整
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
//堆排
int end = n - 1;
while (end > 0)
{
Swap(&arr[end], &arr[0]);
AdjustDown(arr, end, 0);
end--;
}
}
前面已經有詳細的介紹了,這里就不再繼續
插入值
//插入
void HeapPush(Heap *php, int data)
{
//插入值需要考慮庫容
if (php->size == php->capacity)
{
php->a = realloc(php->a, sizeof(int) * php->capacity * 2);
if (php->a == NULL)
{
perror("HeapInsrt::malloc");
exit(-1);
}
//二倍增長
php->capacity *= 2;
}
//尾插
php->a[php->size] = data;
php->size++;
//為了不改變堆的結構,向上調整
AdjustUp(php->a,php->size - 1);
}
插入值后為了保證堆結構不被破壞,又希望希望不用建堆演算法想效率高點,最好的方式是向上調整
洗掉
//洗掉資料
void HeapPop(Heap *php)
{
assert(php);
assert(php->size > 0);
/
int end = php->size - 1;
Swap(&php->a[0], &php->a[end]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
交換第一個和最后一個,洗掉元素,向下調整
取資料
// 取堆頂的資料
HPDataType HeapTop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
獲取元素個數
// 堆的資料個數
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
判斷空
// 堆的判空
int HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0 ? true : false;
}
TOPK問題
劍指 Offer 40. 最小的k個數
鏈接: link.
題目描述:
輸入整數陣列 arr ,找出其中最小的 k 個數,例如,輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4,
實作思路:選出k個數建成堆,再往后比較陣列中的元素,如果陣列中的某個元素小于隊頂元素就進堆,這樣一來堆結構可能會被破壞,需要再次向下調整得到新的堆結構,直到陣列遍歷完最小的k個數也都被篩選出來了
typedef int HPDataType;
typedef struct Heap
{
HPDataType *a;
int size;
int capacity;
}Heap;
//堆的初始化
void HeapInit(Heap *php, HPDataType *a, int n);
//列印
void Heapprint(Heap *php);
//銷毀
void HeapDestroy(Heap *php);
//向下調整
void AdjustDown(int *arr, int n, int parent);
//向上調整
void AdjustUp(int *a, int child);
//堆排序
void SortHeap(int *arr, int n);
//插入
void HeapPush(Heap *php,int data);
//洗掉資料
void HeapPop(Heap *php);
// 取堆頂的資料
HPDataType HeapTop(Heap* php);
// 堆的資料個數
int HeapSize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);
void Swap(int *data1, int *data2)
{
int tmp = *data1;
*data1 = *data2;
*data2 = tmp;
}
//建堆
void AdjustDown(int *arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && arr[child + 1] > arr[child])
{
child++;
}
//交換孩子和父親
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void SortHeap(int *arr, int n)
{
//建堆,從最后一個父節點開始向下調整
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
//堆排
int end = n - 1;
while (end > 0)
{
Swap(&arr[end], &arr[0]);
AdjustDown(arr, end, 0);
end--;
}
}
//堆的初始化
void HeapInit(Heap *php, HPDataType *a, int n)
{
assert(php);
php->a = (HPDataType *)malloc(sizeof(HPDataType) * n);
if (php->a == NULL)
{
perror("HeapInit::malloc");
exit(-1);
}
php->size = php->capacity = n;
//初始化
memcpy(php->a,a,sizeof(HPDataType) * n);
//建堆
int i = 0;
for (i = (php->size - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(php->a,php->size,i);
}
}
//列印
void Heapprint(Heap *php)
{
assert(php);
int i = 0;
int k = 1;
int pos = 0;
for (i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
if (pos % k == 0)
{
printf("\n");
k *= 2;
pos = 0;
}
pos++;
}
printf("\n");
}
//銷毀
void HeapDestroy(Heap *php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
//向上調整
void AdjustUp(int *a,int child)
{
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (a[child] > a[parent])
{
Swap(&a[child],&a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//插入
void HeapPush(Heap *php, int data)
{
assert(php);
//插入值需要考慮庫容
if (php->size == php->capacity)
{
php->a = realloc(php->a, sizeof(int) * php->capacity * 2);
if (php->a == NULL)
{
perror("HeapInsrt::malloc");
exit(-1);
}
//二倍增長
php->capacity *= 2;
}
//尾插
php->a[php->size] = data;
php->size++;
//為了不改變堆的結構,向上調整
AdjustUp(php->a,php->size - 1);
}
//洗掉資料
void HeapPop(Heap *php)
{
assert(php);
assert(php->size > 0);
//交換第一個和最后一個,洗掉元素,向下調整
int end = php->size - 1;
Swap(&php->a[0], &php->a[end]);
--php->size;
AdjustDown(php->a, php->size, 0);
}
// 取堆頂的資料
HPDataType HeapTop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
// 堆的資料個數
int HeapSize(Heap* php)
{
assert(php);
return php->size;
}
// 堆的判空
int HeapEmpty(Heap* php)
{
assert(php);
return php->size == 0 ? true : false;
}
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize){
if(k == 0)
{
*returnSize = 0;
return NULL;
}
//選出k個數建堆,再往后比較陣列中的元素,如果小于隊頂元素就進堆,再向下調整
Heap h;
int* retArr = (int *)malloc(sizeof(int) * k);
for(int i = 0; i < k; i++)
{
retArr[i] = arr[i];
}
for(int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(retArr,k,i);
}
for(int i = k; i < arrSize; i++)
{
if(retArr[0] > arr[i])
{
retArr[0] = arr[i];
AdjustDown(retArr,k,0);
}
}
*returnSize = k;
return retArr;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/325648.html
標籤:其他
上一篇:LeetCode 476. 數字的補數 利用 熟悉題目的解法 對 陌生問題進行解決
下一篇:教你優雅的處理網頁中的圖片









