Java数据结构与算法(树)——堆(完全二叉树)


注意:这里的堆和 Java,C++ 等编程语言在内存中的“堆”是不一样的,这里的堆是一种树结构。

一、堆的定义

(1)它是完全二叉树

完全二叉树——对于一棵具有 n 个节点的二叉树按层序编号,如果编号为 i 的节点与同样深度的满二叉树中编号为 i 的节点在二叉树中的位置完全相同,则这棵树称为完全二叉树。

(2)通常用数组来实现

用数组实现二叉树。假设某个节点的索引为 index,则

  • 该节点的左子节点的索引是 ( 2 × i n d e x + 1 ) (2 \times index + 1)
  • 该节点的右子节点的索引是 ( 2 × i n d e x + 2 ) (2 \times index + 2)
  • 该节点的父节点的索引是 ( i n d e x 1 ) ÷ 2 (index - 1) \div 2

(3)堆中每个节点的关键字都大于其子节点的关键字

注意堆与二叉搜索树的区别:

  • 对于二叉搜索树,每个节点的关键字都大于其子节点的关键字,并且,每个节点的左子节点的关键字都小于右子节点的关键字,通过中序遍历就可实现升序遍历;
  • 对于堆,只要求每个节点的关键字都大于其子节点的关键字,而每个节点的左、右子节点没有确定的大小关系。只有沿着从根节点到叶节点的每一条路径上是降序排列的。因此,相比于二叉搜索树,堆是弱序的。

二、堆的主要操作

优先级队列提供了删除最大(或最小)数据项和插入数据项的方法,可以用有序数组来实现。这种实现方式删除操作的时间复杂度为 O(1),但是插入操作的时间复杂度还是O(N),因为每次插入平均需要移动一半的数据项,来保证插入后的数组依然有序。

用堆实现优先级队列时,其插入和删除的时间复杂度都为 O(logN),删除的时间变慢,但是提高了插入的时间。所以当有很多插入操作时,可以选择用堆来实现优先级队列。

1、遍历和查找

由于堆是弱序的,所以在遍历和查找的过程中,没有足够的信息来确定选择两个子节点的哪一个来走向下一层,因此,堆是不支持查找和遍历的。堆的主要作用就是快速删除最大(最小)节点和快速插入数据。

2、删除

堆的删除操作是指删除关键字最大的节点,即根节点。根节点移除后,还需将这个空的节点填上。

两种方法:

(1)所有数据项向前移动一个单元,但很费时;
(2)把最后一个节点放到根节点的位置,再向下筛选,使得该节点位于一个大于它的节点之下,小于它的节点之上(向下筛选时,将目标节点和其子节点比较,哪个子节点大就和哪个交换位置)。过程图示如下:
在这里插入图片描述

3、插入

插入节点时,先将节点插入数组最后一个数据的后一个位置,然后向上筛选,将该节点放在合适的位置(向上筛选,和其父节点比较,放到比其父节点小的位置即可)。

三、代码实现

class Node{
	private int data;
	
	public Node(int data){ this.data = data; }

	public int getData() { return data; }

	public void setData(int data) { this.data = data; }
}

public class Heap {
	private Node[] heapArray;
	private int maxSize;// 数组的大小
	private int num;// 数组中数据项的个数
	
	public Heap(int size){
		maxSize = size;
		heapArray = new Node[maxSize];
		num = 0;
	}
	
	public boolean isEmpty(){
		return num == 0;
	}
	
	public boolean isFull(){
		return num == maxSize;
	}
	
	// 移除:移除关键字最大的节点,即根节点
	public Node remove(){
		Node root = heapArray[0];
		heapArray[0] = heapArray[--num];
		downward(0);
		return root;
	}
	
	// 向下调整:参数index为要调整节点的索引
	public void downward(int index){
		Node top = heapArray[index];
		int indexOfBigChild;// 较大孩子的索引
		while(index < num / 2){
			int iLeft = 2 * index + 1;// 左孩子的索引
			int iRight = 2 * index + 2;// 右孩子的索引
			if(iRight < num && // 右孩子是否存在
				heapArray[iLeft].getData() < heapArray[iRight].getData()){
				indexOfBigChild = iRight;
			}else{
				indexOfBigChild = iLeft;
			}
			// 目标节点的数据大于子节点的数据时,调整完成,跳出循环
			if(top.getData() > heapArray[indexOfBigChild].getData()){
				break;
			}
			// 较大的子节点向上移
			heapArray[index] = heapArray[indexOfBigChild];
			index = indexOfBigChild;
		}
		heapArray[index] = top;
	}
	
	// 插入
	public void insert(int data){
		if(isFull()){
			heapArray = Arrays.copyOf(heapArray, 2 * maxSize);	;
		}
		Node node = new Node(data);
		heapArray[num] = node;
		upward(num);
		num++;
	}
	
	// 向上调整:参数index为要调整节点的索引
	public void upward(int index){
		int indexOfParent = (index - 1) / 2;
		Node bottom = heapArray[index];
		while(index > 0 && 
				// 若新节点的数据比其父节点的数据大,则需要调整
				bottom.getData() > heapArray[indexOfParent].getData()){
			heapArray[index] = heapArray[indexOfParent];// 父节点向下调整
			index = indexOfParent;
			indexOfParent = (index - 1) / 2;
		}
		heapArray[index] = bottom;
	}
	
	// 打印:用数组存储,打印的顺序就是层序遍历
	public void display(){
		if(isEmpty()){
			System.out.println("Heap is empty!");
			return;
		}else{
			for (int i = 0; i < num; i++) {
				System.out.print(heapArray[i].getData() + " ");
			}
			System.out.println();
		}
	}
	
	public static void main(String[] args) {
		Heap heap = new Heap(12);
		heap.insert(99);
		heap.insert(80);
		heap.insert(57);
		heap.insert(60);
		heap.insert(70);
		heap.insert(33);
		heap.insert(6);
		heap.insert(40);
		heap.insert(20);
		heap.insert(60);
		heap.insert(30);
		heap.insert(31);
		heap.display();
		
		heap.remove();
		heap.display();
	}
}

测试程序中的数据就是删除算法中图示的数据,测试结果如下:

99 80 57 60 70 33 6 40 20 60 30 31 
80 70 57 60 60 33 6 40 20 31 30 
发布了56 篇原创文章 · 获赞 0 · 访问量 946

猜你喜欢

转载自blog.csdn.net/weixin_45594025/article/details/104569374