DS13-使用最大堆实现的优先队列

优先队列是一种特殊的队列,在实际生活中应用很广,比如说医院急救,那肯定不能按照先来后到的方式进行排队;因为病人的病情有轻有重,所以要根据病人的病情进行排队,这就是优先队列,优先队列不再按照队列中先进先出的原则进行操作,而是按照优先级进行排队

使用传统的链表当然也能实现优先队列,每次入队都加在队首就可以了,入队操作的时间复杂度为O(1),但是出队的时候,先要找出最大的元素,然后再出队这个元素,因此需要把整个链表至少遍历一遍,由此带来的时间复杂度就是O(n)

使用最大堆实现优先队列是一种时间复杂度更低的算法,这里只考虑二叉堆,对于最大二叉堆,其本质上可以理解为一颗完全二叉树,对于这棵完全二叉树,存在一个很特殊的地方:任意节点都同时比其左孩子和右孩子大,这样实现的二叉在入队和出队时的时间复杂度都是O(logN)

存储最大二叉堆也有一个比较好的办法,就是把最大二叉堆按从上到下从左到右把每个元素都存进一个数组中,这样对于这个最大堆,假设其在数组中的索引为K,则其左孩子对应的索引为:2K+1,右孩子对应的索引为:2K+2,而其父亲节点的索引为:(K-1)/2,这样的特性保证了可以对任意节点获取其父亲节点或孩子节点的时间复杂度都为O(1)

对于最大堆的插入操作,可以直接插入到数组的最后,但插入的元素可能会破坏最大堆的性质:孩子必须比它的父母小,所以可以设计一个siftUp方法,用于维护插入操作时破坏的最大堆的性质,具体就是一步步比较插入的节点和其父亲节点(直到根节点)的大小,如果更大就换一下两个节点在数组中的位置,如果更小则操作结束,完成插入

对于最大堆的删除操作,则肯定是删除根节点,因为根节点肯定就是最大堆中最大的元素,删掉根节点之后可以用最后一个元素替换根节点,此时依然会破坏最大堆的性质,但是维护一下就可以了,可以设计一个siftDown方法,用于维护删除操作时破坏的最大堆的性质,具体就是一步步比较这个节点和其左右孩子的大小,让左右孩子和这个节点中最大的那个成为父亲节点,直到这个节点比他的孩子都大或者这个节点已经是叶子节点,完成删除

实现的时候还是先定义一个Queue接口类,定义一些优先队列的基本操作,然后把自己实现过的Array类导入,再定义一个MaxHeap类实现最大堆的相关操作,最后定义一个PriorityQueue类,实现Queue接口,复用MaxHeap中的一些方法实现优先队列

具体代码如下:
Queue接口类:

public interface Queue<E> {
	public void enqueue(E e);
	public E dequeue();
	public E getFront();
	public int getSize();
	public boolean isEmpty();
}

Array数组类:

/**
 * 数组类:最大堆的底层实现方式
 * @author ChenZhuJi
 *
 * @param <E>
 */
@SuppressWarnings("unchecked")
public class Array<E> {
	private E[] data;
	private int size;
	
	/**
	 * 空参构造,创建数组默认容量为10
	 */
	public Array() {
		this.data = (E[])new Object[10];
	}
	
	/**
	 * 有参构造:创建指定容量大小的数组
	 * @param capacity 数组容量
	 */
	public Array(int capacity) {
		this.data = (E[])new Object[capacity];
	}
	
	/**
	 * 有参构造:创建可以传进指定类型数组的数组
	 * @param arr 传进的数组
	 */
	public Array(E[] arr) {
		this.data = (E[])new Object[arr.length];
		for(int i = 0; i < arr.length; i++) {
			data[ i ] = arr[ i ];
		}
		size = arr.length;
	}
	
	/**
	 * 返回指定索引的原值
	 * @param index 索引值
	 * @return 该索引对应的元素值
	 */
	public E getElement(int index) {
		return data[index];
	}
	
	/**
	 * 获取数组的长度
	 * @return 数组长度
	 */
	public int getSize() {
		return this.size;
	}
	
	/**
	 * 向指定位置添加元素
	 * @param index 数组索引值
	 * @param e 添加的元素
	 */
	public void add(int index,E e) {
		if(index < 0 || index >data.length) {
			throw new IndexOutOfBoundsException("参数错误:索引越界");
		}
		if(this.size == data.length) {
			resize(data.length * 2);
		}
		for(int i = this.size - 1; i > index; i--) {
			data[i+1] = data[ i ];
		}
		data[index] = e;
		size++;
	}

	/**
	 * 将指定元素添加到数组中索引为0的位置
	 * @param e 添加的元素
	 */
	public void addFirst(E e) {
		add(0,e);
	}
	
	/**
	 * 将指定元素添加到数组尾部
	 * @param e 添加的元素
	 */
	public void addLast(E e) {
		add(size,e);
	}
	
	/**
	 * 判断是否为空
	 * @return 数组为空返回true,不为空返回false
	 */
	public boolean isEmpty() {
		return this.size == 0;
	}
	
	/**
	 * 数组扩容
	 * @param newCapacity 扩容后的数组长度
	 */
	private void resize(int newCapacity) {
		E[] newData = (E[])new Object[newCapacity];
		for(int i =0; i < data.length; i++) {
			newData[i] = data[i];
		}
		data = newData;
	}
	
	/**
	 * 删除某个索引上的元素
	 * @param index
	 * @return
	 */
	public E remove(int index) {
		if(index > data.length || index < 0) {
			throw new IndexOutOfBoundsException("参数错误");
		}
		E res = data[index];
		for(int i = index; i < this.size - 1; i++) {
			data[i] = data[i + 1];
		}
		data[this.size -1] = null;
		size--;
		return res;
	}
	
	/**
	 * 删除最后一个元素
	 * @return 删除的元素
	 */
	public E removeLast() {
		E res = data[data.length - 1];
		remove(data.length - 1);
		return res;
	}

	/**
	 * 设置数组的第index个元素为e
	 * @param index 索引
	 * @param e 元素
	 */
	public void set(int index, E e) {
		if(index > data.length || index < 0) {
			throw new IndexOutOfBoundsException("参数错误:索引越界");
		}
		data[index] = e;
	}
	
	/**
	 * 交换两个元素的值
	 * @param i
	 * @param j
	 */
	public void swap(int i,int j) {
		if( i < 0 || i >= size || j < 0 || j >= size) {
			throw new IllegalArgumentException("交换的索引值不合法!!");
		}
		E pre = data[ i ];
		data[ i ] = data[ j ];
		data[ j ] = pre;
	}
}

MaxHeap类:实现最大堆

/**
 * 使用数组实现最大堆,注意数组中最大堆中孩子节点的索引和父亲节点的索引之间的关系,存放时从索引为0开始
 * @author ChenZhuJi
 *
 * @param <E>元素的类型
 */
public class MaxHeap<E extends Comparable<E>> {
	private Array<E> data;
	public MaxHeap(int capacity) {
		data = new Array<>(capacity);
	}
	public MaxHeap() {
		data = new Array<>();
	}
	/**
	 * Heapify方法创建一个可以将任意数组添加进堆中的构造方法
	 * @param arr 数组
	 */
	public MaxHeap(E[] arr) {
		data = new Array<>(arr);
		for(int i = getParent(arr.length - 1); i >= 0; i--) {
			siftDown(i);
		}
	}
	/**
	 * 获取最大堆中的元素的个数
	 * @return 元素个数
	 */
	public int getSize() {
		return data.getSize();
	}
	
	/**
	 * 判断最大堆是否为空
	 * @return 最大堆是否为空
	 */
	public boolean isEmpty() {
		return data.isEmpty();
	}
	
	/**
	 * 返回最大堆的数组表示中,一个指定索引的节点的父亲节点的索引
	 * @param index 孩子节点的索引值
	 * @return 父亲节点的索引值
	 */
	private int getParent(int index) {
		if(index == 0) {
			throw new IllegalArgumentException("根节点没有父亲节点!!");
		}
		return (index - 1) / 2;
	}
	
	/**
	 * 返回最大堆的数组表示中,一个指定索引的节点的左孩子节点的索引
	 * @param index 父亲节点的索引值
	 * @return 左孩子结点的索引值
	 */
	private int getLeftChild(int index) {
		return index * 2 + 1;
	}
	
	/**
	 * 返回最大堆的数组表示中,一个指定索引的节点的右孩子节点的索引
	 * @param index 父亲节点的索引值
	 * @return 右孩子节点的索引值
	 */
	private int getRightChild(int index) {
		return index * 2 + 2;
	}
	
	/**
	 * 向最大堆中添加一个元素
	 * @param e 待添加的元素
	 */
	public void add(E e) {
		data.addLast(e);
		siftUp(data.getSize() - 1);
	}
	/**
	 * 最大堆中的上浮操作:如果一个节点比它的父亲节点大,就交换这两个节点的位置
	 * @param index 进行上浮操作的节点的索引值
	 */
	private void siftUp(int index) {
		while(index > 0 && data.getElement(index).compareTo(data.getElement(getParent(index))) > 0) {
			data.swap(index, getParent(index));
			index = getParent(index);
		}
	}
	
	/**
	 * 查看最大堆中值最大的元素
	 * @return 最大的元素的值
	 */
	public E findMax() {
		if(data.getSize() == 0) {
			throw new IllegalArgumentException("堆为空!!");
		}
		return data.getElement(0);
	}
	
	/**
	 * 取出最大堆中的最大元素
	 * @return 被取出的元素
	 */
	public E extractMax() {
		E ret = findMax();
		data.swap(0, getSize() - 1);
		data.removeLast();
		siftDown(0);
		return ret;
	}
	/**
	 * 最大堆中的下沉操作
	 * @param index 进行下沉操作的元素的索引值
	 */
	private void siftDown(int index) {
		while( getLeftChild(index) < data.getSize()) {	//若当前节点不是叶子节点(则必有左孩子)
			int pre = getLeftChild(index);		//获取左孩子的节点索引值
			if( pre + 1 < data.getSize() && data.getElement(pre + 1).compareTo(data.getElement(pre)) > 0) {	//若右孩子比左孩子大
				pre = getRightChild(index);		//此时pre存储的是左孩子和右孩子中较大的那一个的索引值
			}
			if( data.getElement(index).compareTo(data.getElement(pre)) > 0) {	//若此时父亲比左右孩子都大
				break;
			} else {	//父亲并不比左右孩子大
				data.swap(index, pre);
				index = pre;
			}
		}
	}
	
	/**
	 * 取出最大堆堆顶元素,并替换进一个新的元素
	 * @param e 要替换的元素
	 * @return 堆顶的元素
	 */
	public E replace(E e) {
		E ret = findMax();
		data.set(0, e);
		siftDown(0);
		return ret;
	}
}

PriorityQueue:优先队列类

/**
 * 优先队列的实现类:实现最大堆实现
 * @author ChenZhuJi
 *
 * @param <E>
 */
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
	private MaxHeap<E> maxHeap;
	public PriorityQueue() {
		maxHeap = new MaxHeap<>();
	}
	@Override
	public void enqueue(E e) {
		maxHeap.add(e);
	}

	@Override
	public E dequeue() {
		return maxHeap.extractMax();
	}

	@Override
	public E getFront() {
		return maxHeap.findMax();
	}

	@Override
	public int getSize() {
		return maxHeap.getSize();
	}

	@Override
	public boolean isEmpty() {
		return maxHeap.isEmpty();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值