SGI STL源码剖析——stack、queue、priority_queue

配接器

stack、queue、priority_queue这三个结构并不是真正意义上的STL容器,他们使用现有的容器作为底层结构,然后对外封装接口,从而表现出不同的特性,此即配接器。

stack、queue

stack和默认使用deque作为底层容器,stack实现了栈顶访问top(),进栈和出栈的push()、pop(),deque支持队列头部front()和队尾back(),支持进队出队push()、pop()
stack和deque也可以list作为底层容器,显示指定

queue<int, list<int>> que;
stack<int, list<int>> sta;

priority_queue

优先队列是一种非常有用的结构,STL的优先队列以vector作为底层容器,通过堆的处理来实现,因此有必要先来学习一下STL对堆的实现。
堆是用数组实现的完全二叉树,堆根据堆属性来排序,堆属性决定了树种节点的位置。堆分为大根堆和小根堆,
大根堆:根节点的值比每一个子节点都要大
小根堆:根节点的值比每一个子节点都要小
注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。–唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。
使用数组存储,主要是建立所以和节点的关系。如果索引从0开始,那么i对应的父节点索引就是(i-1)/2,i的左右子节点分别是2i+1和2(i+1),STL仅仅实现的是大根堆。
对于堆来说,最主要的是两个操作,上浮和下沉,以大根堆为例
上浮:从当前结点开始,和它的父节点比较,若是比父节点大,就交换,然后将父节点作为新操作节点,继续直到不需要交换或者直到根节点为止
下沉:让当前结点的左右子节点(如果有的话)作比较,哪个比较大就和它交换,然后将子节点作为新操作节点,继续直到不需要交换或者直到叶子节点为止

我们先来看一下heap是怎么用的,主要是这几个函数,
make_heap(),pop_heap(),push_heap(),sort_heap();

int main()
{
    vector<int> arr1{1, 2, 3, 4, 5, 6, 7};
	// 创建堆
    make_heap(arr1.begin(), arr1.end());
	// 要往堆里插元素,必须把新元素放到数组尾部
    arr1.push_back(10);
    push_heap(arr1.begin(), arr1.end());
	// pop_heap执行完并没有移除根节点,而是放到了数组尾部
    pop_heap(arr1.begin(), arr1.end());
    arr1.pop_back();
}

对于堆来说,主要是push_heap和pop_heap,

// 先来看一下不指定比较函数的版本
template <class _RandomAccessIterator>
inline void 
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
	// 调用前,新元素已置于4底部容器的最尾端,这就是上面的例子为什么要先push_back的原因
	__push_heap_aux(__first, __last,  __DISTANCE_TYPE(__first), __VALUE_TYPE(__first));               
}
//推断出__DISTANCE_TYPE和__VALUE_TYPE类型
template <class _RandomAccessIterator, class _Distance, class _Tp>
inline void 
__push_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Distance*, _Tp*)               
{
	__push_heap(__first, _Distance((__last - __first) - 1), _Distance(0),  _Tp(*(__last - 1)));	    
}
// 然后看最终的这个调用
//对比上面传过来的参数(__first, _Distance((__last - __first) - 1), _Distance(0),_Tp(*(__last - 1))
//分别是(堆首元素位置,需要处理的元素距离首元素的distance,根节点位置距离首元素的distance,待处理的值)
template <class _RandomAccessIterator, class _Distance, class _Tp>
void 
__push_heap(_RandomAccessIterator __first,  _Distance __holeIndex, _Distance __topIndex, _Tp __value)           
{
	// 父节点,因为数组是0-index,所以是(i - 1) / 2
	// 还有需要注意这里计算出来的是相对first迭代器的距离,所以真正的父节点应该是__first + __holeIndex
	_Distance __parent = (__holeIndex - 1) / 2;
	// 父节点小于新值时进行上浮操作,__holeIndex > __topIndex 表示没有达到根节点
	// (__first + __parent) < __value是比较父节点和当前要处理的值的大小
	while (__holeIndex > __topIndex && *(__first + __parent) < __value) {
		//交换当前处理节点和父节点的值,注意我们变更的知识所以或者说节点,但是比较的值始终是value
		//这里只是把父节点的值放到子节点,原来的父节点对应的值则变成value
		//但是并不需要给父节点赋值,因为只要比较value就可以了
		*(__first + __holeIndex) = *(__first + __parent);
		//变更父节点为新的待处理节点
		__holeIndex = __parent;
		//更新节点的父节点
		__parent = (__holeIndex - 1) / 2;
	}
	//找到最终位置,把value放进去就行了    
	*(__first + __holeIndex) = __value; 
}

如果指定比较函数,

template <class _RandomAccessIterator, class _Distance, class _Tp, class _Compare>          
void
__push_heap(_RandomAccessIterator __first, _Distance __holeIndex,
            _Distance __topIndex, _Tp __value, _Compare __comp)
{
  _Distance __parent = (__holeIndex - 1) / 2;
  //这里使用自定义的比较函数比较迭代器指向的元素值
  while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value)) {
    *(__first + __holeIndex) = *(__first + __parent);
    __holeIndex = __parent;
    __parent = (__holeIndex - 1) / 2;
  }
  *(__first + __holeIndex) = __value;
}

push_heap就是上浮的操作,先把插入元素放到数组末尾然后一直上浮找到最终位置,比较清晰。而pop_heap执行下沉操作,要复杂些,

template <class _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)                    
{
	__pop_heap_aux(__first, __last, __VALUE_TYPE(__first));
}
template <class _RandomAccessIterator, class _Tp>
inline void 
__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp*)              
{
	//注意这里传下去的last是last-1了,说明迭代器范围变成[first, last-1)
	//最后一个元素是__last - 1
	__pop_heap(__first, __last - 1, __last - 1, 
	           _Tp(*(__last - 1)), __DISTANCE_TYPE(__first));
}

template <class _RandomAccessIterator, class _Tp, class _Distance>
inline void 
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
           _RandomAccessIterator __result, _Tp __value, _Distance*)
{
	//这是整个pop_heap的前提,把根节点也就是堆顶放到数组最后一个元素
	//result是上面传的 __last - 1,就是最后一个元素, __value是原来的最后一个元素
	*__result = *__first; 
	//(首迭代器, 需要调整的元素位置, 迭代器范围长度, 需要pop出的value)
	//注意这时候的last已经是last-1了
	__adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
}
template <class _RandomAccessIterator, class _Distance, class _Tp>
void 
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value)           
{
	//下沉总是从根节点开始操作,就是数组的0索引
	_Distance __topIndex = __holeIndex;
	//0-index的右子节点
	_Distance __secondChild = 2 * __holeIndex + 2;
	// __secondChild < __len表示还没有到叶子节点
	while (__secondChild < __len) {
		//这里判断一下左右子节点哪个值大,因为我们要和大的子节点值交换
		//注意我们根本没用到父节点的值
		if (*(__first + __secondChild) < *(__first + (__secondChild - 1)))
			__secondChild--;
		//交换值,同时要操作的节点变更为交换的那个子节点
		*(__first + __holeIndex) = *(__first + __secondChild);
		__holeIndex = __secondChild;
		//更新子节点,如果没有子节点就会大于len,跳出循环,但是holeIndex已经改变成了子节点
		__secondChild = 2 * (__holeIndex + 1);
	}
	//只有左子节点,因为此时左子节点就是数组最后一个元素了
	if (__secondChild == __len) {
		*(__first + __holeIndex) = *(__first + (__secondChild - 1));
		__holeIndex = __secondChild - 1;
	}
	//这一步必须执行,因为value我们还没放进去
	__push_heap(__first, __holeIndex, __topIndex, __value);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值