堆
概念
- 堆逻辑上是一棵完全二叉树
- 堆物理上是保存在数组中
- 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
- 反之则是小堆,或者小根堆,或者最小堆
向下调整
前提:左右子树必须是一个堆才能调整
过程(以小堆为例)
1.index如果已经是叶子节点,则整个调整过程结束
①判断index位置有没有孩子
②因为堆是完全二叉树,没有左孩子就一定没有右孩子,所以判断是否有左孩子
③因为堆的存储结构是数组,所以判断是否有左孩子即判断左孩子下标是否越界,即 left >= size 越界
2.确定 left 或 right,谁是 index 的最小孩子 min
①如果右孩子不存在,则 min = left
② 否则,比较 array[left] 和 array[right] 值得大小,选择小的为 min
3.比较 array[index] 的值 和 array[min] 的值,如果 array[index] <= array[min],则满足堆的性质,调整结束
4.否则,交换 array[index] 和 array[min] 的值
5.然后因为 min 位置的堆的性质可能被破坏,所以把 min 视作 index,向下重复以上过程
import java.util.Arrays;
//以小堆为例 向下调整
public class ShiftDown {
public static void shiftDown(int[] array,int size,int index){
int left=2*index+1;
while(left<size){
int mid=left;
int right=2*index+2;
if(right<size){
if(array[right]<array[left]){
mid=right;
}
}
if(array[index]<=array[mid]){
break;
}
int t=array[index];
array[index]=array[mid];
array[mid]=t;
index=mid;
left=2*index+1;
}
}
public static void createHeap(int[] array,int size){
for(int i=(size-1-1)/2;i>=0;i--){
shiftDown(array,size,i);
}
}
public static void main(String[] args) {
int[] array = { 27,15,19,18,28,34,65,49,25,37 };
createHeap(array,array.length);
System.out.println(Arrays.toString(array));
}
}
import java.util.Arrays;
//以大堆为例,向下调整
public class shiftDownBig {
public static void shiftDown(int[] array,int size,int index){
int parent=index;
int childer=2*parent+1;
while(childer<size){
if(childer+1<size&&array[childer+1]>array[childer]){
childer=childer+1;
}
if(array[childer]>array[parent]){
int temp=array[childer];
array[childer]=array[parent];
array[parent]=temp;
}else{
break;
}
parent=childer;
childer=2*parent+1;
}
}
public static void createHeap(int[] array,int size){
for(int i=(size-1-1)/2;i>=0;i--){
shiftDown(array,size,i);
}
}
public static void main(String[] args) {
int[] array = { 27,15,19,18,28,34,65,49,25,37 };
createHeap(array,array.length);
System.out.println(Arrays.toString(array));
}
}
优先队列
概念
在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。
这种数据结构就是优先级队列(Priority Queuue)
操作-入队列
过程(以大堆为例)
- 首先按尾插方式放入数组
- 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
- 否则,交换其和双亲位置的值,重新进行 2、3 步骤
- 直到根结点
public class MyPriorityQueue {
// array 看起来是一个数组, 其实应该是一个堆的结构
private int[] array = new int[100];
private int size = 0;
public void offer(int x) {
array[size] = x;
size++;
// 把新加入的元素进行向上调整
shiftUp(array, size - 1);
}
// 此时发现 size 这个参数没用上. 仔细想想, 好像也确实不需要.
// 判定调整完了, 只需要和 0 比较即可. 不需要知道整个堆有多大.
private static void shiftUp(int[] array, int index) {
int child = index;
int parent = (child - 1) / 2;
while (child > 0) {
if (array[parent] < array[child]) {
// 当前不符合大堆要求.
int tmp = array[parent];
array[parent] = array[child];
array[child] = tmp;
} else {
// 发现当前 父节点 比子节点大. 这个时候说明整个数组已经符合堆的结构了
break;
}
child = parent;
parent = (child - 1) / 2;
}
}
public int poll() {
// 下标为 0 的元素就是队首元素. 删掉的同时, 我们也希望剩下的结构仍然是一个堆
int oldValue = array[0];
array[0] = array[size - 1];
size--;
shiftDown(array, size, 0);
return oldValue;
}
private static void shiftDown(int[] array, int size, int index) {
int parent = index;
int child = 2 * parent + 1;
// 这个条件的含义是看看 parent 有没有子节点
while (child < size) {
// 把左右子树中较大的节点找到
if (child + 1 < size && array[child + 1] > array[child]) {
child = child + 1;
}
// 上述条件结束后, child 肯定对应左右子树中比较大的元素.
// 再拿当前的这个 child 和 parent 位置的元素比较一下
if (array[child] > array[parent]) {
// 交换父子节点的位置
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
} else {
// 当前这个位置开始, 已经符合堆的要求了, 不需要继续调整
break;
}
parent = child;
child = 2 * parent + 1;
}
}
public int peek() {
return array[0];
}
public boolean isEmpty() {
return size == 0;
}
public static void main(String[] args) {
MyPriorityQueue queue = new MyPriorityQueue();
queue.offer(9);
queue.offer(5);
queue.offer(2);
queue.offer(7);
queue.offer(3);
queue.offer(6);
queue.offer(8);
while (!queue.isEmpty()) {
int cur = queue.poll();
System.out.println(cur);
}
}
}