数据结构::堆及堆的应用~

【堆】:

1.概念:

  堆是一种特殊的树形结构,堆的把每一个节点都有一个值,我们通常所说的堆这种数据结构,指的就是二叉堆。  

  其实它可以被看做一个完全二叉树。

  它的每个节点的子树又可以被看做是堆。

2.分类:

   堆可以分为最大堆和最小堆:

   最大堆:每个父节点都大于孩子节点

   最小堆:每个父节点都小于孩子节点

3.关于堆的实现:

  1)如何建堆

     A:我们在定义堆的数据成员的时候,利用STL中的vector来进行数据成员的创建(为什么要这么做,我后面有说明)

    vector<T> _a

     B:然后我们肯定是要给出构造函数

//构造函数
Heap(){}

     C:接着我们就来进行建堆,这里用到了一个很重要的方法:向下调整算法

     (我这里以建一个大堆为例)

        

       如上图:

       ##向下调整算法的说明:

          *要建一个大堆,即最后每一个堆的节点的值都大于它的孩子

          *我们先找左右孩子中最大的一个

          *然后让最大的一个孩子和父节点进行比较:

            如果孩子大于父节点的值,那么进行交换,并将孩子的值赋给父节点,孩子的值也随父节点的值变化

    否则就结束

          *进行向下调整是从根节点开始的,因此,最终的大循环是孩子的值要小于数组存储的元素的值

       ##建堆

          *我们最终的目的是要建一个大堆

          *每一个叶子节点都是一个堆

          *因此,我们要从第一个非叶子节点开始进行建立,然后逐步调用向下调整算法,实现大堆的建立

【说明】:关于使用vector来作为堆的数据成员的好处:

   使用vector了之后,我们在实现堆类的成员函数的时候,就不用显示的给出拷贝构造函数,赋值运算符重载函数和析构函数了,因为当我们用vector进行对象的创建的时候,编译器就会自动的调用vector的这些成员函数,我们就不用多此一举的进行给出了。      

//建堆
	Heap(T* a, size_t n)
		:_a(a,a+n)
	{
		for(int i = (_a.size()-2)/2; i>=0; i--)
		{
			_Adjustdown(i);
		}
	}
//向下调整
	void _Adjustdown(int root)
	{
		int parent = root;
		int child = parent*2+1;
		//此处的条件有两个:
		//一个是当孩子的值小于父母的值时候这个已经在break处理过了
		//第二个条件就是当是叶子节点的时候
		while(child<_a.size())
		{
			//找左右孩子中值最大的
			if(child+1<_a.size() && _a[child+1]>_a[child])
			{
				++child;
			}
			//将孩子和父母做比较
			if(_a[child]>_a[parent])
			{
				swap(_a[child],_a[parent]);
				parent = child;
				child = parent*2+1;
			}
			else
			{
				break;
			}
		}
	}

  2)堆中其他接口的实现

     A:尾插函数的实现:

           思路分析:先在尾部插入一个元素,然后要进行向上调整

           ##向上调整算法:和向下调整算法很相似

               *直接让孩子和父节点进行比较

                 如果孩子比父节点大的话,就将父亲赋给孩子,然后父亲的值进行改变,一直网上调整

                 否则结束

       *调整结束的另一个结束条件是当调整到最上面的堆顶的时候结束,即就是孩子的值<0

       代码如下:

//尾插
	void Push(const T& x)
	{
		_a.push_back(x);
		_Adjustup(_a.size()-1);
	}
//向上调整
	void _Adjustup(int i)
	{
		int child = i;
		int parent = (i-1)/2;
		while(child >= 0)
		{
			if(_a[child] > _a[parent])
			{
				swap(_a[child],_a[parent]);
				child = parent;
				parent = (parent-1)/2;
			}
			else
			{
				break;
			}
		}
	}
        B:堆顶元素删除的实现

            思路分析:

                   *先将要删除的堆顶的元素和最后一个元素进行交换

    *然后删除尾部的元素

    *再进行向下调整

             代码实现:

//尾删
	void Pop()
	{
		swap(_a[0],_a[_a.size()-1]);
		_a.pop_back();
		_Adjustdown(0);
	}

【堆的应用】:

1.优先级队列的实现:

  #概念:与普通的先进先出的队列不同的是,优先级队列每次都会取出队列中优先级最高的那一个

  #如何实现:(这里我们通过堆来实现优先级队列)

   *首先:优先级有高的优先级最高,低的优先级最高,这里我们如何进行这两个的控制:使用仿函数来进行实现

    *其次:我们是结合堆来进行具体实现的,所以优先级队列的一系列接口我们就通过直接调用堆里的接口就可以了

  #代码实现:

#include<iostream>
#include<vector>
using namespace std;
template<class T>
struct Less
{
	bool operator()(const T& a,const T& b)
	{
		return a<b;
	}
};
template<class T>
struct Greater
{
	bool operator()(const T& a,const T& b)
	{
		return a>b;
	}
};

template<typename T,class com = Greater<int>>
class Heap
{
public:
	//构造函数
	Heap()
	{}
	//建堆
	Heap(T* a, size_t n)
		:_a(a,a+n)
	{
		/*_a.reserve(n);
		for(size_t i = 0; i<n; i++)
		{
			_a.push_back(a[i]);
		}*/
		for(int i = (_a.size()-2)/2; i>=0; i--)
		{
			_Adjustdown(i);
		}
	}
	//尾插
	void Push(const T& x)
	{
		_a.push_back(x);
		_Adjustup(_a.size()-1);
	}
	//尾删
	void Pop()
	{
		swap(_a[0],_a[_a.size()-1]);
		_a.pop_back();
		_Adjustdown(0);
	}
	//求堆里元素的个数
	size_t Size()
	{
		return _a.size();
	}
	//求堆顶的元素
	T& Top()
	{
		return _a[0];
	}
	//判断堆是否为空
	bool Empty()
	{
		return _a.size()==0;
	}
	
protected:
	//向下调整
	void _Adjustdown(int root)
	{
		int parent = root;
		int child = parent*2+1;
		//此处的条件有两个:
		//一个是当孩子的值小于父母的值时候这个已经在break处理过了
		//第二个条件就是当是叶子节点的时候
		while(child<_a.size())
		{
			//找左右孩子中值最大的
			if(child+1<_a.size() && com()(_a[child+1],_a[child]))
			{
				++child;
			}
			//将孩子和父母做比较
			if(com()(_a[child],_a[parent]))
			{
				swap(_a[child],_a[parent]);
				parent = child;
				child = parent*2+1;
			}
			else
			{
				break;
			}
		}
	}
	//向上调整
	void _Adjustup(int i)
	{
		//Compare com;
		int child = i;
		int parent = (i-1)/2;
		while(child >= 0)
		{
			if(com()(_a[child] , _a[parent]))
			{
				swap(_a[child],_a[parent]);
				child = parent;
				parent = (parent-1)/2;
			}
			else
			{
				break;
			}
		}
	}
protected:
	vector<T> _a;
};
template<class T,class com = Greater<int>>
class PriorityQueue
{
public:
	//构造函数
	PriorityQueue()
	{}
	void Push_Back(const T& x)
	{
		_hp.Push(x);
	}
	void Pop_Front()
	{
		_hp.Pop();
	}
	size_t PQ_Size()
	{
		return _hp.Size();
	}
	T& PQ_Top()
	{
		return _hp.Top();
	}
	bool PQ_Empty()
	{
		return _hp.Empty();
	}
protected:
	Heap<T,com> _hp;

};
int main()
{
	int a [] = {10,16, 18, 12, 11, 13, 15, 17, 14, 19};
	Heap<int> hp1(a,sizeof(a)/sizeof(a[0]));
	hp1.Push(20);
	hp1.Pop();
	PriorityQueue<int,Less<int>> hep2;
	hp2.Push_Back(4);

	return 0;
}

2.大数据的处理:

   问题描述:  N个数据,此N 个数很大,找出最大的前K个

   思路分析:

        方法一:我们首先会想到排序(外排):因为是N个数据,并且N个数据是比较大的,有的读者可能会想到用数组进行存储,但是当数据非常大的时候,内存会不足,我们总不可能要买内存吧。但是外排是在磁盘上的,效率会很低。时间复杂度是Nlg(N).

        方法二:利用堆来进行实现

        *我们首先建立一个K大小的堆,

        *接着,我们是要找最大的前K个数,我们应该建大堆还是小堆呢,可能很多读者会不假思索的说建大堆,但是我们来仔细的考虑下,如果建大堆的话,堆顶的元素就是最大的,那么后面的元素就进不来了,只会找到一个最大的元素,所以我们要建小堆。

        *我们建好堆后,每次将来的一个元素和堆顶的元素进行比较,如果大于堆顶的元素的话,那么我们就将此元素直接赋给堆顶的元素。然后进行向下调整

   代码的实现:

/*函数说明:有N个数据,找最大的前k个*/
#include<iostream>
using namespace std;

void _Adjustdown(int* a,int n,int root)
{
	int parent = root;
	int child = 2*parent+1;
	while(child < n)
	{
		if(child+1<n && a[child+1]<a[child])
		{
			++child;
		}
		if(a[child]<a[parent])
		{
			swap(a[child],a[parent]);
			parent = child;
			child = parent*2+1;
		}
		else
		{
			break;
		}
	}
}

void TopK(int* a,int n,int k)
{
	//先建一个k大小的堆
	int* _heap = new int[k];
	//建小堆
	for(size_t i =0;i<k;i++)
	{
		_heap[i] = a[i];
	}

	for(int i = (k-2)/2; i>=0; --i)
	{
		_Adjustdown(_heap,k,i);
	}
	//将顶部的元素和后面的元素进行比较,如果比
	//顶部的元素大,直接赋值,然后向下调整
	for(int i = k; i<n; i++)
	{
		if(a[i]>_heap[0])
		{	
			_heap[0] = a[i];
			_Adjustdown(_heap,k,0);
		}
	}
	for(int i = 0; i<k; i++)
	{
		cout<<_heap[i]<<" ";
	}
	cout<<endl;
	delete[]_heap;
}

int main()
{
	const int N = 100000;
	int k = 10;
	int a[N];
	for(size_t i = 0;i<N;++i)
	{
		a[i] = rand()%1000;
	}

	a[5] = 1001;
	a[10] = 1005;
	a[99] = 1010;
	a[100] = 1100;
	a[111] = 1102;

	TopK(a,N,k);


	return 0;
}

3、堆排的实现:

   思路分析:

          *如果我们是升序的话,要建大堆

          *将第一个元素和最后一个元素进行交换,然后进行向下调整

          *然后循环第二步,知道堆里剩一个元素,结束

    代码分析:

#include<iostream>
#include<vector>
using namespace std;
template<typename T>
class Heap
{
public:
	//构造函数
	Heap()
	{}
	//建堆
	Heap(T* a, size_t n)
		:_a(a,a+n)
	{
		for(int i = (_a.size()-2)/2; i>=0; i--)
		{
			_Adjustdown(i);
		}
	}

	//堆排
	void HeapSort()
	{
		//升序,建大堆
		for(int i = (_a.size()-2)/2; i>=0; --i)
		{
			_Adjustdown(i);
		}
		int end = _a.size()-1;
		while(end>0)
		{
			swap(_a[0],_a[_a.size()-1]);
			_Adjustdown(end);
			--end;
		}
	}
protected:
	//向下调整
	void _Adjustdown(int root)
	{
		int parent = root;
		int child = parent*2+1;
		//此处的条件有两个:
		//一个是当孩子的值小于父母的值时候这个已经在break处理过了
		//第二个条件就是当是叶子节点的时候
		while(child<_a.size())
		{
			//找左右孩子中值最大的
			if(child+1<_a.size() && _a[child+1]>_a[child])
			{
				++child;
			}
			//将孩子和父母做比较
			if(_a[child]>_a[parent])
			{
				swap(_a[child],_a[parent]);
				parent = child;
				child = parent*2+1;
			}
			else
			{
				break;
			}
		}
	}
	//向上调整
	void _Adjustup(int i)
	{
		int child = i;
		int parent = (i-1)/2;
		while(child >= 0)
		{
			if(_a[child] > _a[parent])
			{
				swap(_a[child],_a[parent]);
				child = parent;
				parent = (parent-1)/2;
			}
			else
			{
				break;
			}
		}
	}
protected:
	vector<T> _a;
};

堆排的时间复杂度是:klg(k)+lg(k)*(N-k) =Nlg(k)


  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值