關於堆

你的小垃圾發表於2022-05-27

今天講的堆,簡單記錄一下:

堆,是一棵完全二叉樹,因此設當前節點編號為 i,則其父結點編號為 i/2,左兒子編號為 2*i,右兒子編號為 2i+1

堆有兩種比較特別:

一種是大根堆,一種是小根堆

由他們樸實的名字可知,大根堆就是每個節點都小於父親節點的堆,小根堆反之。

關於堆的操作有兩個比較常用的,一個是put(),另一個是get()。

 

put()演算法介紹

put操作就是往堆裡插入一個元素,我們如果硬插的話會有悖於堆的性質,所以要用能夠維護堆的方法:

拿小根堆舉例:在堆的末尾加入元素,並將該節點當成當前節點。然後比較當前節點與他的父親的大小,如果當前節點小於其父節點。就將這兩個節點位置互換,並持續此操作,直到當前節點不小於其父節點(等於不等於的都可以),以維護小根堆的性質,大根堆亦然。

程式碼如下:

 1 void put(int d)//展示的是小根堆的操作,大根堆亦然
 2 {
 3     int son,pa;
 4     heap[++heap_size]=d;
 5     son=heap_size;
 6     while(son>1)//到頭了就不能繼續了 
 7     {
 8         pa=son>>2;
 9         if(heap[son]>=heap[pa])//如果兒子本來就比他爹大,滿足性質,直接break; 
10         {
11             break;
12         }
13         swap(heap[son],heap[pa]);//將爹與兒子進行交換 
14         son=pa;//讓兒子的編號變成他爹的,再進行下一步操作 
15     }
16 }

當然,使用C++的標準模板庫STL也是可以的:

1 #include<algorithm>//別忘了加STL的標頭檔案 
2 void put(int d)
3 {
4     heap[++heap_size]=d;
5     push_heap(heap+1,heap+heap_size+1,greater<int>());//小根堆
6     push_heap(heap+1,heap+heap_size+1);//大根堆 
7 } 

 

get()演算法介紹

put就是從堆中取出並刪除元素的操作:

拿小根堆舉例:

先將堆的根節點與堆尾元素互換,接著將對堆中元素的個數減1.

接著將當前的根節點視作pa比較他的兩個兒子的大小(如果他有兩個兒子)並選出較小的那個與父親互換,一直持續此操作,直到又變成一個新的小根堆。大根堆亦然。

程式碼如下:

 1 int get()//This is 小根堆 
 2 {
 3     int pa,son,res;
 4     res=heap[1];//堆頂馬上就要離開了,趕緊記錄一下 
 5     heap[1]=heap[heap_size--];//先將堆頂的元素與堆尾的元素進行交換,再將堆中元素個數減一 
 6     pa=1;
 7     while(pa*2<=heap_size)//父親是不能變成葉節點的歐 
 8     {
 9         son=pa*2;
10         if(son<heap_size && heap[son+1]<heap[son])//為了取最小的孩子 
11         {
12             son++;
13         }
14         if(heap[pa]<=heap[son]) break;
15         swap(heap[pa],heap[son]);
16         pa=son;
17     }
18     return res;
19 } 

還有就是C++標準模板庫的STL:

1 #include<algorithm>
2 int get()
3 {
4     pop_heap(heap+1,heap+heap_size+1,greater<int>());//小根堆 
5     pop_heap(heap+1,heap+heap_size+1);//大根堆
6     return heap[heap_size--]; 
7 } 

 

我們由此可以將其運用起來:

1、堆排序:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstdio>
 4 using namespace std;
 5 int heap_size,n;
 6 int heap[100001];
 7 void swap(int &a,int &b)
 8 {
 9     int t=a;
10     a=b;
11     b=t;
12 }
13 void put(int d)
14 {
15     heap[++heap_size]=d;
16     push_heap(heap+1,heap+heap_size+1,greater<int>());
17 }
18 int get()
19 {
20     pop_heap(heap+1,heap+heap_size+1,greater<int>());//小根堆 
21     return heap[heap_size--]; 
22 } 
23 int ans;
24 void work()
25 {
26     int x,y;
27     cin>>n;
28     for(int i=1;i<=n;i++)
29     {
30         cin>>x;
31         put(x);
32     }
33     for(int i=1;i<=n;i++)
34     {
35         cout<<get()<<' ';
36     }
37 }
38 
39 int main()
40 {
41     work();
42     return 0;
43 }

 

2、合併果子

 

 其實就是將堆進行排序,將頭兩個元素相加成一個元素並放在堆尾,ans+=兩數之和:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstdio>
 4 using namespace std;
 5 int heap_size,n;
 6 int heap[100001];
 7 void swap(int &a,int &b)
 8 {
 9     int t=a;
10     a=b;
11     b=t;
12 }
13 void put(int d)
14 {
15     heap[++heap_size]=d;
16     push_heap(heap+1,heap+heap_size+1,greater<int>());
17 }
18 int get()
19 {
20     pop_heap(heap+1,heap+heap_size+1,greater<int>());//小根堆 
21     return heap[heap_size--]; 
22 } 
23 int ans;
24 void work()
25 {
26     int x,y;
27     cin>>n;
28     for(int i=1;i<=n;i++)
29     {
30         cin>>x;
31         put(x);
32     }
33     for(int i=1;i<n;i++)
34     {
35         x=get();
36         y=get();
37         ans+=x+y;
38         put(x+y);
39     }
40     cout<<ans;
41 }
42 
43 int main()
44 {
45     ios::sync_with_stdio(false);
46     work();
47     return 0;
48 }

就先到這吧,再見啦!