最小堆的实现

2019/10/25 算法和数据结构 共 3297 字,约 10 分钟

优先队列,堆排序,都用了最小堆

定义

  • 堆是一颗完全二叉树

  • 堆中某个结点的值总是不大于或不小于其子结点的值
    • 结点小于子结点,最小堆
    • 结点大于子结点,最大堆
  • 堆中每个结点的子树都是堆

  • 堆不唯一

建堆

使用筛选法建堆。

  1. siftDown()对当前结点调整,使其作为根结点的子树为为最小堆
  2. 从最后一个分支结点开始,自下向上执行(1)中的步骤

如下所示为siftDown()方法,从当前结点开始,比较它和它的子结点,如果它大于最小的子结点,就交换它和子结点的值,直至树的最后一层,这样就可以调整为当前结点作为根结点的二叉树为为最小堆。有关父子节点间的索引关系,参见二叉树的性质

void MinHeap::SiftDown(int position) {
	int parent = position;
	// 左子结点
	int child = 2 * parent + 1;
	// 当前值暂存
	int temp = this->heapArray[parent];

	while (child < this->size) {
		// 取左右子结点中较小的一个
		if (child < this->size - 1 && this->heapArray[child] > this->heapArray[child + 1]) {
			child = child + 1;
		}

		if (temp > this->heapArray[child]) {
			// 比暂存值小的结点都向上传播一层
			this->heapArray[parent] = this->heapArray[child];

			// 向下层继续
			parent = child;
			child = 2 * parent + 1;
		} else {
			break;
		}
	}

	// 最后再将暂存值放在合适位置
	this->heapArray[parent] = temp;
}

建堆时,从最后一个结点开始。最后一个分支结点的下标应为 $\lfloor \frac{n}{2} \rfloor-1$

void MinHeap::buildHeap() {
	for (int i = this->size / 2 - 1; i >= 0; i--) {
		this->SiftDown(i);
	}
}

插入

siftDown()类似。siftUp()从当前结点开始,比较它和它的父结点,如果它小于父结点,就交换它和父结点的值,直至第0层

void MinHeap::SiftUp(int position) {
	int child = position;
	// 父结点
	int parent = (child - 1) / 2;
	// 当前值暂存
	int temp = this->heapArray[child];

	while (child > 0) {

		if (temp < this->heapArray[parent]) {
			// 比暂存值大的结点都向下传播一层
			this->heapArray[child] = this->heapArray[parent];

			// 向上层继续
			child = parent;
			parent = (child - 1) / 2;
		} else {
			break;
		}
	}

	// 最后再将暂存值放在合适位置
	this->heapArray[child] = temp;
}

插入时,将插入的元素放在最后一个叶结点后面,再用 siftUp() 向上传播

bool MinHeap::push(int value) {
	if (this->size == this->maxSize) {
		return false;
	}
	this->heapArray[size++] = value;
	this->SiftUp(size - 1);
	return true;
}

删除

将最后一个叶结点放在根结点位置,并向下传播

bool MinHeap::pop() {
	if (size <= 0) {
		return false;
	}
	this->heapArray[0] = this->heapArray[--this->size];
	this->siftDown(0);
	return true;
}

算法复杂度分析

  • 建堆时间复杂度:$\sum_{i=1}^{log\ n}(i-1)\frac{n}{2^i}=O(n)$

  • 入队:$O(logn)$

  • 删除元素(已知位置):$O(logn)$

  • 出队: $O(1)$

完整实现

下面使用Java完整实现了一个带泛型并且可以自定义比较器的堆结构

public class Heap<E> {
  private final Object[] heap;
  private int size = 0;
  private final Comparator<? super E> comparator;

  public Heap(int size, Comparator<? super E> comparator) {
    heap = new Object[size];
    this.comparator = comparator;
  }

  public Heap(Collection<E> data, Comparator<? super E> comparator) {
    this.heap = data.toArray(new Object[0]);
    this.size = data.size();
    this.comparator = comparator;
    // 筛选法建堆, 由下往上调用siftDown
    // 最后一个分支结点下标为 n/2
    for (int i = size / 2 - 1; i >= 0; i--) {
      siftDown(i);
    }
  }

  public int size() {
    return size;
  }

  public void add(E val) {
    heap[size] = val;
    siftUp(size);
    size++;
  }

  public void pop() {
    size--;
    heap[0] = heap[size];
    siftDown(0);
  }

  public E peek() {
    return (E) heap[0];
  }

  /**
   * 从第i个结点向下调整
   */
  private void siftDown(int i) {
    int parent = i;
    // i结点暂存
    E record = (E) heap[parent];
    // 左子结点
    int child = 2 * parent + 1;
    while (child < size) {
      // 取左右子结点中较小的
      if (child + 1 < size && comparator.compare((E) heap[child + 1], (E) heap[child]) < 0) {
        child = child + 1;
      }
      // 当前结点大于子结点则向下传播
      if (comparator.compare(record, (E) heap[child]) > 0) {
        heap[parent] = heap[child];
      } else {
        break;
      }
      // 更新结点索引
      parent = child;
      child = 2 * parent + 1;
    }
    heap[parent] = record;
  }

  /**
   * 从第i个结点向上调整
   */
  private void siftUp(int i) {
    int child = i;
    // i结点暂存
    E record = (E) heap[child];
    // 父结点
    int parent = (child - 1) / 2;
    // 防止出现child=parent的死循环
    while (child > 0) {
      // 当前结点小于父结点则将当前结点向上传播
      if (comparator.compare(record, (E) heap[parent]) < 0) {
        // 给当前结点留出空位
        heap[child] = heap[parent];
      } else {
        break;
      }
      // 更新结点索引
      child = parent;
      parent = (child - 1) / 2;
    }
    heap[child] = record;
  }
}

REFERENCE

[1]堆与优先队列 - 北京大学[EB/OL]. Coursera.

文档信息

Search

    Table of Contents