最小(大)堆

最小(大)堆是一颗完全二叉树,该树中的某个节点的值总是不大于(不小于)其左右子节点的值。可以通过下图理解,另外,为什么会使用数组来保存呢?因为利用完全二叉树的性质,我们可以通过数组来表示完全二叉树(数组下标与完全二叉树节点存在映射关系,比如父节点可以通过Math.floor((index-1)/2)来获取),从而简化了实现及开销,避免使用额外的指针来实现树结构。

java限制最小堆栈内存_二叉树

最小(大)堆性质

  • 树根节点的值是所有堆节点值中最小(大)值。
  • 树中每个节点的子树也都是最小(大)堆。

最小(大)堆作用

  • 最小(大)堆能保证堆顶元素为最小,而如果使用数组无法达到该效果。数组如果要访问最小值则需要遍历查找最小值,时间复杂度至少O(n)。而最小堆访问最小值时间复杂度为O(1),当然天底下没有免费的午餐,我们需要做额外的工作去维护最小(大)堆的结构,这也是需要复杂度花销的。

当然这也是最小(大)堆的优势,通过动态维护使得最小值的获取代价很小,实际上维护的时间复杂度为O(logN)。而数组则无法做到如此,如果数组想要维护顺序性则需要的复杂度至少为O(N)。这样来看最小(大)堆的优势就凸现出来了。

插入操作

为避免冗长累赘,我们这里只挑最小堆作为例子进行说明,最大堆的情况与最大堆相似。

现在分别插入4 7 2 5 6 1 0 3 8,使用一个数组来保存最小堆,为了帮助理解,数组下方提供一个逻辑上的完全二叉树的结构,两者结合着更容易理解其中机制。首先插入4,

java限制最小堆栈内存_堆排序_02

接着插入7,插入后检测到树符合最小堆要求,所以不改动。

java限制最小堆栈内存_java_03

继续插入2,插入后检测到不符合最小堆要求,父节点4大于右子节点2,

java限制最小堆栈内存_java_04

于是将它们对调。

java限制最小堆栈内存_java限制最小堆栈内存_05

继续插入5,插入后检测到不符合最小堆要求,父节点7大于左子节点5,

java限制最小堆栈内存_算法_06

于是将它们对调。

java限制最小堆栈内存_二叉树_07

继续插入6,插入后检测到树符合最小堆要求,所以不改动。

java限制最小堆栈内存_java_08

继续插入1,插入后检测到不符合最小堆要求,父节点4大于左子节点1,

java限制最小堆栈内存_java限制最小堆栈内存_09

于是将它们对调,

java限制最小堆栈内存_算法_10

对调后继续检测到不符合最小堆要求,父节点2大于右子节点1,

java限制最小堆栈内存_堆排序_11

继续将它们对调。

java限制最小堆栈内存_二叉树_12

继续插入0,插入后检测到不符合最小堆要求,父节点2大于右子节点0,

java限制最小堆栈内存_java限制最小堆栈内存_13

于是将它们对调,

java限制最小堆栈内存_java限制最小堆栈内存_14

对调后继续检测到不符合最小堆要求,父节点1大于右子节点0,

java限制最小堆栈内存_二叉树_15

继续将它们对调。

java限制最小堆栈内存_算法_16

继续插入3,插入后检测到不符合最小堆要求,父节点7大于左子节点3,

java限制最小堆栈内存_java_17

于是将它们对调,

java限制最小堆栈内存_java限制最小堆栈内存_18

对调后继续检测到不符合最小堆要求,父节点5大于左子节点3,

java限制最小堆栈内存_算法_19

继续将它们对调,然后符合最小堆要求,不必继续往上对调。

java限制最小堆栈内存_java限制最小堆栈内存_20

继续插入8,插入后检测到树符合最小堆要求,所以不改动。以上,完成所有元素的最小堆插入操作。

java限制最小堆栈内存_算法_21

删除操作

删除操作其实就是删除最小值,即最小堆树中的根节点。主要是将树中最后一个节点替换到被删除的根节点,然后自顶向下递归调整使之符合最小堆要求。

删除根节点0,然后将树的最后一个节点8补到根节点上。

java限制最小堆栈内存_java限制最小堆栈内存_22

比较根节点的左右子节点,

java限制最小堆栈内存_算法_23

因为右子节点1比较小,所以我们要进一步比较的是根节点8与右子节点1,

java限制最小堆栈内存_堆排序_24

1小于8,于是对调。

java限制最小堆栈内存_java_25

继续比较现在节点8的左右子节点,

java限制最小堆栈内存_堆排序_26

因为右子节点2比较小,所以我们要进一步比较的是根节点8与右子节点2,

java限制最小堆栈内存_java限制最小堆栈内存_27

2小于8,于是对调。

java限制最小堆栈内存_算法_28

至此,完成最小值删除操作。

完成的代码实例;

import java.util.Comparator;

public class MinHeap<T> {
	T[] arr;
	private static int capacity = Integer.MAX_VALUE;
	private Comparator<T> comparator;

	private int currentIndex = 0;

	public MinHeap(int capacity, Comparator<T> comparator) {
		if (capacity < 1) {
			throw new RuntimeException("error");
		}

		arr = (T[]) new Object[capacity];
		MinHeap.capacity = capacity;
		this.comparator = comparator;
	}

	public MinHeap(Comparator<T> comparator) {
		this(capacity, comparator);
	}

	public void add(T t) {
		if (currentIndex >= capacity) {
			// 超出容量更新
			update(t);
			return;
		}
		// 插入节点
		doAdd(t);
	}

	private void update(T add) {
		removeMin();
		doAdd(add);
	}

	private void doAdd(T t) {
		// 第一个节点
		if (currentIndex < 1) {
			arr[0] = t;
			currentIndex++;
			return;
		}
		// 插入到尾部,然后平衡
		arr[currentIndex] = t;
		addBalance(currentIndex);
		currentIndex++;
	}

	private void addBalance(int index) {
		// 获取父节点
		int parentIndex = getParentIndex(index);
		if (parentIndex < 0) {
			return;
		}
		// 比较父节点和和自己,小于就替换
		if (comparator.compare(arr[parentIndex], arr[index]) > 0) {
			replace(parentIndex, index);
		}
		// 再次炒父节点平衡
		addBalance(parentIndex);

	}

	public static void main(String[] args) {
		MinHeap<Integer> m = new MinHeap<Integer>(9, (o1, o2) -> {
			return o1 - o2;
		});
		m.add(4);
		m.add(7);
		m.add(2);
		m.add(5);
		m.add(6);
		m.add(1);
		m.add(0);
		m.add(3);
		m.add(8);
		m.removeMin();
	}

	private void replace(int index, int index1) {
		T t = arr[index];
		arr[index] = arr[index1];
		arr[index1] = t;
	}

	/**
	 * 删除首节点 把尾节点放到首节点,然后平衡
	 * 
	 * @return
	 */
	public T removeMin() {
		if (currentIndex < 1) {
			throw new RuntimeException();
		}
		T t = arr[0];
		// 把尾节点放到首节点
		arr[0] = arr[currentIndex - 1];
		// 平衡
		removeMinBalance(0);
		currentIndex--;
		System.err.println(Arrays.toString(arr));
		return t;
	}

	private void removeMinBalance(int index) {
		// 获取子节点
		int childIndex = getChildIndex(index);
		if (childIndex >= currentIndex) {
			return;
		}
		int minIndex = 0;
		// 找出最小的子节点
		if (childIndex + 1 < currentIndex) {
			if (comparator.compare(arr[childIndex], arr[childIndex + 1]) > 0) {
				minIndex = childIndex + 1;
			}
		} else {
			minIndex = childIndex;
		}
		// 最小的子节点和父节点比较,大于子节点就替换
		if (comparator.compare(arr[index], arr[minIndex]) > 0) {
			replace(minIndex, index);
		} else {
			return;
		}
		// 再次平衡
		removeMinBalance(minIndex);
	}

	private int getParentIndex(int index) {
		if (index == 0) {
			return -1;
		}
		return (index - 1) / 2;
	}

	private int getChildIndex(int index) {
		return index * 2 + 1;
	}

}