最近在刷算法導論,第六章是堆排序,本文主要講使用c++實現最大堆、最小堆以及最大優先級隊列和最小優先級隊列,還有堆排序。
對可以分爲最大堆和最小堆。本文所使用的的堆是基於數組的完全二叉樹實現。
最大堆
每個樹節點的子樹中的任意元素的值都不大於該節點的元素值。
上圖爲算法導論中使用數組表示的最大堆示意圖
實現最大堆首先要實現基於二叉樹的尋找節點父節點左右子節點的方法。
由於在c++中數組的索引跟在算法導論的僞代碼索引有所不同(數組是從0開始書中僞代碼是從1開始計數)。
inline size_t get_parent(size_t i)
{
try
{
size_t index;
if (i > 1)
index = floor(i / 2) - 1;
else if (i == 1)
index = 0;
assert(index < heap_size && index >= 0);
return index;
}
catch(const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
inline size_t get_left(size_t i)
{
try
{
size_t index = 2 * i + 1;
assert(index >= 0 && index < heap_size);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
inline size_t get_right(size_t i)
{
try
{
size_t index = 2 * i + 2;
assert(index >= 0 && index < heap_size);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
上面是實現尋找數組中節點的父節點、左右子節點的代碼
因爲是完全二叉樹,所以滿足這種索引計算關係,
注意這個跟算法導論的僞代碼不完全相同!!!
下圖爲算法導論中的僞代碼,藉此說明原理
在這個max_heap的模板類中,數據成員是
array<T, 1024> data_array = { 0 };
size_t heap_size = 0;
一個是array數組模板一個變量用來表示堆中元素數量。
首先應該做的是初始化堆,即輸入一個數組,將堆中的二叉樹節點的元素初始化,如下代碼:輸入n是數組的大小
void init_data(T* input,int n)
{
for (int i = 0; i < n; i++)
{
data_array.at(i) = *(input+i);
heap_size++;
}
}
最大堆的實現最核心的部分是對初始化了的數組進行調整以使之滿足最大堆性質。
在介紹該成員函數之前,還需要一個輔助函數,即判斷一個節點是內部節點還是葉節點,代碼如下:
int leaf_class(size_t i,int n)
{
if ((2 * i) < n && (2 * i + 1) < n)
return 2;
if ((2 * i) < n+1 && (2 * i + 1) == n)
return 1;
else
return 0;
}
上述代碼將節點分成三種類型,返回整數2類型是擁有左右兩個節點的類型。返回整數1是隻有左節點沒有右節點的類型;返回0是葉節點。
有了這個工具函數就可以實現維持最大堆性質的max_heap()成員函數了
void max_heapify(size_t i,int n)
{
assert(i < n+1);
if (leaf_class(i,n) == 2)
{
size_t l = get_left(i);
size_t r = get_right(i);
size_t largest;
if (l<n && data_array.at(l)>data_array.at(i))
{
largest = l;
}
else
{
largest = i;
}
if (r<n+1 && data_array.at(r)>data_array.at(largest))
{
largest = r;
}
if (largest != i)
{
T tmp;
tmp = data_array.at(largest);
data_array.at(largest) = data_array.at(i);
data_array.at(i) = tmp;
max_heapify(largest,n);
}
}
else if (leaf_class(i,n) == 1)
{
size_t l = get_left(i);
if (data_array.at(l) > data_array.at(i))
{
T tmp = data_array.at(i);
data_array.at(i) = data_array.at(l);
data_array.at(l) = tmp;
}
}
}
算法導論的僞代碼如下:
注意真實代碼和僞代碼的下標的不同哦
基本思想是如果一個節點的子節點比該節點大,就交換該節點和子節點中最大的。然後再遞歸的調用被交換過數據的子節點,這樣就可以保證從上往下都是滿足最大堆的性質了。但是僅僅這個函數還沒有完成將一個初始化了的數組調整爲最大堆的工作
需要下面的成員函數
void build_max_heap()
{
for (int i = floor(heap_size / 2) + 1; i >= 0; i--)
max_heapify(i,get_heap_size()-1);
}
原理很簡單,只要將完全二叉樹的倒數第二層的最後一個元素開始逐步向前每個元素調整滿足最大堆的性質就可以了,畢竟最外層的元素都是葉子結點。這樣就完成了最大堆的實現過程,完整代碼如下:
template <typename T>
class max_heap
{
public:
inline size_t get_parent(size_t i)
{
try
{
size_t index;
if (i > 1)
index = floor(i / 2) - 1;
else if (i == 1)
index = 0;
assert(index < heap_size && index >= 0);
return index;
}
catch(const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
inline size_t get_left(size_t i)
{
try
{
size_t index = 2 * i + 1;
assert(index >= 0 && index < heap_size);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
inline size_t get_right(size_t i)
{
try
{
size_t index = 2 * i + 2;
assert(index >= 0 && index < heap_size);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
void max_heapify(size_t i,int n)
{
assert(i < n+1);
if (leaf_class(i,n) == 2)
{
size_t l = get_left(i);
size_t r = get_right(i);
size_t largest;
if (l<n && data_array.at(l)>data_array.at(i))
{
largest = l;
}
else
{
largest = i;
}
if (r<n+1 && data_array.at(r)>data_array.at(largest))
{
largest = r;
}
if (largest != i)
{
T tmp;
tmp = data_array.at(largest);
data_array.at(largest) = data_array.at(i);
data_array.at(i) = tmp;
max_heapify(largest,n);
}
}
else if (leaf_class(i,n) == 1)
{
size_t l = get_left(i);
if (data_array.at(l) > data_array.at(i))
{
T tmp = data_array.at(i);
data_array.at(i) = data_array.at(l);
data_array.at(l) = tmp;
}
}
}
size_t get_heap_size()
{
return heap_size;
}
int leaf_class(size_t i,int n)
{
if ((2 * i) < n && (2 * i + 1) < n)
return 2;
if ((2 * i) < n+1 && (2 * i + 1) == n)
return 1;
else
return 0;
}
void build_max_heap()
{
for (int i = floor(heap_size / 2) + 1; i >= 0; i--)
max_heapify(i,get_heap_size()-1);
}
void init_data(T* input,int n)
{
for (int i = 0; i < n; i++)
{
data_array.at(i) = *(input+i);
heap_size++;
}
}
void print_heap()
{
for (int i = 0; i < heap_size; i++)
cout << data_array.at(i) << endl;
}
array<T, 1024> data_array = { 0 };
size_t heap_size = 0;
private:
protected:
};
堆排序
設計出來最大堆的數據結構,實現堆排序就很簡單了。因爲最大堆的根節點就是數組中最大的元素,只要將最大的元素與數組最後的元素交換,在對根節點使用最大堆化的轉換函數即可。唯一需要注意的是,因爲我們已經將最大的元素放置在數組的最後面,所以需要創造一個新的變量,初始值是數組的長度,沒調用一次就取出一個最大的元素,該變量減1,進而來實現在原有的數組上進行排序。代碼實現中這個指示變量是n。
代碼如下
template <typename T>
void heap_sort(max_heap<T> input,int size)
{
int n = input.get_heap_size() - 1;
for (size_t i = size-1; i > 0; i--)
{
T tmp = input.data_array.at(0);
input.data_array.at(0) = input.data_array.at(i);
input.data_array.at(i) = tmp;
n--;
input.max_heapify(0, n);
}
cout << endl;
for (int i = 0; i < input.get_heap_size(); i++)
{
cout << input.data_array.at(i) << endl;
}
}
算法導論中的僞代碼如下:
最大優先級隊列
基於最大堆就可以實現最大優先級隊列
返回隊列中的最大值,就是數組第一個的元素,堆的根節點。
代碼實現如下
T maximum()
{
if ((this->heap_size) == 0)
assert("the max heap is empty!!!");
else
return this->data_array[0];
}
提取最大的元素,方法是將最大堆的根節點元素與最後的元素交換,然後將最大堆的元素個數減1,對交換之後的根節點進行最大堆性質調整即可。
算法導論僞代碼如下:
代碼實現如下:
T heap_extract_max()
{
if (this->heap_size == 0)
assert("the queue is empty!!1");
else
{
T max = this->data_array.at(0);
this->data_array.at(0) = this->data_array.at(this->heap_size - 1);
this->heap_size--;
this->max_heapify(0, this->heap_size - 1);
return max;
}
}
還有一個功能函數是實現插入的基礎:
即將節點i的元素值提升爲key,然後從新排列最大堆的元素組合。這麼實現的現實意義是將個別任務的優先級做調整,
算法導論僞代碼如下
實現代碼如下:
void heap_increase_key(int i,T key)
{
assert(0 <= i && i < this->heap_size);
if (key < this->data_array.at(i))
assert("new key is smaller than current key!");
else
{
this->data_array.at(i) = key;
while (i > 0 && this->data_array.at(this->get_parent(i)) < this->data_array.at(i))
{
std::swap(this->data_array.at(i), this->data_array.at(this->get_parent(i)));
i = this->get_parent(i);
}
}
}
核心思想就是提升完後跟父節點比大小,要是比父節點大,就交換,接着往上比。
最後一個功能就是優先級隊列的插入元素
其實可以理解爲在數組最後面元素的下一位加入待調整的元素,然後不斷跟父節點比較,一級一級往上提就可以了。
書中僞代碼如下:
代碼實現如下:
void insert(T key)
{
this->data_array.at(this->get_heap_size()) = key;
this->heap_size++;
heap_increase_key(this->get_heap_size() - 1, key);
}
最大優先級隊列的完整實現如下:
template<typename T>
class max_priority_queue:public max_heap<T>
{
public:
T maximum()
{
if ((this->heap_size) == 0)
assert("the max heap is empty!!!");
else
return this->data_array[this->heap_size-1];
}
T heap_extract_max()
{
if (this->heap_size == 0)
assert("the queue is empty!!1");
else
{
T max = this->data_array.at(0);
this->data_array.at(0) = this->data_array.at(this->heap_size - 1);
this->heap_size--;
this->max_heapify(0, this->heap_size - 1);
return max;
}
}
void heap_increase_key(int i,T key)
{
assert(0 <= i && i < this->heap_size);
if (key < this->data_array.at(i))
assert("new key is smaller than current key!");
else
{
this->data_array.at(i) = key;
while (i > 0 && this->data_array.at(this->get_parent(i)) < this->data_array.at(i))
{
std::swap(this->data_array.at(i), this->data_array.at(this->get_parent(i)));
i = this->get_parent(i);
}
}
}
void insert(T key)
{
this->data_array.at(this->get_heap_size()) = key;
this->heap_size++;
heap_increase_key(this->get_heap_size() - 1, key);
}
};
測試代碼:
void test_max_heap()
{
max_heap<int> heap;
int data[10] = { 4,1,3,2,16,9,10,14,8,7 };
heap.init_data(data,10);
heap.build_max_heap();
heap.print_heap();
heap_sort<int>(heap, 10);
}
void test_max_priority_queue()
{
max_priority_queue<int> queue;
int data[10] = { 4,1,3,2,16,9,10,14,8,7 };
queue.init_data(data, 10);
queue.build_max_heap();
queue.print_heap();
cout << endl;
/*queue.heap_extract_max();
queue.print_heap();*/
/*queue.heap_increase_key(9, 60);
queue.print_heap();*/
queue.insert(100);
queue.print_heap();
}
最小堆和最小優先隊列
這個實現起來實際上跟最大堆和最大優先級隊列反着來就可以,畢竟都是通過比較的方法實現的。
template <typename T>
class min_heap
{
public:
inline size_t get_parent(size_t i)
{
try
{
size_t index;
if (i > 1)
index = floor(i / 2) - 1;
else if (i == 1)
index = 0;
assert(index < heap_size && index >= 0);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
inline size_t get_left(size_t i)
{
try
{
size_t index = 2 * i + 1;
assert(index >= 0 && index < heap_size);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
inline size_t get_right(size_t i)
{
try
{
size_t index = 2 * i + 2;
assert(index >= 0 && index < heap_size);
return index;
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
throw;
}
}
void min_heapify(size_t i, int n)
{
assert(i < n + 1);
if (leaf_class(i, n) == 2)
{
size_t l = get_left(i);
size_t r = get_right(i);
size_t smallest;
if (l<n && data_array.at(l)<data_array.at(i))
{
smallest = l;
}
else
{
smallest = i;
}
if (r<n + 1 && data_array.at(r)<data_array.at(smallest))
{
smallest = r;
}
if (smallest != i)
{
T tmp;
tmp = data_array.at(smallest);
data_array.at(smallest) = data_array.at(i);
data_array.at(i) = tmp;
min_heapify(smallest, n);
}
}
else if (leaf_class(i, n) == 1)
{
size_t l = get_left(i);
if (data_array.at(l) < data_array.at(i))
{
T tmp = data_array.at(i);
data_array.at(i) = data_array.at(l);
data_array.at(l) = tmp;
}
}
}
size_t get_heap_size()
{
return heap_size;
}
int leaf_class(size_t i, int n)
{
if ((2 * i) < n && (2 * i + 1) < n)
return 2;
if ((2 * i) < n + 1 && (2 * i + 1) == n)
return 1;
else
return 0;
}
void build_min_heap()
{
for (int i = floor(heap_size / 2) + 1; i >= 0; i--)
min_heapify(i, get_heap_size() - 1);
}
void init_data(T* input, int n)
{
for (int i = 0; i < n; i++)
{
data_array.at(i) = *(input + i);
heap_size++;
}
}
void print_heap()
{
for (int i = 0; i < heap_size; i++)
cout << data_array.at(i) << endl;
}
array<T, 1024> data_array = { 0 };
size_t heap_size = 0;
private:
protected:
};
template<typename T>
class min_priority_queue :public min_heap<T>
{
public:
T minimum()
{
if ((this->heap_size) == 0)
assert("the max heap is empty!!!");
else
return this->data_array[0];
}
T heap_extract_min()
{
if (this->heap_size == 0)
assert("the queue is empty!!1");
else
{
T min = this->data_array.at(0);
this->data_array.at(0) = this->data_array.at(this->heap_size - 1);
this->heap_size--;
this->min_heapify(0, this->heap_size - 1);
return min;
}
}
void heap_increase_key(int i, T key)
{
assert(0 <= i && i < this->heap_size);
if (key > this->data_array.at(i))
assert("new key is larger than current key!");
else
{
this->data_array.at(i) = key;
while (i > 0 && this->data_array.at(this->get_parent(i)) > this->data_array.at(i))
{
std::swap(this->data_array.at(i), this->data_array.at(this->get_parent(i)));
i = this->get_parent(i);
}
}
}
void insert(T key)
{
this->data_array.at(this->get_heap_size()) = key;
this->heap_size++;
heap_increase_key(this->get_heap_size() - 1, key);
}
};
測試代碼如下:
void test_min_heap()
{
min_heap<int> heap;
int data[10] = { 4,1,3,2,16,9,10,14,8,7 };
heap.init_data(data, 10);
heap.build_min_heap();
heap.print_heap();
//heap_sort<int>(heap, 10);
}
void test_min_priority_queue()
{
min_priority_queue<int> queue;
int data[10] = { 4,1,3,2,16,9,10,14,8,7 };
queue.init_data(data, 10);
queue.build_min_heap();
queue.print_heap();
cout << endl;
/*queue.heap_extract_min();
queue.print_heap();*/
/*queue.heap_increase_key(1, 0);
queue.print_heap();*/
queue.insert(0);
queue.print_heap();
}