【數據結構與算法】內部排序之三:堆排序(含完整源碼)


轉載請注明出處:http://blog.csdn.net/ns_code/article/details/20227303


前言

? ??堆排序、快速排序、歸并排序(下篇會寫這兩種排序算法)的平均時間復雜度都為O(n*logn)。要弄清楚堆排序,就要先了解下二叉堆這種數據結構。本文不打算完全講述二叉堆的所有操作,而是著重講述堆排序中要用到的操作。比如我們建堆的時候可以采用堆的插入操作(將元素插入到適當的位置,使新的序列仍符合堆的定義)將元素一個一個地插入到堆中,但其實我們完全沒必要這么做,我們有執行操作更少的方法,后面你會看到,我們基本上只用到了堆的刪除操作,更具體地說,應該是刪除堆的根節點后,將剩余元素繼續調整為堆的操作。先來看二叉堆的定義。

二叉堆

? ? 二叉堆其實是一棵有著特殊性質的完全二叉樹,這里的特殊性質是指:

? ? 1、二叉堆的父節點的值總是大于等于(或小于等于)其左右孩子的值;

? ? 2、每個節點的左右子樹都是一棵這樣的二叉堆。

? ? 如果一個二叉堆的父節點的值總是大于其左右孩子的值,那么該二叉堆為最大堆,反之為最小堆。我們在排序時,如果要排序后的順序為從小到大,則需選擇最大堆,反之,選擇最小堆,這點通過后面對堆排序分析,你會有所體會。

堆排序

? ? 由二叉堆的定義可知,堆頂元素(即二叉堆的根節點)一定為堆中的最大值或最小值,因此如果我們輸出堆頂元素后,將剩余的元素再調整為二叉堆,繼而再次輸出堆頂元素,再將剩余的元素調整為二叉堆,反復執行該過程,這樣便可輸出一個有序序列,這個過程我們就叫做堆排序。

? ? 由于我們的輸入是一個無序序列,因此要實現堆排序,我們要先后解決如下兩個問題:

? ? 1、如何將一個無序序列建成一個二叉堆;

? ? 2、在去掉堆頂元素后,如何將剩余的元素調整為一個二叉堆。

? ? 針對第一個問題,可能很明顯會想到用堆的插入操作,一個一個地插入元素,每次插入后調整元素的位置,使新的序列依然為二叉堆。這種操作一般是自底向上的調整操作,即先將待插入元素放在二叉堆后面,而后逐漸向上將其與父節點比較,進而調整位置。但正如前言中所說,我們完全用不著一個節點一個節點地插入,那我們要怎么做呢?我們需要先來解決第二個問題,解決了第二個問題,第一個問題問題也就迎刃而解了。

? ??調整二叉堆

? ? 要分析第二個問題,我們先給出以下前提:

? ? 1、我們排序的目標是從小到大,因此我們用最大堆;

? ? 2、我們將二叉堆中的元素以層序遍歷后的順序保存在一維數組中,根節點在數組中的位置序號為0。

? ? 這樣,如果某個節點在數組中的位置序號為i,那么它的左右孩子的位置序號分別為2i+1和2i+2。

? ? 為了使調整過程更易于理解,我們采用如下二叉堆來分析(注意下面的分析,我們并沒有采用額外的數組來存儲每次去掉的堆頂數據):?

??? ? ??

? ? 這里數組A中元素的個數為8,很明顯最大值為A0,為了實現排序后的元素按照從小到大的順序排列,我們可以將二叉堆中的最后一個元素A7與A0互換,這樣A7中保存的就是數組中的最大值,而此時該二叉樹變為了如下情況:

? ?

? ? 為了將其調整為二叉堆,我們需要尋找4應該插入的位置。為此,我們讓4與它的孩子節點中最大的那個,也就是其左孩子7,進行比較,由于4<7,我們便把二者互換,這樣二叉樹便變成了如下的形式:

? ? 接下來,繼續讓4與其左右孩子中的最大者,也就是6,進行比較,同樣由于4<6,需要將二者互換,這樣二叉樹變成了如下的形式:


? ? 這樣便又構成了二叉堆,這時候A0為7,是所有元素中的最大元素。同樣我們此時繼續將二叉堆中的最后一個元素A6和A0互換,這樣A6中保存的就是第二大的數值7,而A0就變為了3,形式如下:

? ? 為了將其調整為二叉堆,一樣將3與其孩子結點中的最大值比較,由于3<6,需要將二者互換,而后繼續和其孩子節點比較,需要將3和4互換,最終再次調整好的二叉堆形式如下:


? ? 一樣將A0與此時堆中的最后一個元素A5互換,這樣A5中保存的便是第三大的數值,再次調整剩余的節點,如此反復,直到最后堆中僅剩一個元素,這時整個數組便已經按照從小到大的順序排列好了。

? ? 據此,我們不難得出將剩余元素繼續調整為二叉堆的操作實現代碼如下(同前面兩篇博文中一樣,我們不需每次比較后都交換元素位置,代碼中可以再次體會到這點):

[cpp]?view plain?copy
  1. /*?
  2. arr[start+1...end]滿足最大堆的定義,?
  3. 將arr[start]加入到最大堆arr[start+1...end]中,?
  4. 調整arr[start]的位置,使arr[start...end]也成為最大堆?
  5. 注:由于數組從0開始計算序號,也就是二叉堆的根節點序號為0,?
  6. 因此序號為i的左右子節點的序號分別為2i+1和2i+2?
  7. */??
  8. void?HeapAdjustDown(int?*arr,int?start,int?end)??
  9. {??
  10. ????int?temp?=?arr[start];??//保存當前節點??
  11. ????int?i?=?2*start+1;??????//該節點的左孩子在數組中的位置序號??
  12. ????while(i<=end)??
  13. ????{??
  14. ????????//找出左右孩子中最大的那個??
  15. ????????if(i+1<=end?&&?arr[i+1]>arr[i])????
  16. ????????????i++;??
  17. ????????//如果符合堆的定義,則不用調整位置??
  18. ????????if(arr[i]<=temp)???
  19. ????????????break;??
  20. ????????//最大的子節點向上移動,替換掉其父節點??
  21. ????????arr[start]?=?arr[i];??
  22. ????????start?=?i;??
  23. ????????i?=?2*start+1;??
  24. ????}??
  25. ????arr[start]?=?temp;??
  26. }??
? ? 這樣,將已經建好的二叉堆進行排序的代碼如下:

[cpp]?view plain?copy
  1. //進行堆排序??
  2. for(i=len-1;i>0;i--)??
  3. {??
  4. ????//堆頂元素和最后一個元素交換位置,??
  5. ????//這樣最后的一個位置保存的是最大的數,??
  6. ????//每次循環依次將次大的數值在放進其前面一個位置,??
  7. ????//這樣得到的順序就是從小到大??
  8. ????int?temp?=?arr[i];??
  9. ????arr[i]?=?arr[0];??
  10. ????arr[0]?=?temp;??
  11. ????//將arr[0...i-1]重新調整為最大堆??
  12. ????HeapAdjustDown(arr,0,i-1);??
  13. }??

? ??建立二叉堆

? ? 搞懂了第二個問題,那么我們回過頭來看如何將無序的數組建成一個二叉堆。

? ? 我們同樣以上面的數組為例,假設其數組內元素的原始順序為:A[]={6,1,3,9,5,4,2,7},那么在沒有建成二叉堆前,個元素在該完全二叉樹中的存放位置如下:


? ? 這里的后面四個元素均為葉子節點,很明顯,這四個葉子可以認為是一個堆(因為堆的定義中并沒有對左右孩子間的關系有任何要求,所以可以將這幾個葉子節點看做是一個堆),而后我們便考慮將第一個非葉子節點9插入到這個堆中,再次構成一個堆,接著再將3插入到新的堆中,再次構成新堆,如此繼續,直到該二叉樹的根節點6也插入到了該堆中,此時構成的堆便是由該數組建成的二叉堆。因此,我們這里同樣可以利用到上面所寫的HeapAdjustDown(int *,int,int)函數,因此建堆的代碼可寫成如下的形式:

[cpp]?view plain?copy
  1. //把數組建成為最大堆??
  2. //第一個非葉子節點的位置序號為(len-1)/2??
  3. for(i=(len-1)/2;i>=0;i--)??
  4. ????HeapAdjustDown(arr,i,len-1);??

? ? 如果還不是很明白,注意讀下HeapAdjustDown(int *,int,int)函數代碼中關于該函數作用的注釋。

完整源碼

? ? 最后貼出完整源碼:

[cpp]?view plain?copy
  1. /*******************************?
  2. ????????????堆排序?
  3. Author:蘭亭風雨?Date:2014-02-27?
  4. Email:zyb_maodun@163.com?
  5. ********************************/??
  6. #include<stdio.h>??
  7. #include<stdlib.h>??
  8. ??
  9. /*?
  10. arr[start+1...end]滿足最大堆的定義,?
  11. 將arr[start]加入到最大堆arr[start+1...end]中,?
  12. 調整arr[start]的位置,使arr[start...end]也成為最大堆?
  13. 注:由于數組從0開始計算序號,也就是二叉堆的根節點序號為0,?
  14. 因此序號為i的左右子節點的序號分別為2i+1和2i+2?
  15. */??
  16. void?HeapAdjustDown(int?*arr,int?start,int?end)??
  17. {??
  18. ????int?temp?=?arr[start];??//保存當前節點??
  19. ????int?i?=?2*start+1;??????//該節點的左孩子在數組中的位置序號??
  20. ????while(i<=end)??
  21. ????{??
  22. ????????//找出左右孩子中最大的那個??
  23. ????????if(i+1<=end?&&?arr[i+1]>arr[i])????
  24. ????????????i++;??
  25. ????????//如果符合堆的定義,則不用調整位置??
  26. ????????if(arr[i]<=temp)???
  27. ????????????break;??
  28. ????????//最大的子節點向上移動,替換掉其父節點??
  29. ????????arr[start]?=?arr[i];??
  30. ????????start?=?i;??
  31. ????????i?=?2*start+1;??
  32. ????}??
  33. ????arr[start]?=?temp;??
  34. }??
  35. ??
  36. /*?
  37. 堆排序后的順序為從小到大?
  38. 因此需要建立最大堆?
  39. */??
  40. void?Heap_Sort(int?*arr,int?len)??
  41. {??
  42. ????int?i;??
  43. ????//把數組建成為最大堆??
  44. ????//第一個非葉子節點的位置序號為len/2-1??
  45. ????for(i=len/2-1;i>=0;i--)??
  46. ????????HeapAdjustDown(arr,i,len-1);??
  47. ????//進行堆排序??
  48. ????for(i=len-1;i>0;i--)??
  49. ????{??
  50. ????????//堆頂元素和最后一個元素交換位置,??
  51. ????????//這樣最后的一個位置保存的是最大的數,??
  52. ????????//每次循環依次將次大的數值在放進其前面一個位置,??
  53. ????????//這樣得到的順序就是從小到大??
  54. ????????int?temp?=?arr[i];??
  55. ????????arr[i]?=?arr[0];??
  56. ????????arr[0]?=?temp;??
  57. ????????//將arr[0...i-1]重新調整為最大堆??
  58. ????????HeapAdjustDown(arr,0,i-1);??
  59. ????}??
  60. }??
  61. ??
  62. int?main()??
  63. {??
  64. ????int?num;??
  65. ????printf("請輸入排序的元素的個數:");??
  66. ????scanf("%d",&num);??
  67. ??
  68. ????int?i;??
  69. ????int?*arr?=?(int?*)malloc(num*sizeof(int));??
  70. ????printf("請依次輸入這%d個元素(必須為整數):",num);??
  71. ????for(i=0;i<num;i++)??
  72. ????????scanf("%d",arr+i);??
  73. ??
  74. ????printf("堆排序后的順序:");??
  75. ????Heap_Sort(arr,num);??
  76. ????for(i=0;i<num;i++)??
  77. ????????printf("%d?",arr[i]);??
  78. ????printf("\n");??
  79. ??
  80. ????free(arr);??
  81. ????arr?=?0;??
  82. ????return?0;??
  83. }??
? ? 測試結果如下:


總結

? ? 最后我們簡要分析下堆排序的時間復雜度。我們在每次重新調整堆時,都要將父節點與孩子節點比較,這樣,每次重新調整堆的時間復雜度變為O(logn),而堆排序時有n-1次重新調整堆的操作,建堆時有((len-1)/2+1)次重新調整堆的操作,因此堆排序的平均時間復雜度為O(n*logn)。由于我們這里沒有借用輔助存儲空間,因此空間復雜度為O(1)。

? ? 堆排序在排序元素較少時有點大才小用,待排序列元素較多時,堆排序還是很有效的。另外,堆排序在最壞情況下,時間復雜度也為O(n*logn)。相對于快速排序(平均時間復雜度為O(n*logn),最壞情況下為O(n*n)),這是堆排序的最大優點。



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

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

相關文章

模線性方程(中國剩余定理+擴展中國剩余定理)

已知一系列除數和模數,求最小的滿足條件的數 我們先考慮一般的情況&#xff0c;即模數不互質。&#xff08;擴展中國剩余定理&#xff09; 我們考慮兩個方程的情況 x%MR xk1?MRxk1 * MRxk1?MR x%mr xk2?mrxk2 * mrxk2?mr 所以k1?MRk2?mrk1 * MRk2 * mrk1?MRk2?mr 即…

C++:Vector和List的實現

Vector的實現 //test.h #pragma once#include <iostream> #include <cstdio> #include <string.h> #include <assert.h>using namespace std;typedef int DataType;#define TESTHEADER printf("\n%s\n", __FUNCTION__)class Vector { publi…

【數據結構】(面試題)使用兩個棧實現一個隊列(詳細介紹)

http://blog.csdn.net/hanjing_1995/article/details/51539578 使用兩個棧實現一個隊列 思路一&#xff1a; 我們設定s1是入棧的&#xff0c;s2是出棧的。 入隊列&#xff0c;直接壓到s1即可 出隊列&#xff0c;先把s1中的元素倒入到s2中&#xff0c;彈出s2中的棧頂元素&#x…

POJ 1006 Biorhythms

中國剩余定理的模板題 只是有一個問題就是求出來Xk*MR中的R比給定的日期還大&#xff0c;但是如果負數的整除就不是向下取整了&#xff0c;為了解決這個問題&#xff0c;我們將R減小M&#xff0c;這樣總是正的&#xff0c;求出來的就沒有什么問題。 #include <iostream>…

POJ 3696 歐拉函數+快速冪

題目的意思大概就是問是否存在一串全是8的數字是L的倍數 直接想沒有什么想法&#xff0c;要想到用簡潔的形式將這個數字表示出來&#xff0c;對于每一位都是8的數字我們可以用 X8*(10k-1)/9的形式表示出來&#xff0c;那么題目的意思就是求X使L|X&#xff0c;我們先處理一下8和…

兩個棧實現一個隊列,兩個隊列實現一個棧

http://blog.csdn.net/zw_1510/article/details/51927554 問題1&#xff1a;用兩個棧實現一個隊列&#xff0c;實現隊列的push和delete操作 棧的特性是先進后出&#xff08;FILO&#xff09;,隊列的特性是先進先出&#xff08;FIFO&#xff09;,在實現delete時&#xff0c;我們…

C++:String的寫時拷貝

String的寫時拷貝 //test.h #pragma once#include <iostream> #include <string.h> #include <cstdio> #include <assert.h> using namespace std;#define TESTHEADER printf("\n%s\n", __FUNCTION__) class String { public:String(const …

兩個棧實現一個隊列與兩個隊列實現一個棧

http://blog.csdn.net/z84616995z/article/details/19204529 兩個棧實現一個隊列&#xff1a; 原理方法&#xff1a;用一個棧為主棧&#xff0c;一個棧為輔助棧存放臨時元素。 入隊&#xff1a;將元素依次壓入主棧 出隊&#xff1a;先檢測輔助棧是否為空&#xff0c;如果非空&a…

UVa11426——歐拉函數

發現對于gcd問題要多和歐拉函數聯系在一起&#xff0c;雖然有時候并不是互質&#xff0c;但是我們知道有多少互質的然后根據互質的數目就能解決很多個gcd的問題 對于這道題目&#xff0c;題目要求的是所有數對的gcd的和&#xff0c;直接思考的話有難度。但是我們如果聯想到歐拉…

C++:繼承和多態

虛函數:只有類的成員函數才能定義為虛函數 虛函數 在類的成員函數前面加上一個 virtual 關鍵字, 此時這個成員函數就叫做虛函數 虛函數 當在子類中定義了一個與父類完全相同的虛函數的時候,此時就叫做子類的虛函數重寫了父類的虛函數 構成多態的條件 派生類重寫基類的虛函數…

POJ 1061擴展歐幾里得

擴展歐幾里得的模板題&#xff0c;需要注意的是為了得到一個最小正數解我們要使axbyc中的a,b都是正數 #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<iostream> #include<cmath> #include<ctim…

C++::探索對象模型

前面我們已經知道, 在沒有虛函數的時候, 對象的大小就是對應的成員變量的大小, 而成員函數不會占用對象的空間, 今天我們來討論一下, 當類中定義了虛函數的時候, 此時對象的大小以及對象模型 非繼承下的對象模型 class Base { public:virtual void func1(){cout << &qu…

auto_ptr

#include <iostream> #include <memory> using namespace std;class A { public:A(){cout<<"構造"<<endl;}~A(){cout<<"A析構"<<endl;}void fun(){cout<<"A::fun"<<endl;} };class PA { public…

POJ 2142——擴展歐幾里得

題目是很裸的擴展歐幾里得&#xff0c;但是對x,y有限制條件&#xff0c;要求所有x,y中abs(x)abs(y)最小&#xff0c;在這個條件下要求abs(a* x)abs(b* y)最小 顯然我們需要用擴展歐幾里得求得一組解&#xff0c;問題在于如何處理這組解以得到符合條件的值。 我是這樣處理的&a…

C++::模板

模板的簡單介紹 C中模板是為了能夠使得函數或者類實現范型編程的目的, 同時C模板的出現是為了避免代碼的冗余 舉個例子 void Swap(int& a, int& b) {int tmp a;b a;a b; } void Swap(char& a, char& b) {char tmp a;b a;a b; } 上面的函數除了類型不…

Linux select TCP并發服務器與客戶端編程

http://blog.csdn.net/szkbsgy/article/details/10558881 [cpp] view plaincopy <span style"font-size:18px;">服務端&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #i…

BZOJ - 2186 歐拉函數

題目的意思大概是求1~N!中和M&#xff01;互質的數的個數 因為對歐拉函數理解不夠深刻所以我是分析得到結果的&#xff1a; 當N<M的時候顯然符合要求的數的個數為0&#xff1b; 當N>M的時候我們要求的是1~N!中不含1 ~M的素因子的的數的個數&#xff0c;結合歐拉函數的…

多態相關概念

多態相關注意事項 所謂的多態就是指函數有多中狀態, 在C中通常是通過父類指針指向子類對象的方法實現多態, 這樣父類可以通過子類的類型調用不同的方法. 即實現一個接口多種方法, 多態的引用是為了實現接口復用 在 C中多態是通過虛函數來實現的. 子類通過對父類相關接口進行重…

模板實現棧隊列以及鏈表

模板實現鏈表 //test.h #include <iostream> #include <cstdio> #include <assert.h> using namespace std;template <class T> struct ListNode {ListNode* _prev;ListNode* _next;T _data;ListNode(const T& x):_prev(NULL),_next(NULL),_data(…

基于Visual C++2013拆解世界五百強面試題--題5-自己實現strstr

http://blog.csdn.net/itcastcpp/article/details/12907371 請用C語言實現字符串的查找函數strstr&#xff0c; 找到則返回子字符串的地址&#xff0c;沒有找到返回為空&#xff0c;請用數組操作與指針操作實現 看到題目想到最簡單的方法就是母字符串和子字符串比較&#xff0c…