最大(小)堆---陣列的實現
最大堆是我在看浙大陳越老師的資料結構視訊學到的,用的陣列實現的
如何描述堆呢?用我自己的話說就是:
根是這顆樹最大的值,每個 根節點都比 左右子節點的值大, 對左右子樹仍然成立;
那麼最小堆呢正好相反:
根是這顆樹的最小的值, 每個 根節點都比左右子節點的值小, 同樣對左右子樹成立;
圖能最直觀的體現:
我們很容易看出來,堆的左右子樹仍然是堆。
(圖摘自CSDN)所以無論對於最大堆,最小堆,同一層之間的大小順序是不確定的
那麼現在說,如何建立一顆這樣的堆呢?
1、在說堆之前相必大家都瞭解完全二叉樹,以及陣列下標(從1開始)和完全二叉樹節點位置的技巧(即假如說根節點是i,那麼它的左兒子=2*i, 右兒子=2*i+1),堆就是利用完全二叉樹建立的,這裡不理解沒關係,因為答案在下邊。
現在,我們說假如說有這麼一棵樹:
假如說現在在右下角插入100,那麼,很明顯就不符合堆的概念了,怎麼調整呢?
如果 它比根節點大,就讓它和根節點調換位置
那麼這顆樹如果是上面大根堆裡面的一顆子樹呢?
還是:如果 它比根節點大,就讓它和根節點調換位置
直到調換到整棵樹 根的位置,或者現在已經比根節點要小了
(已經滿足堆的概念了)
當然最小堆道理是相同的
以上是 當前一顆最大堆,如何往裡面插值,那麼現在我最想知道的是如何構建一個最大堆,你跟我扯插入扯這麼半天干什麼呢?那麼如果咱們是把一組資料往一個空堆裡面插呢?哈哈哈
2、咱們繼續往下說:
如果現在給你一個最大堆:
刪除堆的最大值:你怎麼辦呢?
把堆頂刪了(也就是根),為了保證他還是一顆完全二叉樹,就把最下面一層最右邊一個放到堆頂。
為了要讓它還滿足堆的要求,那麼就跟它的左右子節點中最大的交換值(就是說現在80在樹頂,10為左兒子)
現在目標轉移到左子樹即:
重複操作。
也就是實現了資料的刪操作
那麼我又是在扯皮了,建樹呢?建樹呢?!建樹呢?!!!
3、現在正式來說一下建樹的問題(。。。你終於說到建樹了):
現在給你一組資料,如果你直接放到數組裡面是這個樣子(從下標1開始):
根據陣列下標與完全二叉樹節點的關係,構成的二叉樹是這個樣子:
我們知道刪除堆頂時把最下面的值覆蓋堆頂元素,經過操作讓能成為堆,是因為它的左右子樹仍然是堆。
那麼我們現在對26這個節點做如下調整:
~~1、如果 它比左右子節點的最大值小, 就跟較大子節點交換值
~~2、目標移到交換的子節點
~~3、否則滿足堆的概念
然後對6節點做上面的~~ 1 ~~2 ~~3 調整,
然後對15節點做~~ 1 ~~2 ~~3 調整。。。
最後就行成如下堆:
over,以下是程式碼實現:
#include<cstdio>
#include<iostream>
#include<cstdlib>
#define MAXDATA 100000000
#define MAXSIZE 10005
using namespace std;
typedef struct HeapChain
{
int *Element;
int sizee;
int capacity;
} HeapChain, *HeapChain_idx;
HeapChain_idx H;
///建造一個空的堆
void Creat_Heap(HeapChain_idx &H)
{
H = (HeapChain_idx) malloc(sizeof(HeapChain));
H->Element = (int *)malloc(sizeof(MAXSIZE * sizeof(int)));
H->Element[0] = MAXDATA; ///Element[0]的位置做哨兵,防止插入時越界
H->sizee = 0;
H->capacity = MAXSIZE - 3;
return ;
}
int In_Heap(HeapChain_idx H, int x)
{
if(H->sizee >= H->capacity)
return 0;
int i = ++H->sizee;
for( ; H->Element[i/2] < x; i /= 2) ///插入的數只要比父節點大,父節點的值就往下移
H->Element[i] = H->Element[i/2];
H->Element[i] = x;
return 1;
}
int Remove_Heap(HeapChain_idx H, int &e)
{
if(H->sizee == 0)
return 0;
e = H->Element[1];
H->Element[1] = H->Element[H->sizee--]; ///用最後一個數將堆頂覆蓋
int parent, child;
for(parent = 1; parent * 2 <= H->sizee; parent = child) ///將堆頂數往滿足堆定義的位置移動
{
child = parent * 2;
if(child < H->sizee && H->Element[child] < H->Element[child+1]) ///找出左右子節點的較大者
child++;
if(H->Element[parent] < H->Element[child]) ///如果比子節點最大者小就往下移動
{
int t;
t = H->Element[parent], H->Element[parent] = H->Element[child], H->Element[child] = t;
}
else ///否則就比兩個子節點都大或相等,滿足堆定義。調整完成
break;
}
return 1;
}
int Adjust_Heap(HeapChain_idx H)
{
if(H->sizee == 0)
return 0;
for(int k = H->sizee/2; k > 0; k--) ///從最後一個節點的父節點調整使滿足堆定義,調整至堆頂
{
int parent, child;
for(parent = k; parent * 2 <= H->sizee; parent = child) ///如果當前節點要比最大的子節點要小,就往下移,相等不用移動
{
child = parent * 2;
if(child < H->sizee && H->Element[child] < H->Element[child+1])
child++;
if(H->Element[parent] < H->Element[child])
{
int t;
t = H->Element[parent], H->Element[parent] = H->Element[child], H->Element[child] = t;
}
else
break;
}
}
return 1;
}
int main()
{
int n, e;
HeapChain_idx H;
///測試建堆
scanf("%d", &n);
Creat_Heap(H);
for(int i = 1; i <= n; i++)
scanf("%d", &H->Element[i]);
H->sizee = n;
int flag;
flag = Adjust_Heap(H);
if(flag == 0)
printf("堆為空\n");
else
for(int i = 1; i <= H->sizee; i++)
printf("%d ", H->Element[i]);
printf("\n\n\n\n");
///測試刪除的堆頂元素:
flag = Remove_Heap(H, e);
printf("被刪除的數字為:%d\n", e);
if(flag == 0)
printf("堆為空\n");
else
for(int i = 1; i <= H->sizee; i++)
printf("%d ", H->Element[i]);
printf("\n\n\n\n");
///測試插入一個數:
printf("請輸入要插入的數字\n");
scanf("%d", &e);
flag = In_Heap(H, e);
if(flag == 0)
printf("堆已經滿了\n");
else
for(int i = 1; i <= H->sizee; i++)
printf("%d ", H->Element[i]);
printf("\n\n\n\n");
return 0;
}