9.STL简单binary heap的实现

我用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如下:

时间: 2024-10-10 03:52:05

9.STL简单binary heap的实现的相关文章

二叉堆(binary heap)

堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权.堆即为解决此类问题设计的一种数据结构. 本文地址:http://www.cnblogs.com/archimedes/p/binary-heap.html,转载请注明源地址. 逻辑定义 n个

C++之Binary Heap/Max Heap

1 #include <iostream> 2 #include <time.h> 3 #include <random> 4 5 using namespace std; 6 7 //Binary Heap; Max Heap; 8 9 class BinaryHeap 10 { 11 public: 12 BinaryHeap(); 13 BinaryHeap(int capacity); 14 ~BinaryHeap(); 15 16 int insert(int

Binary Heap

(referrence: cmu_binary_heap) Definition A binary heap is a complete binary tree arranged in heap ordering property. There are two types of ordering: 1. min-heap The value of each node >= the value of its parent. Root is minimum-value element. 2. max

STL简单&lt;stl_algorithms.h&gt;算法的实现

1.简介 STL标准中,没有区分基本算法和复杂算法,然而SGI STL却把常用的算法定义在<stl_algorithms.h>中.本文介绍部分<stl_algorithms.h>算法的实现,给出实现代码和测试代码. 本文介绍的算法包括: 1.      mismatch:比较两个序列,指出两者之间第一个不匹配的点,返回一对迭代器,分别指向两序列中不匹配的点: 2.      equal:如果两个序列在 [first, last ] 区间内相等,equal() 返回true,忽略第二

STL简单 copy 算法的实现

1.简介 不论是对客户端或对STL内部而言,copy() 都是一个常常被调用的函数.由于copy进行的是复制操作,而复制操作不外乎运用赋值运算符(assignment operator)或复制构造函数(copy constructor),但是某些元素的类型是trivial assignment operator,因此如果能使用内存直接进行复制(例如使用C标准函数memmove.memcpy),便能节约大量时间.为此,copy算法用尽各种办法,包括函数重载(function overloading

堆(Heap)和二叉堆(Binary heap)

堆(Heap): The operations commonly performed with a heap are: create-heap: create an empty heap heapify: create a heap out of given array of elements find-max or find-min: find the maximum item of a max-heap or a minimum item of a min-heap (aka, peek)

11.STL简单set和multiset的实现

set的特性是所有元素都会根据键值自动排序,set的元素不像map那样同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值.Set不允许两个元素拥有相同的键值.不能通过迭代器修改set元素的值. multiset和set的唯一区别在于multiset允许键值重复. 我们采用红黑树作为set和multiset的底层数据结构,set和multiset的实现完完全全是在红黑树的基础上封装的一层接口,所有的set和multiset操作都转而调用红黑树的API.有关STL红黑

STL简单hashtable的实现

1. 简介 Hash_table可提供对任何有名项(nameditem)的存取.删除和查询操作.由于操作对象是有名项,所以hash_table可被视为是一种字典结构(dictionary). Hash_table使用名为hashfaction的散列函数来定义有名项与存储地址之间的映射关系.使用hash faction会带来一个问题:不同的有名项可能被映射到相同的地址,这便是所谓的碰撞(collision)问题,解决碰撞问题的方法主要有三种:线性探测(linear probing).二次探测(qu

C++11新特性应用--介绍几个新增的便利算法(stl中的heap使用,最大堆)

有的时候为了维护数据,我们使用stl的堆去维护一序列. 首先您要弄清楚堆和栈的区别,即heap和stack stl中的堆默认是最大堆. 先介绍 push_heap,pop_heap,make_heap,sort_heap这四个算法,这四个不是C++11新增加的内容. 首先是如何产生一个最大推: make_heap 原型: template <class RandomAccessIterator> void make_heap (RandomAccessIterator first, Rando