数据结构-最大堆

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/love905661433/article/details/82989404

  • 二叉堆(Binary Heap)
    • 二叉堆是一颗完全二叉树
    • 堆中某个节点的值总是大于等于(或小于等于)其子节点, 对应的就是最大堆和最小堆

可以用数组存储二叉堆, 数组下标以1开始,可以如下展示 :

1538727825731

数组从0开始, 展示如下 :

1539080268780

操作

添加元素

  • 在数组的最后一个位置添加一个新元素

  • 新的元素进行上浮(Sift Up), 上浮操作如下图:

    1539088008107

取出元素

堆每次只能取出最大的元素, 具体取出元素的步骤如下 :

  • 将堆的根节点与最后一个元素交换位置

  • 删除最后的一个元素, 此时最后一个元素位于根节点

  • 如果左右子节点不小于现在的根节点, 则将现有根节点和左右子节点中较大的那个交换位置

  • 重复上一步, 直到不再需要最后的那个节点找到它应该存放的位置, 示意图如下:

    1539090084771

替换元素

取出堆中的最大元素, 再放入新元素, 步骤如下:

  • 直接将堆顶元素替换成新元素
  • 对新元素执行Sift Down操作

将任意数组整理成堆(heapify)

  • 首先将数组当成一个完全二叉树

  • 找到最后一个非叶子节点, 方法是根据最后一个元素计算其父节点

  • 从最后一个非叶子节点之前的节点都执行Sift Down操作
    1539092074313

完整最大堆代码实现如下 :

package heap;

import java.util.ArrayList ;
import java.util.Collection ;

/**
 * 使用动态数组实现最大堆, 这里jdk中ArrayList实现
 * 数组下标以0开始时,父节点parent(i)=i/2, left child (i)= 2*i, right child = 2*i +1
 * 数组下标以1开始时,父节点parent(i)=(i-1)/2, left child (i)= 2*i + 1, right child = 2*i +2
 * 这里使用下标为0开始
 *  
 * @author 七夜雪
 *
 */
public class MaxHeap<E extends Comparable<E>> {
	// 使用动态数组保存堆中元素
	private ArrayList<E> data;
	
	public MaxHeap(int capacity){
		data = new ArrayList<>(capacity);
	}
	
	public MaxHeap(){
		data = new ArrayList<>();
	}
	
	/**
	 * Heapify方式将集合中的元素添加到最大堆中
	 * @param collection
	 */
	public MaxHeap(Collection<E> collection){
		data = new ArrayList<>(collection);
		for(int i = parent(data.size() -1); i >= 0; i--){
			siftDown(i);
		}
	}
	
	/**
	 * 返回堆中元素个数
	 * @return
	 */
	public int getSize(){
		return data.size();
	}
	
	/**
	 * 返回堆是否为空
	 * @return
	 */
	public boolean isEmpty(){
		return data.isEmpty();
	}
	
	/**
	 * 返回下标为index的节点的父节点下标值
	 * @param index
	 * @return
	 */
	private int parent(int index){
		if (index == 0) {
			throw new IllegalArgumentException("下标为0的节点不存在父节点");
		}
		return (index - 1) / 2; 
	}
	
	/**
	 * 返回下标为index的节点的左孩子节点
	 * @param index
	 * @return
	 */
	private int leftChild(int index){
		return 2 * index +1;
	}
	
	/**
	 * 返回下标为index的节点的右孩子节点
	 * @param index
	 * @return
	 */
	private int rightChild(int index){
		return 2 * index +2;
	}
	
	/**
	 * 向最大堆中添加元素
	 * @param e
	 */
	public void add(E e)	{
		data.add(e);
		siftUp(data.size() - 1);
	}
	
	/**
	 * 节点上浮
	 * @param index
	 */
	private void siftUp(int index){
		// 不是根节点, 且当前节点大于父节点时, 和父节点交换位置
		while (index != 0 && data.get(parent(index)).compareTo(data.get(index)) < 0) {
			swap(index, parent(index));
			index = parent(index);
		}
	}
	
	/**
	 * 查询最大堆中的最大元素
	 * @return
	 */
	public E findMax(){
		if (data.isEmpty()) {
			throw new IllegalArgumentException("当前堆为空, 无最大值");
		}
		return data.get(0);
	}

	/**
	 * 取出最大堆的元素
	 * @return
	 */
	public E extractMax(){
		// 获取最大值
		E ret = findMax();
		// 将最大值和最后一个叶子节点交换位置, 即用最后一个叶子节点替换了根节点
		swap(0, data.size() -1);
		// 删除最后一个叶子节点, 即删除了最大值
		data.remove(data.size() - 1);
		// 现在的根节点下沉
		siftDown(0);
		return ret;
	}
	
	/**
	 * 节点下沉
	 * @param index
	 */
	private void siftDown(int index){
		// 不是根节点, 且当前节点小于父节点时, 和父节点交换位置
		while (leftChild(index) < data.size()) {
			// 用于和index节点进行比较的节点, 先默认为左孩子节点
			int swapIndex = leftChild(index);
			/*
			 * swapIndex + 1 < data.size表示右孩子也不为空
			 * 右孩子节点值大于左孩子时, 使用右子树与当前节点进行比较
			 */
			if (swapIndex + 1 < data.size() && data.get(swapIndex + 1).compareTo(data.get(swapIndex)) > 0) {
				// 右孩子下标进行替换, 这里也可以直接写成swapIndex ++;
				swapIndex = rightChild(index);
			}
			
			if (data.get(swapIndex).compareTo(data.get(index)) > 0) {
				swap(index, swapIndex);
				index = swapIndex;
			} else {
				return;
			}
			
		}
	}
	
	/**
	 * 对data中的元素交换位置
	 * @param first
	 * @param second
	 */
	private void swap(int first, int second){
		E temp = data.get(first);
		data.set(first, data.get(second));
		data.set(second, temp);
	}
	
	/**
	 * 替换根节点
	 * @param e
	 * @return
	 */
	public E replace(E e){
		E ret = findMax();
		// 替换根节点
		data.set(0, e);
		// 根节点下沉
		siftDown(0);
		return ret;
	}
		
}


慕课网<玩转数据结构>学习笔记

猜你喜欢

转载自blog.csdn.net/love905661433/article/details/82989404
今日推荐