堆和堆排序

堆和優先隊列

普通隊列:FIFO,LILO

優先隊列:出隊順序和入隊順序無關,和優先級相關。一個典型應用就是操作系統中。動態選擇優先級高的任務執行

堆的實現

最典型的堆就是二叉堆,就像是一顆二叉樹。這個堆的特點,下圖可以看出:

這里以最大堆為例,,每一個節點都不大于其父親節點。另外,堆必須是一顆完全二叉樹,正因為此,我們可以使用數組來存儲二叉堆如下圖所示,給二叉堆自上而下,自左到右表上序號,

由圖中節點序號,可以看出,如果某個節點的序號為k,則其左子節點的序號是2*k,右子節點的序號是2*k+1,這里,與通常我們數組的規定不同,根節點是從1開始的,不是0,這也是堆的經典實現方式。不過從0開始標定,也會有類似性質,只是常數的變化。

下面就要實現最大堆,做成一個MaxHeap 類,最大堆中要存儲數據,為了通用性,將這個類做成一個模板類。這個最大堆首先得有一個存儲數據的數組,在用戶定義之前,我們不知道數組的大小,所以該數組是一個指針類型,相應的會在構造函數中初始化該數組。還需要一個int型的size來表示堆中元素數量。所以堆的基本框架如下:

 1 template<typename Item>
 2 class MaxHeap{
 3 private:
 4     Item* data;
 5     int count;
 6 private:
 7     int shiftDown(int k){
 8 
 9     }
10 public:
11     MaxHeap(int capacity){
12         data = new Item[capacity];
13         count=0;
14     }
15     ~MaxHeap(){
16         delete[] data;
17     }
18 
19     int size(){return count;}
20 
21     bool isEmpty(){return count==0;}
22 };

?向堆中添加、刪除元素

添加元素

因為堆的性質,我們這里要用到一個添加元素時的核心操作。、;shiftUp,用下面的堆具體說一下思路

? ??

?

?

?

??

?

??

?因為是用數組存儲的堆中的元素,最初52加入的時候如第二幅圖所示,由于新元素的加入,這個堆已經不滿足最大堆的性質了,我們需要根據最大堆的性質調整,給52找到正確的位置,如何調整呢?:考查52的父節點,比52小,違背了最大堆的性質,所以交換之,這樣以52為根節點的子堆滿足了最大堆的性質,繼續考查新的堆中52的父節點,依然不滿足最大堆的定義,同樣的操作,繼續考查,直到整個堆滿足最大堆的性質。那么添加元素后只需要不斷調用shiftUp操作就可以了,具體代碼實現

 1 // insert(Item)
 2  2 void insert(Item item){
 3  3         data[count+1] =item;
 4  4         count++;
 5  5         shiftUp(count);
 6  6     }
 7  7 
 8  8 // shiftUp(int)
 9  9 
10 10 void shiftDown(int k){
11 11         while(data[k/2]<data[k] && k>1){
12 12             swap(data[k],data[k/2]);
13 13             k/=2;
14 14         }
15 15     }

但是,?data[count+1] =item;?有一個數組越界的潛在風險,所以,我們需要在類中再定義一個capacity的成員變量,并在構造函數中使用用戶指定的capacity初始化?this->capacity = capacity;?并且,在添加新元素之前assert一下,在insert(Item)函數中第三行之前加入?assert(count+1<=capacity);?當然,更好的方法就是一旦發現capacity不足夠,就分配新的空間,C++primer中在講容器的時候提到過,一般是采用倍增的方法,這里主要講堆,我自己也沒研究過,這里先Mark下,以后仔細想想具體實現,此處就先用這種簡單的方法防止數組越界。

從堆中取出元素

Note: 從堆中取出元素只能取出根節點的元素。

一旦取出對頂元素,就需要調整堆,使得堆這顆二叉樹依然滿足堆的性質。還是以圖的形式給出過程。

?

?

??

?

??

?

??

?

?

?

?由上面過程可以看出,一旦取出堆頂元素,就把堆中最后一個元素放到堆頂,不斷調整,直到這顆二叉樹再次滿足最大堆的性質。這個不斷調整的過程就是shiftDown 的過程。簡單說一下這個過程。現在16處在堆頂位置,比它的左右子節點都要小,最大堆的性質要求父親節點要大于子節點,所以,應該調整,向左還是向右是有左右的大小決定的,誰大跟誰換,這樣16跟52換,然后再考查新的堆,繼續考查16,直到16在它正確的位置。具體實現如下:

 1 //shiftDown(int);
 2 void shiftDown(int k){
 3         //int j = 2*k;
 4         while(2*k<=count){
 5             int j = 2*k;
 6             if(data[j]<data[j+1])
 7                 j++;
 8             if(data[k]<data[j]){
 9                 swap(data[k],data[j]);
10                 k = j;
11             }
12         }
13     }
14 
15 //get the top of heap Item top()
16 Item get(){
17         assert(count>0);
18         Item item  = data[1];
19         swap(data[1],data[count])
20         count--;
21         //swap(data[])
22         shiftDown(1);
23         return item;
24     }

前面給出了取出對頂元素的方法,如果,不斷取出堆頂元素并打印出來,就是一個從大到小的數組,由此,可以想到利用堆進行排序,這個排序接收一個數組和數組元素個數,創建一個heap類的對象,通過這個對象調用insert()函數和top() 函數即可實現,具體實現

1 void heap_sort(int arr[],int n){
2     MaxHeap<int> maxHeap = MaxHeap(n);
3     for(int i = 0;i<n;i++)
4         maxHeap.insert(arr[i]);
5     for(int i = 0;i<n;i++)
6         arr[i] = maxHeap.top();
7 }

上面實現的是從大到小的排序,要想從小到大排序,反向打印就可以,只需將第5行改為?for(int i = n-1;i>=0;i--)?

堆的heapify

前面提到了最大堆排序,我將上面的堆排序實現方式與歸并排序和快速排序時間做了一個比較,前面的堆排序的方式花費的時間較長,回顧一下,前面的堆排序需要將數組元素一個一個的插入堆中,利用堆不斷的調整,從堆中取出元素的時候也需要不斷的調整,使得二叉樹依然保有最大堆的性質,這種方式的效率顯然不高,說道這里,我想起來了,不斷調整過程中需要不斷的shiftUp和shiftDown,這兩個操作都需要swap()操作,前面講插入排序的時候提到過,swap操作相對于移動/賦值操作是低效的,所以,這里也是可以改進的,不過下面要說的是改進數組構成堆的方式,給定一個數組,我們讓這個數組形成一個堆的形狀,這個過程叫--Heapify.還是以圖片形式演示過程,下圖只給出了一部分過程的圖示

?

?

?

?

?

??

?

?

?看第一幅圖,右下角是一個數組,將該數組構建成一顆完全二叉樹,但是,這顆二叉樹不是堆,如果將這顆完全二叉樹根據對的性質調整成堆,那么,就將數組構造成了堆,調整過程就是上圖示過程

看第二幅圖中所有葉子節點,它們本身各自就是一個堆,還是以最大堆為例,一個最大堆,Note:一顆完全二叉樹,第一個非葉子節點是5,元素數目為10,可以多舉些例子,可以找出規律,一顆完全二叉樹第一個非葉子節點是k/2取整(k為完全二叉樹節點個數),這里是上取整,并且是從1開始而不是從0開始(可以證明的)。現在自下而上考查非葉子節點,第一個是5,它的值是32,以它為根的子樹不滿足最大堆的性質,它比子節點小,所以做一次shiftDown操作,接著考查4位置,13也比子節點小,再執行一次shiftDown操作,接著考查位置3,也不滿足,繼續執行shiftDown,再看2 ,17比它的子節點小,shiftDown,但是調整到5上,依然不滿足,繼續shiftDown,直到17在正確的位置上, 這時,以62為根的子樹滿足了最大堆性質,接著向上一層,樹頂元素不滿足,shiftDown,直到調整到15應該在的位置。可以構建一個構造函數接收一個數組和數組容量,代碼實現如下

1 MaxHeap(Item arr[],int n){
2         data = new Item[n];
3         capacity = n;
4         for(int i =0;i<n;i++)
5             data[i+1] = arr[i];
6         count = n;
7         for(int k = n/2;k>0;k--)
8             shiftDown(k);
9     }

使用這個構造函數進行heapsort

1 void heap_sort2(int arr[],int n){
2     MaxHeap<int> maxHeap = MaxHeap(arr,n);
3     for(int i = n-1;i>=0;i--)
4         arr[i] = maxHeap.top();
5 }

再次測試,時間性能上比前面的堆排序快,但依然是比歸并排序和快速排序慢,不過堆排序通常用于動態數據的維護,而不是系統級別的排序。

將n個元素逐個插入到一個空堆中,時間復雜度是O(NlogN)的

但是heapify的過程,算法復雜度是O(N),這個,我沒有證明。只是看書上寫的

原地堆排序

前面介紹的堆排序算法都需要將數據從數組放入堆中,再從堆中取出。這需要分配額外的空間,但是,根據堆排序的思想,整個數組的排序過程可以原地進行,不需要再分配額外空間。回想之前通過數組構造堆的過程可以看出一個數組其實可以把它看成一個堆,因此可以將數組通過heapify過程構建成堆,還是以最大堆為例。?這是一個數組形式排列的最大堆,堆頂元素v為整個數組中最大的,按照排序后的結果,v應該在數組尾部,所以將v與最后一個元素w交換,交換后這個數組不再是一個最大堆(橙色部分不再是最大堆),如何調整成最大堆,根據前面的,就是不斷的調用shiftDown,調整成一個最大堆(依然用暗紅色表示該部分是最大堆),藍色表示已經排序好的。

?

?

?

???

? 此時又重復上面的過程,繼續交換v和w,然后將未排序的部分調整成最大堆。

Note:由于整個過程在數組上原地進行,數組是從0開始索引的,所以在實現的時候注意調整,主要是shiftDown過程的索引,具體的就是父親節點與子節點索引的關系,舉個例子很容易看出來

最后一個非葉子節點同時也變成了?(count-1)/2?,依然是上取整。

實現代碼如下:

?

 1 void heapSort(int arr[],int n){
 2     //heapify
 3     // index begins with 0
 4     // the first leaf node which is not null (count-1)/2
 5     for(int i = (n-1)/2;i>=0;i--)
 6         __shiftDown(arr,n,i);
 7     for(int i = n-1;i>=0;i--){
 8         swap(arr[i],arr[0]);
 9         __shiftDown(arr,i,0);
10     }
11 }
12 
13 void __shiftDown(int arr[],int n,int k){
14     while(2*k+1<n){
15         int j = 2*k+1;
16         if(arr[j]<arr[j+1] && j+1<n)
17             j+=1;
18         if(arr[k]<arr[j]){
19             swap(arr[k],arr[j]);
20             k = j;
21         }
22     }
23 }

優化

之前提到過,用賦值操作代替交換操作會提升時間效率,在這里實現

 1 void __shiftDown2(int arr[],int n,int k){
 2     int e = arr[k];
 3     while(2*k+1<n){
 4         int j = 2*k+1;
 5         if(arr[j]<arr[j+1])
 6             j+=1;
 7         if(e>=arr[j]) break;
 8         arr[k] = arr[j];
 9         k = j;
10     }
11     arr[k] = e;
12 }

排序算法總結

到這里,所有排序算法都寫完了,下面的圖是我在GitHub上看到的關于排序算法的總結

這里,有一個排序算法穩定性的概念,之前沒有提到過

算法穩定性:對于相等的元素在排序后,原來靠前的元素依然靠前,相等的元素的相對位置沒有發生改變

我覺得他給出的這個解釋的前半句非常好理解,對于算法穩定性。

?

轉載于:https://www.cnblogs.com/Holly-blog/p/9321011.html

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

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

相關文章

ES5-1 發展史、ECMA、編程語言、變量、JS值

1. 5大主流瀏覽器及內核&#xff08;自主研發&#xff09; 瀏覽器內核IEtridentChromewebkit blinkSafariwebkitFirefoxgeckoOperapresto 2. 瀏覽器的歷史 和 JS誕生 1989-1991 WorldWideWeb&#xff08;后來為了避免與萬維網混淆而改名為Nexus&#xff09;是世界上第一個網頁…

javascript --- 使用對象關聯簡化整體設計

在某個場景中,我們有兩個控制器對象: 1.用來操作網頁中的登錄表單; 2.用來與服務器進行通信. 類設計模式 // 把基礎的函數定義在名為Controller的類中,然后派生兩個子類LoginController和AuthController. // 父類 function Controller() {this.errors []; } Controller.prot…

javascript --- polyfill中幾個常用方法

ES6中,新增了許多有用的方法,下面分享幾個ES6之前得版本寫的polyfill Number.EPSILON: // 機器精度,并判斷2個數是否相等 if(!Number.EPSILON){Number.EPSILON math.pow(2, -52); }function numberCloseEnoughToEqual(n1, n2) {return Math.abs(n1 - n2 ) < Number.EPSIL…

[Usaco2010 Nov]Visiting Cows

題目描述 經過了幾周的辛苦工作,貝茜終于迎來了一個假期.作為奶牛群中最會社交的牛,她希望去拜訪N(1<N<50000)個朋友.這些朋友被標號為1..N.這些奶牛有一個不同尋常的交通系統,里面有N-1條路,每條路連接了一對編號為C1和C2的奶牛(1 < C1 < N; 1 < C2 < N; C1…

ES5-2 語法、規范、錯誤、運算符、判斷分支、注釋

1. 錯誤 MDN錯誤列表 Uncaught SyntaxError: Unexpected token ) // 語法錯誤 Uncaught ReferenceError: a is not defined // 引用錯誤等類型 Uncaught TypeError: Cannot read property toString of null出現一個語法錯誤&#xff0c;則一行代碼都不會執行&#xff08;檢查…

新單詞 part 4

part 41.veto 英[?vi:t??]美[?vi:to?]n. 行使否決權; 否決權&#xff0c;否認權; 否決理由;vt. 否決&#xff0c;不同意; 不批準&#xff0c;禁止;vi. 否決; 禁止;2.acoustics 英[??ku:st?ks]美[??kust?ks]n. 聲學; &#xff08;傳聲系統的&#xff09; 音響效果; 聲…

unity深度查找某個子物體和遍歷所有子物體方法

本文總結一下關于unity的查找子物體的方法 首先說明一下這里將講三種查找子物體方法&#xff1a; 查找固定路徑的某一個子物體的方法、通過名字深度查找某個子物體的方法、查找父物體下所有子物體的方法。 第一:查找固定路徑的某一個子物體的方法 對于已知的路徑可以直接用go.t…

javascript --- JSON字符串化

工具函數JSON.stringify()將JSON對象序列化為字符串時也用到了ToString. 看下面的代碼: console.log(JSON.stringify(42)); console.log(JSON.stringify("42")); console.log(JSON.stringify(null)); console.log(JSON.stringify(true));所有安全的JSON值都可以使用…

靜態鏈接和動態鏈接

靜態鏈接和動態鏈接 靜態鏈接方法&#xff1a;靜態鏈接的時候&#xff0c;載入代碼就會把程序會用到的動態代碼或動態代碼的地址確定下來 靜態庫的鏈接可以使用靜態鏈接&#xff0c;動態鏈接庫也可以使用這種方法鏈接導入庫 動態鏈接方法&#xff1a;使用這種方式的程序并不在一…

ES5-3 循環、引用值初始、顯示及隱式類型轉換

1. 循環 for循環的三個參數abc&#xff0c;a只執行一次&#xff0c;c在每次循環后執行 // 打印0-100的質數 1不是質數 var list [2] for (var i 3; i < 100; i i 2) {var flag falsefor (var j 0; j < list.length; j) {var cur list[j]if (i % cur 0 &…

hihocoder 二分

題目 一個簡單的二分&#xff0c;只是想說明一下&#xff0c;如若要查找一個數組中某個數的下標可以直接用lower_bound()這個函數。只是要考慮到要查找的數不在數組中的這種情況。 #include <cstdio> #include <iostream> #include <algorithm> using namesp…

ubuntu開啟ssh服務

更新資源列表&#xff1a;sudo apt-get update -> 輸入管理員密碼 安裝openssh-server: sudo apt-get install openssh-server 查看 ssh服務是否啟動&#xff1a;sudo ps -e |grep ssh 啟動ssh服務&#xff1a; sudo service ssh start 轉載于:https://www.cnblogs.com/ver…

javascript --- 判斷只有1個為真

下面寫一個用于判斷只有一個為真的函數: function onlyOne(a,b,c){return !!((a && !b && !c) ||(!a && b && !c) || (!a && !b && c)); } var a true; var b false;onlyOne(a,b,b) // true onlyOne(a,b,a); // false上述…

13 代碼分割之import靜動態導入

前端首屏優化方案之一 項目構建時會整體打包成一個bundle的JS文件&#xff0c;而有的代碼、模塊是加載時不需要的&#xff0c;需要分割出來單獨形成一個文件塊chunk&#xff08;不會打包在main里&#xff09;&#xff0c;讓模塊懶加載&#xff08;想加載時才加載&#xff09;&a…

2018.01.01(數字三角形,最長上升子序列等)

2017.12.24 簡單的動態規劃 1.數字三角形(算法引入) 題目描述&#xff1a;下圖所示是一個數字三角形&#xff0c;其中三角形中的數值為正整數&#xff0c;現規定從最頂層往下走到最底層&#xff0c;每一步可沿左斜線向下或右斜線向下走。設三角形有n層&#xff0c;編程計算出從…

Mac iOS 允許從任何來源下載應用并打開

一個快捷的小知識點&#xff0c;mark&#xff01; 允許從任何來源下載應用并打開&#xff0c;不用手動去允許&#xff0c;更加簡潔&#xff01; 只需一行命令 sudo spctl --master-disable 1.正常情況下&#xff0c;打開偏好設置&#xff0c;選擇安全性與隱私&#xff0c;界面是…

ES5-4 函數基礎與種類、形實參及映射、變量類型

模塊編程原則&#xff1a;高內聚&#xff0c;低耦合&#xff08;重復部分少&#xff09;&#xff0c;讓一個模塊有強的功能性、高的獨立性 → 單一責任制&#xff0c;用函數進行解耦合。 1. 函數命名規則 不能以數字開頭可以以字母_$開頭包含數字小駝峰命名法 函數聲明一定有…

javascript --- 抽象相等

字符串和數字之間的相等比較 var a 42; var b "42";a b; // false a b; // trueES5規范11.9.3.4-5定義如下: (1)如果Type(x)是數字,Type(y)是字符串,則返回 x ToNumber(y) 的結果 (2)如果Type(x)是字符串,Type(x)是數字,則返回 ToNumber(x) y 的結果// 總結…

Spring加載context的幾種方法

Spring中的context管理 Spring中IOC容器的初始化&#xff1a; ApplicationContext即是保存bean對象的容器&#xff0c;故容器本身的初始化&#xff0c;就是通過一系列的配置&#xff0c;將ApplicationContext進行初始化。 而配置ApplicationContext大方向上分為了3中&#xff1…

centos 6.5 配置網絡

編輯 vi /etc/sysconfig/network-scripts/ifcfg-eth0修改內容 DEVICE"eth0" BOOTPROTO"static" HWADDR"00:50:56:98:06:D0" IPV6INIT"no" MTU"1500" NM_CONTROLLED"no" ONBOOT"yes" TYPE…