欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

堆的实现(数据结构)

程序员文章站 2024-02-11 20:43:40
...

 一、问题描述

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

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

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

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

因此就出现了堆。

定义:

1.它是一棵满二叉树,所以往往用数组来实现这棵二叉树。

2.堆中的数据是局部有序的。也就是节点储存的值和它的子节点之间存在某种关系。

结构图:

堆的实现(数据结构)

堆类型:

最大值堆的性质:任意一个节点的值都大于它的子节点的值;这样子根节点存储的就是这组数据的最大值。

(以上图为例,A的值必须要大于B和C的值,B的值必须要大于D和E的值,但是C和D之间以及C和E之间没有必然的大小关系。C可以比D大也可以比D小)

最小值堆的性质:任意一个节点的值都小于它的子节点的值;这样子根节点存储的就是这组数据的最小值。

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

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

堆的实现(数据结构)

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

二、详细设计

(一)、首先我们需要构建一个堆类。

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

A、属性:指向堆数组的指针、堆的最大元素个数、当前堆的元素个数

B、方法:

构造函数(设置初始值)

返回当前元素个数

判断当前节点是否是叶节点

返回当前节点的左孩子节点位置

返回当前节点的右孩子节点位置

返回当前节点的父节点位置

建堆函数

下拉函数

插入函数

移除函数

 

(二)、算法思想

1、判断当前函数是否是叶节点

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

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

我们可以通过当前节点的下标来计算。

完全二叉树在数组中的储存如下:

P1

LC1

RC1

 

 

 

 

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

左孩子节点:2*pos+1;

右孩子节点:2*pos+2;

父节点:(pos-1)/2;

3、建堆函数

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

4、下拉函数

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

5、插入函数

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

6、移除函数

先把根节点和最后一个叶节点交换位置,把堆的元素大小减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
*/

堆的实现(数据结构)

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);
	}

}

堆的实现(数据结构)

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