資料結構與演算法C++之堆排序
首先需要介紹一下一個新的資料結構:堆
堆使用了優先佇列
普通佇列:先進先出,後進後出
優先佇列:出隊順序與入隊順序無關,與優先順序有關,一般取出優先順序最高的元素,堆入隊出隊的演算法複雜度都為O(nlogn)
最常使用的是二叉堆(Binary Heap)
如上圖所示,62稱為41和30的父節點,41稱為左節點,30稱為右節點,以此類推,41又是28和16的父節點,等等,二叉堆有如下性質:
- 父節點大於左右子節點
- 堆總是一棵完全二叉樹,即最後一層上面的所有層都是完整的,即因為上圖有四層,那麼30一定同時存在左右節點,否則就不滿足堆的性質,倒數第二層的父節點可以左右節點都沒有,如22,也可以只有一個節點,但這個節點必須是左節點,如16
用陣列來儲存二叉堆,如下圖所示
習慣於將最上面的父節點62編號為1,然後從上到下,從左到右依次編號,因為編號從1開始,所以用來儲存堆的陣列需要10+1個儲存空間
- 定義某個節點 的父節點為
- 定義該節點 的左節點為
- 定義該節點 的又節點為
如節點4,它的值是28,他的父節點為
,值是41
它的左節點為
,值是19
它的右節點為
, 值是17
Shift Up 堆中插入元素
如上圖所示,插入元素值為52
(1)首先將52插入堆的最後一個位置,編號為11
(2)將52與其父節點16作比較,比16大,那麼交換16和52的位置
(3)將52再與其父節點41作比較,比41大,那麼交換41和52的位置
(4)將52再與新的父節點62作比較,比62小,那麼插入操作結束
Shift Down 堆中取出最大元素
(1)首先將最上面的父節點62取出
(2)將最後一個節點16填補到最上面的位置,同時將堆的元素總數count減1
(3)然後將父節點16與其他兩個子節點52和30作比較,16比52小,那麼交換16與52的位置
(4)繼續比較16與它的兩個新的子節點28和41的大小,比41小,那麼交換16和41的位置
(4)繼續比較16與其子節點15的大小,16比15大,那麼不用交換位置,Shift Down結束
下面就先使用ShiftUp和ShiftDown進行插入元素和取出元素
#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <cmath>
#include <cassert>
#include <typeinfo>
#ifndef _SORTINGHELP_H_
#define _SORTINGHELP_H_
#include "SortingHelp.h"
#endif // _SORTINGHELP_H_
#include "MergeSorting.h"
#include "quickSorting.h"
using namespace std;
template<typename Item>
class MaxHeap{
private:
Item *data;
int count;
int capacity;
void ShiftUp(int k){
while (data[k/2] < data[k] && k > 1){
swap(data[k/2], data[k]);
k /= 2;
}
}
void ShiftDown(int k){
while (k <= count/2){
int j = 2*k; //此輪迴圈中,data[k]和data[j]交換位置
if (data[j] < data[j+1] && j + 1 <= count)
j += 1;
if (data[k] >= data[j])
break;
swap(data[j], data[k]);
k = j;
}
}
public:
MaxHeap(int capacity){
data = new Item[capacity + 1];
count = 0;
this->capacity = capacity;
}
~MaxHeap(){
delete[] data;
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
void insert(Item item){
assert( count + 1 <= capacity );
data[count+1] = item;
count ++;
ShiftUp(count);
}
Item extractMax(){
assert( count > 0);
Item ret = data[1];
swap(data[1], data[count]);
count --;
ShiftDown(1);
return ret;
}
public:
void testPrint(){ //列印堆函式,不用掌握
if( size() >= 100 ){
cout<<"Fancy print can only work for less than 100 int";
return;
}
if( typeid(Item) != typeid(int) ){
cout <<"Fancy print can only work for int item";
return;
}
cout<<"The Heap size is: "<<size()<<endl;
cout<<"data in heap: ";
for( int i = 1 ; i <= size() ; i ++ )
cout<<data[i]<<" ";
cout<<endl;
cout<<endl;
int n = size();
int max_level = 0;
int number_per_level = 1;
while( n > 0 ) {
max_level += 1;
n -= number_per_level;
number_per_level *= 2;
}
int max_level_number = int(pow(2, max_level-1));
int cur_tree_max_level_number = max_level_number;
int index = 1;
for( int level = 0 ; level < max_level ; level ++ ){
string line1 = string(max_level_number*3-1, ' ');
int cur_level_number = min(count-int(pow(2,level))+1,int(pow(2,level)));
bool isLeft = true;
for( int index_cur_level = 0 ; index_cur_level < cur_level_number ; index ++ , index_cur_level ++ ){
putNumberInLine( data[index] , line1 , index_cur_level , cur_tree_max_level_number*3-1 , isLeft );
isLeft = !isLeft;
}
cout<<line1<<endl;
if( level == max_level - 1 )
break;
string line2 = string(max_level_number*3-1, ' ');
for( int index_cur_level = 0 ; index_cur_level < cur_level_number ; index_cur_level ++ )
putBranchInLine( line2 , index_cur_level , cur_tree_max_level_number*3-1 );
cout<<line2<<endl;
cur_tree_max_level_number /= 2;
}
}
private:
void putNumberInLine( int num, string &line, int index_cur_level, int cur_tree_width, bool isLeft){
int sub_tree_width = (cur_tree_width - 1) / 2;
int offset = index_cur_level * (cur_tree_width+1) + sub_tree_width;
assert(offset + 1 < line.size());
if( num >= 10 ) {
line[offset + 0] = '0' + num / 10;
line[offset + 1] = '0' + num % 10;
}
else{
if( isLeft)
line[offset + 0] = '0' + num;
else
line[offset + 1] = '0' + num;
}
}
void putBranchInLine( string &line, int index_cur_level, int cur_tree_width){
int sub_tree_width = (cur_tree_width - 1) / 2;
int sub_sub_tree_width = (sub_tree_width - 1) / 2;
int offset_left = index_cur_level * (cur_tree_width+1) + sub_sub_tree_width;
assert( offset_left + 1 < line.size() );
int offset_right = index_cur_level * (cur_tree_width+1) + sub_tree_width + 1 + sub_sub_tree_width;
assert( offset_right < line.size() );
line[offset_left + 1] = '/';
line[offset_right + 0] = '\\';
}
};
int main(){
MaxHeap<int> maxheap = MaxHeap<int>(100);
srand(time(NULL));
for (int i = 0; i < 10; i++){
maxheap.insert(rand()%100);
}
maxheap.testPrint();
return 0;
}
輸出為
取出元素測試程式為
int main(){
MaxHeap<int> maxheap = MaxHeap<int>(100);
srand(time(NULL));
for (int i = 0; i < 10; i++){
maxheap.insert(rand()%100);
}
//maxheap.testPrint();
while(!maxheap.isEmpty()){
cout<<maxheap.extractMax()<<" ";
}
cout<<endl;
return 0;
}
輸出為
下面使用堆實現排序
其實就是先將陣列中元素挨個放入堆中,然後挨個將堆中元素取出來逆序再放入陣列中就可以
首先將堆的實現放入 Heap.h 中
//Heap.h
#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <cmath>
#include <cassert>
#include <typeinfo>
using namespace std;
template<typename Item>
class MaxHeap{
private:
Item *data;
int count;
int capacity;
void ShiftUp(int k){
while (data[k/2] < data[k] && k > 1){
swap(data[k/2], data[k]);
k /= 2;
}
}
void ShiftDown(int k){
while (k <= count/2){
int j = 2*k; //此輪迴圈中,data[k]和data[j]交換位置
if (data[j] < data[j+1] && j + 1 <= count)
j += 1;
if (data[k] >= data[j])
break;
swap(data[j], data[k]);
k = j;
}
}
public:
MaxHeap(int capacity){
data = new Item[capacity + 1];
count = 0;
this->capacity = capacity;
}
~MaxHeap(){
delete[] data;
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
void insert(Item item){
assert( count + 1 <= capacity );
data[count+1] = item;
count ++;
ShiftUp(count);
}
Item extractMax(){
assert( count > 0);
Item ret = data[1];
swap(data[1], data[count]);
count --;
ShiftDown(1);
return ret;
}
public:
void testPrint(){
if( size() >= 100 ){
cout<<"Fancy print can only work for less than 100 int";
return;
}