Java-优先级队列(堆)

1. 二叉树的顺序存储

1.1 存储方式

使用数组保存二叉树的结构,方式即将二叉树用层序遍历方式放入数组中。一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。这种方式的主要用法就是堆的表示。
Java-优先级队列(堆)_第1张图片

1.2 下标关系

已知双亲(parent)的下标,则
左孩子(left)下标 = 2 * parent + 1;
右孩子(right)下标 = 2 * parent + 2;
已知孩子(不区分左右)(child)下标,则:
双亲(parent)下标 = (child - 1) / 2;

2. 堆(heap)

2.1 概念

  1. 堆逻辑上是一棵完全二叉树
  2. 堆物理上是保存在数组中
  3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
  4. 反之,则是小堆,或者小根堆,或者最小堆
  5. 堆的基本作用是,快速找集合中的最值
    Java-优先级队列(堆)_第2张图片

2.2 建堆

给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
代码(以大堆为例):

public static void createHeap(int[] array, int size) { 
	for (int i = (size - 1) / 2; i >= 0; i--) { 
	shiftDown(array, size, i); 
} 
}
// 建堆前 
int[] array = { 1,5,3,8,7,6 }; 
// 建堆后 
int[] array = { 8,7,6,5,1,3 };

时间复杂度分析:
大概估算,可以认为是在循环中执行向下调整,为 O(n * log(n)),实际上是 O(n)
建堆过程时间复杂度怎么来的?

3. 堆的应用-优先级队列

3.1 有关概念

很多场景中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

3.1 内部原理

优先级队列的实现方式有很多,但最常见的是使用堆来构建。

3.2 操作-入队列

过程(以大堆为例):

  1. 首先按尾插方式放入数组
  2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
  3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤
  4. 直到根结点
    代码:
public static void shiftUp(int[] array, int index) { 
	while (index > 0) { 
		int parent = (index - 1) / 2; 
		if (array[parent] >= array[index]) {
			 break; 
		 }
	 int t = array[parent]; 
	 array[parent] = array[index]; 
	 array[index] = t; 
	 index = parent; 
 	} 
 }

3.3 操作-出队列

为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆
代码:

public class MyPriorityQueue { 
	// 不考虑扩容部分的代码 
	private int[] array = new int[100]; 
	private int size = 0; 
	public void offer(int e) { 
		array[size++] = e; 
		shiftUp(array, size - 1); 
	}
	public int poll() { 
		int oldValue = array[0]; 
		array[0] = array[--size]; 
		shiftDown(array, size, 0); 
		return oldValue; 
	}
	public int peek() { 
		return array[0]; 
	} 
}

你可能感兴趣的:(数据结构,堆,优先级队列)