STL源码剖析之stl_heap(三)

Head并不是STL中的容器组件,它是优先级队列(priority queue)的底层实现,优先级队列允许用户以任何次序将任何元素推入容器内,但是取出时一定是从优先权最高的元素开始取,最大二叉堆正是具有这样的特性,适合作为priority queue的底层机制。

堆堆分为大堆小堆小堆指的是每个父节点的值都比子节点小,相反为大堆;故可以实现堆结构,但是要在插入时候实现自动调整。

调整函数:

SiftDown()//向下调整:根节点先下来,把最后一个节点移到根节点,然后进行调整,重新满足小堆特点,以此类推实现排序

SiftUp()//向上调整:指插入新数据后依然满足大小堆

下面分别用两种方法实现上面两个函数(都采用小堆方式):

#include<iostream>
using namespace std;

#define DEFAULT_SIZE 20

template<class Type>
class Heap
{
public:
	Heap()
	{
		heap = new Type[DEFAULT_SIZE];
		memset(heap, 0, sizeof(Type)*DEFAULT_SIZE);
		cursize = 0;
		maxsize = DEFAULT_SIZE;
	}
	virtual ~Heap()
	{
		delete []heap;
		heap = NULL;
		cursize = maxsize = 0;
	}
public:
	virtual bool Insert(const Type &x) = 0;
	virtual bool Remove(Type &x) = 0;
	bool IsEmpty()const
	{
		return cursize == 0;
	}
	bool IsFull()const
	{
		return cursize >= maxsize;
	}
	int size()const
	{return cursize;}
	Type& operator[](int i)
	{
		return heap[i];
	}
protected:
	virtual void SiftUp(int start) = 0;
	virtual void SiftDown(int start, int n) = 0;
protected:
	Type *heap;
	int cursize;
	int maxsize;
};

////////////////////////////////////////////////////////////////
//MinHeap
template<class Type> 
class MinHeap : public Heap<Type>
{
public:
	bool Insert(const Type &x)
	{
		if(IsFull())
		{
			cout<<"堆已满,不能插入数据."<<endl;
			return false;
		}
		heap[cursize] = x;
		SiftUp(cursize);
		cursize++;
		return true;
	}
	bool Remove(Type &x)
	{
		x = heap[0];
		heap[0] = heap[--cursize];
		SiftDown(0, cursize-1);
		return true;
	}
protected:
	void SiftUp(int start)
	{
		int j = start;
		int i = (j-1) / 2;
		while(j > 0)
		{
			if(heap[j] < heap[i])
			{
				Type tmp = heap[j];
				heap[j] = heap[i];
				heap[i] = tmp;
			}
			j = i;
			i = (j-1) / 2;
		}
	}
	void SiftDown(int start, int n)
	{
		int i = start;

		int j = 2*i+1;
		while(j <= n)
		{
			if(j+1<=n && heap[j]>heap[j+1])
				j = j+1;
			if(heap[i] > heap[j])
			{
				Type tmp = heap[i];
				heap[i] = heap[j];
				heap[j] = tmp;
			}
			i = j;
			j = 2*i+1;

		}
	}
};

void main()
{
int ar[] = {41,435,14,45,54,15,51,65,76,8,1};
	//int ar[] = {8,5,9,2,1};
	int n = sizeof(ar) / sizeof(int);
	int i;
	MinHeap<int> mp;
	for( i=0; i<n; ++i)
	{
		mp.Insert(ar[i]);
	}
	for(i=0; i<mp.size(); ++i)
	{
		cout<<mp[i]<<" ";
	}
	cout<<endl;

	int x;
	i = 1;
	while(i <= 3)
	{
		mp.Remove(x);
		cout<<x<<"删除"<<" ";
		cout<<endl;
		++i;
	}
	for(i=0; i<mp.size(); ++i)
	{
		cout<<mp[i]<<" ";
	}
	cout<<endl;

}

上述方法采用子节点若小于父节点就交换的算法,效率相对低,下面采用另一种算法:

//stl_heap.h
#pragma once
#ifndef _STL_HEAP_H
#define _STL_HEAP_H

#include<iostream>
using namespace std;
#define DEFAULT_SIZE 20

template <class T>
class Heap
{
public:
	Heap()
	{
		heap = new T[DEFAULT_SIZE];
		memset(heap , 0 , sizeof(T)*DEFAULT_SIZE);
		cursize = 0;
		maxsize = DEFAULT_SIZE;
	}
	virtual bool Insert(const T& value)= 0 ;
	virtual bool Remove(T &x) = 0;
	bool IsFull(){return cursize >= maxsize;}
	bool IsEmpt(){return cursize == 0;}
	int size(){return cursize;}
	T& operator[](int i){return heap[i];}
	virtual ~Heap()
	{
		delete []heap;
		heap = NULL;
		cursize = maxsize = 0;
	}
protected:
	virtual void SiftUp(int start) = 0;
	virtual void SiftDown(int start , int n) = 0;
protected:
	T* heap;
	int cursize;
	int maxsize;
};


template<class T>
class MinHeap : public Heap<T>
{
public:
	bool Insert(const T &x)
	{
		if(IsFull())
		{
			cout<<"Heap is FULL."<<endl;
			return false;
		}
		else
		{
			heap[cursize] = x;
			SiftUp(cursize);
			cursize++;
			return true;
		}
	}
	bool Remove(T &x)
	{
		x = heap[0];
		heap[0] = heap[--cursize];
		SiftDown(0, cursize-1);
		return true;
	}
protected:
	void SiftUp(int start)
	{
		int first = start;
		T tmp = *(heap + first);
		int parent = (first-1) / 2;
		while(parent >= 0 && *(heap + parent) > tmp)
		{
			*(heap + first) =*(heap +parent) ;
			*(heap + parent) = tmp;
			first = parent;
			parent = (first-1) / 2;
		}
	}
	void SiftDown(int start, int n)
	{
		int first = start;
		T tmp = *(heap+first);
		int child = 2*first +1;
		while(child <= n && *(heap+first) > *(heap + child))
		{
			if(child + 1<=n && *(heap+child) > *(heap+child+1))
				child = child + 1;
			*(heap+first) = *(heap+child);
			*(heap+child) = tmp;
			first = child;
			child = 2*first+1;
		}
	}
};
#endif
//主函数

#include"stl_heap.h"

void main()
{
    int ar[] = {41,435,14,45,54,15,51,65,76,8,1};
	//int ar[] = {8,5,9,2,1};
	int n = sizeof(ar) / sizeof(int);
	int i;
	MinHeap<int> mp;
	for( i=0; i<n; ++i)
	{
		mp.Insert(ar[i]);
	}
	for(i=0; i<mp.size(); ++i)
	{
		cout<<mp[i]<<" ";
	}
	cout<<endl;

	int x;
	i = 1;
	while(i <= 3)
	{
		mp.Remove(x);
		cout<<x<<"删除"<<" ";
		cout<<endl;
		++i;
	}
	for(i=0; i<mp.size(); ++i)
	{
		cout<<mp[i]<<" ";
	}
	cout<<endl;

}

第二种方法,采用先存储新插入或删除值,满足条件子父结点进行覆盖,最后在循环后的下表位置进行赋值即可。

在编写第二种方法时候遇到的问题:中间值tmp的定义误用了引用返回,造成了插入的值总是第一个,在一步步调试后发现问题,加深了对引用返回的认识,引用返回即使用的相同的空间,故肯定不能进行交换值。

需要注意的陷阱:

Stl_head的底层是借助矢量来实现通过make_head来创建堆,

扫描二维码关注公众号,回复: 2354879 查看本文章

其中push_back方法仅仅是在尾部插入,并且不会立即进行调整堆的结构,必须调用push_head才会进行调整;

pop_back是真实的删除,pop_heap不会真正的进行数据删除。

猜你喜欢

转载自blog.csdn.net/zzb2019/article/details/81189244