我用VS2013写的程序(github ),queue版本的代码位于cghSTL/version/cghSTL-0.3.6.rar
所谓binary heap就是一种完全二叉树,也就是说,整颗binary tree除了对底层的叶节点外,是填满的,而最底层的叶节点由左至右不能有空隙。
完全二叉树内没有任何节点漏洞,这带来一个极大的好处:我们可以利用vector来存储所有节点。我们把vector的0号元素保留,那么当完全二叉树的某个节点位于vector的i处时,其左子树必然位于2i处,右子树必然位于2i + 1处,其父节点必然位于i / 2处。通过这么简单的规则,vector就能轻易实现完全二叉树。
我们需要的工具很简单:一个array和一组heap算法(用来添加、删除元素,将某组数组排列成binary heap)。
根据元素排列规则,heap分为max-heap和min-heap,前者每个节点的值大于等于子节点,后者每个节点的值小于等于子节点。我们接下来构建的是max-heap。
Binary heap的实现需要以下几个文件:
1. globalConstruct.h,构造和析构函数文件,位于cghSTL/allocator/cghAllocator/
2. cghAlloc.h,空间配置器文件,位于cghSTL/allocator/cghAllocator/
3. cghHeap.h,queue的实现,位于cghSTL/sequence
containers/cghHeap/
4. test_cghHeap.cpp,测试代码,位于cghSTL/test/
介绍两个核心算法:
1.push_heap算法
新加入的元素一定要放在最下层作为叶节点,并填补在从左至右的第一个空格处。为满足max-heap的条件(每个节点的值大于等于其子节点的值),我们要执行上溯(percolate up)程序:将新加入的节点拿来与其父节点比较,如果值比父节点大,就父子对换位置,一直上溯,直到不需要对换或直到跟节点为止。
2.pop_heap算法
Pop操作取走根节点(max-heap中根节点值最大)。为了满足每个节点的值都大于子节点,我们需要执行下溯(percolate down)程序:割舍最下层最右边的叶节点,并将其值重新安插到max-heap中(要重新调整max-heap的结构),然后将空间节点和其较大子节点对调,并持续下放,直至叶节点为止,并将前述被割舍元素的值设置给这个“已到达叶层的空节点”,再对它执行一次percolate up(上溯)。
下面进入正题,介绍heap的代码
1.构造与析构
先看第一个,globalConstruct.h构造函数文件
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:全局构造和析构的实现代码 ******************************************************************/ #include "stdafx.h" #include <new.h> #include <type_traits> #ifndef _CGH_GLOBAL_CONSTRUCT_ #define _CGH_GLOBAL_CONSTRUCT_ namespace CGH { #pragma region 统一的构造析构函数 template<class T1, class T2> inline void construct(T1* p, const T2& value) { new (p)T1(value); } template<class T> inline void destroy(T* pointer) { pointer->~T(); } template<class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { // 本来在这里要使用特性萃取机(traits编程技巧)判断元素是否为non-trivial // non-trivial的元素可以直接释放内存 // trivial的元素要做调用析构函数,然后释放内存 for (; first < last; ++first) destroy(&*first); } #pragma endregion } #endif
按照STL的接口规范,正确的顺序是先分配内存然后构造元素。构造函数的实现采用placement new的方式;为了简化起见,我直接调用析构函数来销毁元素,而在考虑效率的情况下一般会先判断元素是否为non-trivial类型,non-trivial的元素可以直接释放内存,可以理解为non-trivial全部分配在栈上,不用我们操心,由程序自动回收,trivial的元素要分配在堆上,需要手工调用析构函数来销毁,然后释放内存。
2.空间配置器
cghAlloc.h是空间配置器文件,空间配置器负责内存的申请和回收。
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 功能:cghAllocator空间配置器的实现代码 ******************************************************************/ #ifndef _CGH_ALLOC_ #define _CGH_ALLOC_ #include <new> #include <cstddef> #include <cstdlib> #include <climits> #include <iostream> namespace CGH { #pragma region 内存分配和释放函数、元素的构造和析构函数 // 内存分配 template<class T> inline T* _allocate(ptrdiff_t size, T*) { set_new_handler(0); T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)))); if (tmp == 0) { std::cerr << "out of memory" << std::endl; exit(1); } return tmp; } // 内存释放 template<class T> inline void _deallocate(T* buffer) { ::operator delete(buffer); } // 元素构造 template<class T1, class T2> inline void _construct(T1* p, const T2& value) { new(p)T1(value); } // 元素析构 template<class T> inline void _destroy(T* ptr) { ptr->~T(); } #pragma endregion #pragma region cghAllocator空间配置器的实现 template<class T> class cghAllocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; template<class U> struct rebind { typedef cghAllocator<U> other; }; static pointer allocate(size_type n, const void* hint = 0) { return _allocate((difference_type)n, (pointer)0); } static void deallocate(pointer p, size_type n) { _deallocate(p); } static void deallocate(void* p) { _deallocate(p); } void construct(pointer p, const T& value) { _construct(p, value); } void destroy(pointer p) { _destroy(p); } pointer address(reference x) { return (pointer)&x; } const_pointer const_address(const_reference x) { return (const_pointer)&x; } size_type max_size() const { return size_type(UINT_MAX / sizeof(T)); } }; #pragma endregion #pragma region 封装STL标准的空间配置器接口 template<class T, class Alloc = cghAllocator<T>> class simple_alloc { public: static T* allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T)); } static T* allocate(void) { return (T*)Alloc::allocate(sizeof(T)); } static void deallocate(T* p, size_t n) { if (0 != n)Alloc::deallocate(p, n*sizeof(T)); } static void deallocate(void* p) { Alloc::deallocate(p); } }; #pragma endregion } #endif
classcghAllocator是空间配置器类的定义,主要的四个函数的意义如下:allocate函数分配内存,deallocate函数释放内存,construct构造元素,destroy析构元素。这四个函数最终都是通过调用_allocate、_deallocate、_construct、_destroy这四个内联函数实现功能。
我们自己写的空间配置器必须封装一层STL的标准接口,
template<classT, class Alloc = cghAllocator<T>> classsimple_alloc
构造与析构函数、空间配置器是最最基本,最最底层的部件,把底层搭建好之后我们就可以着手设计cghHeap了。
3.cghHeap的实现
我把cghHeap的内部结构分为以下部分
1. 一堆typedef、成员变量的声明;
2. cghHeap的构造与析构函数,以及构造与析构的辅助函数;
3. cghHeap的读操作;
4. cghHeap的写操作,以及写操作的辅助函数;
cghHeap代码的注释已经写得十分详细了,有疑问的地方我都给出了说明,童鞋们可以参考cghHeap的内部结构来总体把握cghHeap的框架,通过注释来理解cghHeap的工作原理。
cghHeap.h代码如下:
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件名称:cghHeap容器的实现 ******************************************************************/ #ifndef _CGH_HEAP_ #define _CGH_HEAP_ #include <memory> #include "globalConstruct.h" #include "cghAlloc.h" namespace CGH { template<class T, class Alloc = cghAllocator<T>> class cghHeap { public: typedef T value_type; typedef value_type* pointer; typedef value_type* iterator; typedef value_type& reference; typedef size_t size_type; typedef ptrdiff_t difference_type; protected: typedef simple_alloc<value_type, Alloc> data_allocator; // 定义空间配置器 iterator start; iterator finish; iterator end_of_storage; #pragma region 构造和析构的辅助函数 /* * fill_initialize和allocate_and_fill把cghHeap的初始化分为了两步: * 1.fill_initialize的职责是分配一段内存 * 2.fill_initialize调用allocate_and_fill,在分配的内存中调用构造函数创建cghHeap的元素 */ void fill_initialize(size_type n, const T& value) { start = allocate_and_fill(n, value); finish = start + n; end_of_storage = finish; } iterator allocate_and_fill(size_type n, const T& x) { iterator result = data_allocator::allocate(n); iterator cur = result; for (; n > 0; --n, ++cur) { construct(&*cur, x); } return result; } /* * 释放内存,析构对象 */ void deallocate() { if (start) { data_allocator::deallocate(start, end_of_storage - start); } } #pragma endregion public: #pragma region 构造函数和析构函数 cghHeap() :start(0), finish(0), end_of_storage(0) { } // 初始化空的cghHeap cghHeap(size_type n, const T& value){ fill_initialize(n, value); } // 初始化包含n个值为value的cghHeap cghHeap(int n, const T& value){ fill_initialize(n, value); } // 同上 cghHeap(long n, const T& value){ fill_initialize(n, value); } // 同上 explicit cghHeap(size_type n){ fill_initialize(n, T()); } // 初始化cghHeap的长度为n ~cghHeap() { destroy(start, finish); // 先调用cghHeap中元素的析构函数 deallocate(); // 再释放cghHeap占用的内存 } #pragma endregion #pragma region cghHeap的读操作 iterator begin(){ return start; } // 返回cghHeap头元素的地址 iterator end(){ return finish; } // 返回cghHeap尾元素的地址 size_type size(){ return size_type(int(end() - begin())); } // cghHeap的长度 = 尾元素地址 - 头元素地址 #pragma endregion #pragma region cghHeap的写操作 /** * 在cghHeap末尾插入一个元素 */ void push_back(const T& x) { // 判断cghHeap的容量是否满了,如果没满我们直接在已有的内存区域上构造元素 if (finish != end_of_storage) { construct(finish, x); ++finish; } else // 如果满了我们就要重新分配内存并重新构造函数 { insert_aux(end(), x); } } /** * 弹出尾元素 */ void pop_back() { --finish; destroy(finish); } inline void make_heap(iterator first, iterator last) { if (last - first < 2)return; difference_type len = last - first; difference_type parent = len / 2; while (true) { _adjust_heap(first, parent, len, T(*(first + parent))); if (parent == 0)return; --parent; } } inline void push_heap(iterator first, iterator last) { difference_type holeIndex = last - first - 1; difference_type topIndex = 0; T value = T(*(last - 1)); _push_heap(first, holeIndex, topIndex, value); } inline void pop_heap(iterator first, iterator last) { __pop_heap(first, last - 1, last - 1, T(*(last - 1))); } inline void sort_heap(iterator first, iterator last) { while (last - first > 1) { pop_heap(first, last--); } } #pragma endregion protected: #pragma region cghHeap写操作辅助操作 void insert_aux(iterator position, const T& value) { if (finish != end_of_storage) { construct(finish, *(finish - 1)); ++finish; T x_copy = value; std::copy_backward(position, finish - 2, finish - 1); *position = x_copy; } else { ptrdiff_t old_size = size(); const size_type len = old_size != 0 ? 2 * old_size : 1; /* 配置原则:如果原大小为0,则配置1个元素大小 如果原大小不为0,则配置原大小的两倍 */ iterator new_start = data_allocator::allocate(len); iterator new_finish = new_start; try { // 把 start 到 position 这段内存拷贝到 new_start 处,返回 new_finish = new_start + ( position - start ) new_finish = std::uninitialized_copy(start, position, new_start); construct(new_finish, value); // 在 new_finish 处构造新元素 ++new_finish; //new_finish = std::uninitialized_copy(position, finish, new_finish); } catch (std::exception ex) { // 如果执行失败就要回滚 destroy(new_start, new_finish); data_allocator::deallocate(new_start, len); throw; } destroy(begin(), end()); deallocate(); start = new_start; finish = new_finish; end_of_storage = new_start + len; } } void __pop_heap(iterator first, iterator last, iterator result, T value) { *result = *first; _adjust_heap(first, difference_type(0), difference_type(last - first), value); } void _adjust_heap(iterator first, difference_type holeIndex, difference_type len, T value) { difference_type topIndex = holeIndex; difference_type secondChild = 2 * holeIndex + 2; while (secondChild < len) { if (*(first + secondChild) < *(first + (secondChild - 1))) { --secondChild; } *(first + holeIndex) = *(first + secondChild); holeIndex = secondChild; secondChild = 2 * (secondChild + 1); } if (secondChild == len) { *(first + holeIndex) = *(first + (secondChild - 1)); holeIndex = secondChild - 1; } _push_heap(first, holeIndex, topIndex, value); } void _push_heap(iterator first, difference_type holeIndex, difference_type topIndex, T value) { difference_type parent = (holeIndex - 1) / 2; while (holeIndex>topIndex && *(first + parent) < value) { *(first + holeIndex) = *(first + parent); holeIndex = parent; parent = (holeIndex - 1) / 2; } *(first + holeIndex) = value; } #pragma endregion }; } #endif
4.测试
测试环节的主要内容已在注释中说明
test_heap.cpp:
/******************************************************************* * Copyright(c) 2016 Chen Gonghao * All rights reserved. * * [email protected] * * 文件名称:cghHeap容器的测试代码 ******************************************************************/ #include "stdafx.h" #include "cghHeap.h" using namespace::std; int _tmain(int argc, _TCHAR* argv[]) { using namespace::CGH; cghHeap<int> test1; std::cout << "************************ max-heap 生成测试************************" << endl << endl; std::cout << "依次压入元素:\t0,1,2,3,4,8,9,3,5,我们要把压入的元素排成一颗完全二叉树" << endl << endl; test1.push_back(0); test1.push_back(1); test1.push_back(2); test1.push_back(3); test1.push_back(4); test1.push_back(8); test1.push_back(9); test1.push_back(3); test1.push_back(5); test1.make_heap(test1.begin(), test1.end()); // 生成heap std::cout << "生成 max-heap:\t" ; for (cghHeap<int>::iterator it = test1.begin(); it != test1.end(); ++it) { std::cout << *it << ","; } std::cout << endl << endl; std::cout << "************************ max-heap 压入测试************************" << endl << endl; std::cout << "压入元素:\t\t7,压入后我们要对 max-heap 这颗完全二叉树重新排序" << endl << endl; test1.push_back(7); test1.push_heap(test1.begin(), test1.end()); // 重新排序 std::cout << "压入 7 之后的 max-heap:\t"; for (cghHeap<int>::iterator it = test1.begin(); it != test1.end(); ++it) { std::cout << *it << ","; } std::cout << endl << endl; std::cout << "************************ max-heap 弹出测试************************" << endl << endl; std::cout << "我们只能弹出 max-heap 这颗二叉树的根节点" << endl << endl; test1.pop_heap(test1.begin(), test1.end()); std::cout << "弹出根节点 9 之后的 max-heap:\t"; test1.pop_back(); for (cghHeap<int>::iterator it = test1.begin(); it != test1.end(); ++it) { std::cout << *it << ","; } std::cout << endl << endl; system("pause"); return 0; }
生成测试中生成的序列为:9,5,8,3,4,0,2,3,1,对应的max-heap结构如下:
可以发现,每个节点的值都大于等于其子节点的值。
压入7之后的序列为:9,7,8,3,5,0,2,3,1,4,对应的max-heap如下:
弹出根节点9之后的序列为:8,7,4,3,5,0,2,3,1,对应的max-heap如下: