堆的实现(数据结构)

 一、前言

        堆是什么?堆也叫做优先队列,一些按照重要性或优先级来组织的对象称为优先队列。

        为什么需要堆?在现实生活中,存在许多需要从一群人、一些任务或一些对象中找出“下一位最重要”目标的情况。例如:在平时处理事情的时候我们总会先把“下一个最重要的”的事情取出来先处理。在处理的过程中,可能还会有其他的事情加进来。因此在事情处理完的事情,我们又要重新找出“下一个最重要”的事情进行处理。

        如何去建立一个堆?通常来说,我们容易想到以下的方法:

        对所有元素进行一次排序然后取出最大值。但这种方法不是很好。它多做了很多无用功。因为其实我可能只要取一个最大值就OK了,但是却画蛇添足地帮我把所有元素都排好序了。很浪费时间。排序的时间复杂度至少为O(nlogn),插入和删除操作的时间复杂度为O(n)。理论上我们所要实现的优先队列的时间复杂度是可以比这个更优的。

二、堆的属性

2.1 定义

  • 堆是一棵满二叉树,所以往往用数组来实现这棵二叉树
  • 堆中的数据是局部有序的。也就是节点储存的值和它的子节点之间存在某种关系

2.2 结构图

        

2.3 堆类型

  • 最大值堆:任意一个节点的值都大于它的子节点的值;这样子根节点存储的就是这组数据的最大值。(以上图为例,A的值必须要大于B和C的值,B的值必须要大于D和E的值,但是C和D之间以及C和E之间没有必然的大小关系。C可以比D大也可以比D小)
  • 最小值堆:任意一个节点的值都小于它的子节点的值;这样子根节点存储的就是这组数据的最小值。

        注意点:堆的兄弟节点之间没有必然的联系

        我们以最大值堆为例,来看一下它的结构示例图:

        

        接下来我们来实现一个最大值堆。

三、详细设计

3.1 构建一个堆类

        这个类需要包含以下的属性和方法

  • 属性:指向堆数组的指针、堆的最大元素个数、当前堆的元素个数
  • 方法:
    • 构造函数(设置初始值)
    • 返回当前元素个数
    • 判断当前节点是否是叶节点
    • 返回当前节点的左孩子节点位置
    • 返回当前节点的右孩子节点位置
    • 返回当前节点的父节点位置
    • 建堆函数
    • 下拉函数
    • 插入函数
    • 移除函数

3.2 算法思想

3.2.1 判断当前函数是否是叶节点

        由于我们的树是存放在数组中的,我们可以通过下标来判断。如果下标的值大于等于n/2并且小于n,就说明它是一个叶节点。

3.2.2 返回左右孩子及父节点的位置

        我们可以通过当前节点的下标来计算,完全二叉树在数组中的储存如下:

P1

LC1

RC1

        根据二叉树的结构和数组下标的信息,我们可以推断出以下结论:        

  • 左孩子节点:2*pos+1;
  • 右孩子节点:2*pos+2;
  • 父节点:(pos-1)/2;

3.2.3 建堆函数

        有两种方法,一种是利用插入函数,逐个插入数据。另一种是对已经存有数据的数组进行堆排序。我们这里采用的是第二种方法。基本思路:依次从树的倒数第二层往上遍历节点。如果当前节点的值小于它的某一个叶节点,我们调用下拉函数进行下拉操作。而由于叶节点不可能再往下走,所以我们直接从倒数第二层开始遍历即可。倒数第二层的位置:n/2-1。

3.2.4 下拉函数

        判断当前节点和它两个子节点的大小关系,如果当前节点小于它的子节点。那么就将该节点往下拉。与较大的子节点交换位置。

3.2.5 插入函数

        首先将要插入的数据加到堆的一个叶节点中,也就是当前数组的尾部。然后判断该节点和其父节点的大小关系,如果该节点大于其父节点,就把其上拉,和父节点交换位置,重复该过程直到该节点到了正确的位置。

3.2.6 移除函数

        先把根节点和最后一个叶节点交换位置,把堆的元素大小减1。对改变后的根节点进行下拉操作,直到正确的位置。最后再返回被替换的那个叶节点的值。

四、源码分析

4.1 C++版代码+运行结果

#include<iostream>
using namespace std;

template<class E>
void swap(E *Heap,int p,int j){
	E zj=Heap[p];
	Heap[p]=Heap[j];
	Heap[j]=zj;
} 

//Heap class 
template<class E>
class heap{
	private:
		E *Heap;//指向Heap数组的指针 
		int maxsize;//堆的最大空间
		int n;//堆的当前元素个数
		 
		//下拉函数
		void siftdown(int pos){
			while(!isLeaf(pos)){//如果已经拉到叶节点了就停止 
				int j=leftchild(pos);
				int rc=rightchild(pos);//获取左右孩子节点的pos 
				
				if((rc<n)&&(Heap[rc]>Heap[j]))
				  j=rc;//把j指向较大的子节点的位置
				if(Heap[pos]>Heap[j]) return; 
				swap(Heap,pos,j);//如果根节点的值小于较大的子节点的值,就交换位置 
				pos=j;//继续往原本值较大的节点走 
			}
		}
	public:
		//创建构造函数 
	    heap(E *h,int num,int max){
	    	Heap=h; n=num; maxsize=max; buildHeap();
		} 
		//返回当前堆的大小 
		int size() const{ return n;}
		//判断当前节点是否是叶节点
		bool isLeaf(int pos) const{ return (pos >= n/2) && (pos<n);}
		//返回左孩子节点的位置
		int leftchild(int pos) const{ return 2*pos + 1;}
		//返回右孩子节点的位置
		int rightchild(int pos) const{ return 2*pos + 2;}
		//返回父母节点的位置
		int parent(int pos) const{ return (pos-1)/2;}
		//建堆函数
		void buildHeap() {
			for(int i=n/2-1;i>=0;i--) siftdown(i);//从倒数第二层开始往上遍历,依次调用下拉函数,建立最大值堆 
		} 
		//构建插入函数
		void insert(const E it){
			if(n>=maxsize){
				cout<<"Heap is full!"<<endl;
				return;
			}
			int curr = n++;//开辟一个位置 
			Heap[curr]=it;//在数组的最后加入数据
			//加入之后开始将这个值往上拉
			while((curr!=0)&&(Heap[curr]>Heap[parent(curr)])){
				swap(Heap,curr,parent(curr));
				curr = parent(curr);//往上走 
			}
		} 
		//移除首个元素,也就是最大值
		E removefirst(){
			if(n<=0){
				cout<<"Heap is empty!"<<endl;
				return 0;
			}
			swap(Heap,0,--n);//把最后一个元素和第一个元素换位置,将堆的空间减1 
			if(n!=0) siftdown(0);//如果堆元素不为0,就对该元素进行下拉操作直到合适的位置
			return Heap[n];//返回最大值 
		} 
};

int main(){
	int n,maxsize=256;
	cin>>n;
	int h[n];
	for(int i=0;i<n;i++) cin>>h[i];
	
	heap<int>a(h,n,maxsize);//数据类型属于模板的对象实例化要带上具体的数据类型
	
	//检测建堆函数 
	for(int i=0;i<n;i++){
		cout<<h[i]<<" ";
	} 
	cout<<endl;
	//检测出堆函数
	int max=a.removefirst();
	cout<<max<<endl;
	for(int i=0;i<n;i++){
		cout<<h[i]<<" ";
	} 
	cout<<endl;
	//检测插入函数
	a.insert(15);
	for(int i=0;i<n;i++){
		cout<<h[i]<<" ";
	} 
	cout<<endl;
	return 0;
}
/*
8
5 3 21 6 9 4 5 12
*/

4.2 Java版代码+运行结果

package version2;

public class Node {
	public int key;//关键字,也就是权值
	public String value;//值
	
	public Node(int key,String value){
		this.key=key;
		this.value=value;
	}
}
package version2;
import java.util.ArrayList;

public class Heap {
	public ArrayList<Node>nodeList;//存储数据的数组,可以说就是堆
	private int length;//堆的大小
	
	public Heap(){
		
	}
	
	public Heap(ArrayList<Node>nodeList){
		this.nodeList=nodeList;
		this.length=nodeList.size();
	}
	
	//交换函数,交换两个节点的位置
	public void swap(Node nodeA,Node nodeB){
		Node nodetemp=new Node(nodeA.key,nodeA.value);
		nodeA.key=nodeB.key;
		nodeA.value=nodeB.value;
		nodeB.key=nodetemp.key;
		nodeB.value=nodetemp.value;
	}
	
	public int length(){
		return length;
	}
	
	//判断是不是叶节点
	public boolean isLeaf(int pos){
		return (pos >= length/2) && (pos<length);
	}
	
	//返回左孩子节点的地址
	public int leftchild(int pos){
		return 2*pos+1;
	}
	
	//返回右孩子节点的地址
	public int rightchild(int pos){
		return 2*pos+2;
	}
	
	//返回父节点的地址
	public int parent(int pos){
		return (pos-1)/2;
	}
	
	//建堆函数
	public void buildHeap(){
		for(int i=length/2-1;i>=0;i--) shiftDown(i);//从倒数第二层开始往上遍历,依次调用下拉函数,建立最大值堆 
	}
	
	//下拉函数
	public void shiftDown(int pos){
		while(!isLeaf(pos)){//如果已经拉到叶节点了就停止 
			int j=leftchild(pos);
			int rc=rightchild(pos);//获取左右孩子节点的pos 
			
			if((rc<length)&&(nodeList.get(rc).key>nodeList.get(j).key))
			  j=rc;//把j指向较大的子节点的位置
			if(nodeList.get(pos).key>nodeList.get(j).key) return; 
			swap(nodeList.get(pos),nodeList.get(j));//如果根节点的值小于较大的子节点的值,就交换位置 
			pos=j;//继续往原本值较大的节点走 
		}
	}
	
	//插入新节点
	public void insert(Node node){
		
		nodeList.add(node);
		int curr=nodeList.size()-1;//新插入数据的位置
		//加入之后开始将这个值往上拉
		while((curr!=0)&&(nodeList.get(curr).key>nodeList.get(parent(curr)).key)){
			swap(nodeList.get(curr),nodeList.get(parent(curr)));
			curr = parent(curr);//往上走 
		}
		length++;//数量加1
	} 
	
	//取最值函数
	public Node removeFirst(){
		if(length<=0){
			System.out.println("Heap is empty!");
			return null;
		}
		swap(nodeList.get(0),nodeList.get(--length));//把最后一个元素和第一个元素换位置,将堆的空间减1
		if(length!=0) shiftDown(0);//如果堆元素不为0,就对该元素进行下拉操作直到合适的位置
		return nodeList.get(length);//返回最大值 
	}
	
	public static void main(String[] args) {
		ArrayList<Node>nodeList=new ArrayList();
		nodeList.add(new Node(1,"A"));
		nodeList.add(new Node(2,"B"));
		nodeList.add(new Node(3,"C"));
		nodeList.add(new Node(4,"D"));
		
		Heap heap=new Heap(nodeList);
		
		System.out.println("当前的数组结构");
		for(int i=0;i<heap.length();i++){
			System.out.println(heap.nodeList.get(i).key+"-"+heap.nodeList.get(i).value);
		}
		
		heap.buildHeap();
		System.out.println("当前的堆结构");
		for(int i=0;i<heap.length();i++){
			System.out.println(heap.nodeList.get(i).key+"-"+heap.nodeList.get(i).value);
		}
		Node result=heap.removeFirst();
		System.out.println("最大值:"+result.key+result.value);
		result=heap.removeFirst();
		System.out.println("最大值:"+result.key+result.value);
	}

}

至此我们已经成功构建了最大值堆。最小值堆只需要做一些小修改即可。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值