日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

通俗易懂的讲解堆排序(含Gif图)

發布時間:2024/2/28 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通俗易懂的讲解堆排序(含Gif图) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

堆的定義

堆排序是一種樹形結構選擇排序方法,其特點是:在排序過程中,將序列視為一顆完全二叉樹的順序存儲結構,利用完全二叉樹中雙親結點和孩子結點之間的關系,在當前無序區間中選擇關鍵字最大(或最小)的元素。堆的定義如下:

n 個關鍵字序列 L[1,2,3...n] 稱為堆,當且僅當該序列滿足:

① L(i) <= L(2i) 且 L(i) <= L(2i+1)? ? ? ? ?② L(i) >= L(2i) 且 L(i) >= L(2i+1)? ? ? ? ? ? ? ? ? ?(1 <= i <= ?n/2?)

滿足第一種情況的堆稱為小根堆(或小頂堆),滿足第二種情況的堆稱為大根堆(大頂堆)。在大根堆中,最大的元素存放在根結點中。對其任一非根結點,它的值小于等于其父親結點的值。小根堆的定義剛好相反,根結點是最小元素。建立一個堆有兩種方法,很多書籍把這兩種方法稱為向上調整和向下調整:

向下調整是讓調整的結點與其孩子結點進行比較
向上調整是讓調整的結點與其父親結點進行比較

?

向上調整

向上調整需要從根結點開始建堆(以小根堆為例),基本思路:每添加一個元素,將該元素與其父結點進行比較,若新增結點小于父結點,則交換兩個結點的值,然后繼續與上一級的父結點進行比較。停止向上調整的條件是該結點大于父結點的值。現有序列 list [ ] = {87, 45, 78, 32, 17, 65, 53, 9} 分別用向上調整和向下調整進行建堆,看看這兩種方法有什么區別。

向上調整建堆的過程:

添加元素 9 的向上調整 gif:

向上調整代碼:

void AdjustUp(int a[],int size) { //size是數組目前的大小int temp,flag=0;if(size==1)return; //此時是堆頂,不需要上調while(size!=1 && flag==0) {if(a[size] < a[size/2]) //與父結點進行對比{temp = a[size];a[size] = a[size/2];a[size/2] = temp;}else{flag = 1;}size = size/2; //更新編號i為它父結點的編號。} }void AdjustUp1(int a[], int k) {//參數k為向上調整的結點,也是堆的元素個數if(k == 1)return;a[0] = a[k];int i = k/2;while(i > 0 && a[i] > a[0]) //若結點值小于父結點,則將父結點向下調{a[k] = a[i]; //父節點向下調k = i;i = k/2; //繼續向上比較}a[k] = a[0]; }

?

向下調整

向下調整建堆是一個反復篩查的過程。n 個結點的完全二叉樹,最后一個結點是第?n/2?個結點的孩子(完全二叉樹的性質)。對第?n/2?個結點為根的子樹篩查(對于小根堆,若結點的關鍵字大于左右孩子中關鍵字較小者,則交換),使該子樹成為堆。之后向前依次對各結點(?n/2?-1 ~ 1)為根的子樹進行篩選,看該結點值是否小于其左右子結點的值,若不小于,則將左右子結點中的較小值與之交換,交換后可能會破壞下一級的堆,于是繼續采取上述方法構造下一級的堆,直到以該結點為根的子樹構成堆為止。反復利用上述調整方法建堆,直到根結點。根據原始數據建立的樹形結構如下:

根據向下調整的思路,從第 4 個結點開始進行篩查建立小根堆:

向下調調整思路:將該結點與其孩子結點進行比較,選取其中最小進行交換,若該節點本身就是最小的結點,則不需要進行交換操作。如上圖,需要將 9 和 32 進行交換。然后對第 3 個結點進行調整,該過程較為簡單,所以省略。接下來展示第 2?個結點的調整情況。

對第 2 個結點進行調整時,需要交換 9 和 45。但是交換后發現 32 比 45 要小,所以還需要進行一次交換操作。該過程告訴我們,在交換父子結點后,子結點的堆屬性可能被破壞,所以要對子結點進行調整(直至子結點為葉子結點為止)。以下是第 2 個結點最終的調整情況:

向下調整的最終形態:

向下調整 gif:

向下調整代碼:

void heap_ajust_min(int *b, int i, int size) //a為數組,size為堆的大小 {int lchild = 2*i; //i的左孩子節點序號 int rchild = 2*i +1; //i的右孩子節點序號 int min = i; //記錄根和左右子樹中最小的數的下標int temp;if(i <= size/2) //調整不需要從葉結點開始{if(lchild<=size && b[lchild]<b[min]){min = lchild;} if(rchild<=size && b[rchild]<b[min]){min = rchild;} //兩個if語句尋找三個結點中最小的數if(min != i) //如果min不等于i,說明最小的值在左右子樹中{temp = b[i]; //交換a[i]和a[min]的值b[i] = b[min];b[min] = temp;heap_ajust_min(b, min, size); //被交換的子樹可能不滿足堆的定義,需要對被交換的子樹重新調整}} }void build_heap_min(int *b, int size) //建立小根堆 {int i;for(i = size/2; i >= 1; i--) //非葉子節點最大序號值為size/2,從這個結點開始調整{ //注意for中的循環條件(i = size/2; i >= 1; i--)heap_ajust_min(b, i, size); } }

向下調整的時間與樹的高度有關,為 O(h)。建堆過程中每次向下調整時,大部分結點的高度都比較小。我們很容易會發現向上調整和向下調整的最終結果有所不同,但都滿足了小根堆的性質。向上調整和向下調整都能夠完成建立堆的工作。區別在于,使用向上調整建堆需要從第一個元素開始,過程相當于每次插入一個元素,然后再進行向上調整的工作。向下調整充分的利用了完全二叉樹的性質,從?n/2?結點到根結點逐一篩查。它們的具體操作如下:

for(k = 1; k < size ; ++k) //向上調整建堆 {AdjustUp(b, k); }for(i = size/2; i >= 1; i--) //向下調整建堆 {heap_ajust_min(b, i, size); }

?

堆排序

應用堆進行排序的思路很簡單:首先將存放在 L[1,2,3...n] 中的 n 個元素建成初始堆,由于堆本身的特點,堆頂元素就是最值。輸出堆頂元素后,通常將堆底元素送入堆頂,此時根結點不滿足堆的性質,經過向下調整后使其恢復堆的性質,再輸出堆頂元素。重復操作,直至最后一個元素為止。

堆排序代碼:

void heap_sort_min(int *a, int size) {int i;int temp;for(i = size; i >= 1; i--){temp = a[1];a[1] = a[i];a[i] = temp; //交換堆頂和最后一個元素heap_ajust_min(a, 1, i-1); //再一次調整堆頂節點成為小頂堆} }

利用上述小根堆代碼完成排序,會發現序列是倒序的,這是因為排序過程是從后往前不斷調整小根堆的結果。如果想要獲得升序序列,需要使用大根堆進行排序。

堆支持刪除和插入操作,刪除元素時,需要將最后一個元素放到堆頂,然后使用向下調整,使堆保持堆的特性。對堆進行插入操作時,先將新結點放在堆的末端,再對這個新結點進行向上調整操作。注意:插入元素時(小根堆為例),只需要調用一次向上調整函數便能恢復堆的屬性。因為在插入之前,堆本身是完整的。此時若插入元素小于父結點的值,那么只需要交換兩個結點而不需要考慮別的情況。這是因為之前滿足堆性質,而插入了一個更小的值,交換后堆屬性不變。具體可以看添加 9 的時候的 gif。可以根據需求,將向上調整和向下調整搭配著使用。例如:小根堆刪除元素后使用向下調整恢復堆的性質。

完整代碼:

#include<stdio.h> #include<math.h>void heap_ajust_min(int *b, int i, int size) /*a為堆存儲數組,size為堆的大小*/ {int lchild = 2*i; //i的左孩子節點序號 int rchild = 2*i +1; //i的右孩子節點序號 int min = i; /*存放三個頂點中最大的數的下標*/int temp;if(i <= size/2) //假設i是葉節點就不用進行調整 {if(lchild<=size && b[lchild]<b[min]){min = lchild;} if(rchild<=size && b[rchild]<b[min]){min = rchild;}if(min != i){temp = b[i]; /*交換a[i]和a[max]的值*/b[i] = b[min];b[min] = temp;heap_ajust_min(b, min, size); /*被交換的位置曾經是大根堆,如今可能不是大根堆所以須要又一次調整使其成為大根堆結構*/ }} }void build_bheap_min(int *b, int size) //建立小堆 {int i;for(i=size/2; i >= 1; i--) //非葉節點最大序號值為size/2{heap_ajust_min(b, i, size); } }void heap_sort_min(int *a, int size) {int i;int temp;for(i = size; i >= 1; i--){temp = a[1];a[1] = a[i];a[i] = temp; //交換堆頂和最后一個元素heap_ajust_min(a, 1, i-1); //再一次調整堆頂節點成為小頂堆} } void AdjustUp(int a[],int size) {int temp,flag=0;int k;if(size==1)return;//此時是堆頂,不需要上調while(size!=1 && flag==0){if(a[size] < a[size/2]){temp = a[size];a[size] = a[size/2];a[size/2] = temp;}else{flag = 1;}size = size/2;//更新編號i為它父結點的編號。} }void AdjustUp1(int a[], int k) {//參數k為向上調整的結點,也是堆的元素個數if(k == 1)return;a[0] = a[k];int i = k/2;while(i > 0 && a[i] > a[0]) //若結點值小于父結點,則將父結點向下調{a[k] = a[i]; //父節點向下調k = i;i = k/2; //繼續向上比較}a[k] = a[0]; }int main(int argc, char *argv[]) { int i,j,k;int count=1;int a[]={0, 87, 45, 78, 32, 17, 65, 53, 9};int b[]={0, 87, 45, 78, 32, 17, 65, 53, 9};int size = sizeof(a)/sizeof(int) -1;build_bheap_min(a, size);printf("向下調整建立小頂堆:\n"); count=1;for(i=0;i<=4;i++){for(j=0;j<pow(2,i);j++){if(count<=size){printf("%d ",a[count++]);}else{break;}}printf("\n");}for(k = 1; k < 9 ; ++k){AdjustUp(b, k);}printf("向上調整小頂堆:\n"); count=1;for(i=0;i<=4;i++){for(j=0;j<pow(2,i);j++){if(count<=size){printf("%d ",b[count++]);}else{break;}}printf("\n");}return 0; }

?

總結

以上是生活随笔為你收集整理的通俗易懂的讲解堆排序(含Gif图)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。