堆,优先队列以及堆排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/freestyle4568/article/details/50726048

  今天我们来看一下另一种著名的排序算法,叫堆排序。它可以达到O(nlogn)的时间复杂度。这比我们之前介绍的插入排序算法要来的高效,和归并排序一样。在这里我们讨论高效性其实忽略了算法的常数因子大小,看起来堆排序和归并排序的时间复杂度很低,但是我们在实际运用中仍然用的不多。在大多数需要排序的场合下,我们一般使用快速排序算法,虽然该算法的时间复杂度是O(n^2),但是它的常数因子很小。所以在常用的数据规模下,它所花费的时间是最小的。

  在介绍堆排序之前,我们先来认识一下堆这个数据结构--heap。

1. 什么是堆?堆有哪些性质?

   上图,先来看看堆的样子:






















    上图展示的是二叉堆,近似的可以看做是完全二叉树,可以用数组表示出来。我们约定左孩子节点的索引是父节点索引的2倍,右孩子索引是父节点索引的2倍加1。这样一个数组可以完整的表示出二叉堆。

    堆分两种,一种是最大堆,一种是最小堆。如果用A数组来存放堆,我们可以表示出他们的性质。

    最大堆的性质是:A[parent(i)] >= A[i]

    最小堆的性质是:A[parent(i)] <= A[i]

    parent(i)表示索引是i的孩子的父亲的索引。

    这就是堆的所有的性质,简单吧!不过真因为简单,所以在堆中只包含很少的信息,以最大堆为例,我们只知道父节点一定不小于子节点,最顶端的节点最大,仅此而已。对于最小值在哪,我们根本无法确定,只知道它位于叶子节点上。

   一般的堆类都包含下面几个功能:

 

public interface Heap<T extends Comparable<T>> {
	void insert(T element);
	T get();
	T delete();
	boolean isEmpty();
	void makeEmpty();
}

我们可以向堆中insert元素,可以get最大或者最小的元素,可以delete最顶端的元素,也可以makeEmpty清空堆。

   在insert元素的时候,我们需要上滤的策略,即先将要插入的元素放在数组的最后,然后为了保证堆的性质,我们要从底向上不断的更新过滤。

   在delete元素的时候,我们需要下滤的策略,即将最后的元素放在顶端,从上往下更新过滤,为的也是保证堆的性质不变。

   也许你会晕,什么上滤下滤啊,其实也没那么复杂,都是为了保证堆的性质不发生变化。

   下面可以看看我用java写的最小二叉堆的代码:

 

package charp_1;

import java.util.ArrayList;

/**
 * @author freestyle458
 */

public class MinBinaryHeap<T extends Comparable<T>> implements Heap<T> {
	public static void main(String[] args)
	{
/*		MinBinaryHeap<Integer> minHeap = new MinBinaryHeap<>();
		minHeap.insert(10);
		minHeap.insert(3);
		minHeap.insert(40);
		minHeap.insert(100);
		minHeap.insert(1);
		minHeap.insert(5);
		minHeap.insert(7);
		minHeap.insert(2);
		
		System.out.println(minHeap);
		System.out.printf("the min element is : %d\n", minHeap.get());
		
		System.out.println(minHeap.delete());
		System.out.println(minHeap);
		
		System.out.println(minHeap.delete());
		System.out.println(minHeap);
		
		ArrayList<Integer> items = new ArrayList<>();
		items.add(8);
		items.add(15);
		items.add(7);
		items.add(100);
		items.add(2);
		items.add(7);
		MinBinaryHeap<Integer> minHeap2 = new MinBinaryHeap<>(items);
		System.out.println(minHeap2);
		
		System.out.println(minHeap2.delete());
		System.out.println(minHeap2);
		
		System.out.println(minHeap2.delete());
		System.out.println(minHeap2);
		System.out.println(minHeap2.delete());
		System.out.println(minHeap2);*/
	}
	
	private ArrayList<T> array;
	
	public MinBinaryHeap()
	{
		// TODO Auto-generated constructor stub
		array = new ArrayList<>();
		array.add(null);
	}
	
	public MinBinaryHeap(ArrayList<T> items)
	{
		this();
		array.addAll(items);
		for (int i = (array.size()-1)/2; i > 0; i--) {
			precolateDownMinHeap(i);
		}
	}
	
	
	@Override
	public void insert(T element)
	{
		// TODO Auto-generated method stub
		array.add(element);
		percolateUpMinHeap(array.size()-1);
	}
	
	private void percolateUpMinHeap(int index) 
	{
		int hole = index;
		T element = array.get(hole);
		while (hole > 1 && element.compareTo(array.get(hole/2)) < 0) {
			array.set(hole, array.get(hole/2));
			hole /= 2;
		}
		array.set(hole, element);
	}
	
	@Override
	public T get()
	{
		// TODO Auto-generated method stub
		if (isEmpty()) {
			return null;
		} else {
			return array.get(1);
		}
	}

	@Override
	public T delete()
	{
		// TODO Auto-generated method stub
		T minElement = get();
		if (minElement == null)
			return null;
		array.set(1, array.get(array.size() - 1));
		array.remove(array.size() - 1);
		if (isEmpty())
			return minElement;
		precolateDownMinHeap(1);
		return minElement;
			
	}
	
	private void precolateDownMinHeap(int index) 
	{
		int lChild = 2*index;
		int rChild = 2*index + 1;
		int min = index;
		if (lChild < array.size() && array.get(lChild).compareTo(array.get(index)) < 0) {
			min = lChild;
		}
		if (rChild < array.size() && array.get(rChild).compareTo(array.get(min)) < 0) 
			min = rChild;

		if (min != index) {
			T element = array.get(index);
			array.set(index, array.get(min));
			array.set(min, element);
			precolateDownMinHeap(min);
		} 
	}

	@Override
	public boolean isEmpty()
	{
		// TODO Auto-generated method stub
		if (array.size() == 1)
			return true;
		else
			return false;
	}

	@Override
	public void makeEmpty()
	{
		// TODO Auto-generated method stub
		array.clear();
		array.add(null);
	}
	
	@Override
	public String toString()
	{
		// TODO Auto-generated method stub
		String result = "the minHeap is :\n";
		int height = (int)(Math.log(array.size() - 1) / Math.log(2));
		for (int i = 0; i <= height; i++) {
			int levelSize = (int)Math.pow(2, i);
			for (int j = 0; j < levelSize; j++) {
				int position = (int)Math.pow(2, i) + j;
				if (position > array.size() - 1) {
					break;
				} else {
					result += array.get(position) + " ";
				}
			}
			result += "\n";
		}
		return result;
	}
	
}
因为以堆以树形的方式比较直观,所以我在toString方法中用近似数的形式表示出来了。

运行的效果是:



























可以看到堆的建立是正确的。insert和delete操作也是正常的!!!

在这基础上,我们可以将最大堆和最小堆合并起来,写出来一般性的堆类。其实所谓的最大堆和最小堆,无非是在上滤和下滤的过程中比较元素的方式不一样而已。

下面欣赏一下终极版的BinaryHeap类吧,可以很轻松的构造最大最小堆。

<span style="font-size:18px;">/**
 * 
 */
package charp_1;

import java.util.ArrayList;

/**
 * @author freestyle458
 *
 */
public class BinaryHeap<T extends Comparable<T>> implements Heap<T> {

	public static void main(String[] args)
	{
		BinaryHeap<Integer> items1 = new BinaryHeap<>(true);
		BinaryHeap<Integer> items2 = new BinaryHeap<>(false);
		
		items1.insert(10);
		items1.insert(8);
		items1.insert(20);
		items1.insert(30);
		items1.insert(3);
		items1.insert(50);
		System.out.println(items1);
		System.out.println("---------------------------");
		
		items2.insert(10);
		items2.insert(8);
		items2.insert(20);
		items2.insert(30);
		items2.insert(3);
		System.out.println(items2);
		System.out.println("----------------------------");
		
		ArrayList<Integer> nums = new ArrayList<>();
		nums.add(3);
		nums.add(4);
		nums.add(1);
		nums.add(10);
		nums.add(11);
		BinaryHeap<Integer> items3 = new BinaryHeap<>(true, nums);
		System.out.println(items3);
		System.out.println("---------------------------");
		
		BinaryHeap<Integer> items4 = new BinaryHeap<>(false, nums);
		System.out.println(items4);
		System.out.println("---------------------------");
		
		System.out.println("delete top element is : " + items3.delete());
		System.out.println(items3);
		System.out.println("delete top element is : " + items3.delete());
		System.out.println(items3);
	}
	
	private ArrayList<T> array;
	private boolean isMaxHeap;
	
	public BinaryHeap()
	{
		//默认是以最大堆的形式出现
		this(true);
	}
	
	public BinaryHeap(boolean isMaxHeap)
	{
		this.isMaxHeap = isMaxHeap;
		array = new ArrayList<>();
		array.add(null);
	}
	
	public BinaryHeap(boolean isMaxHeap, ArrayList<T> items)
	{
		this(isMaxHeap);
		array.addAll(items);
        //从每个节点(除叶子节点)开始下滤
		for (int i = (array.size()-1)/2; i > 0; i--) {
			percolateDown(isMaxHeap, i);
		}
	}
	
	private void percolateDown(boolean isMaxHeap, int index) 
	{
		int child;
		int hole = index;
		T element = array.get(hole);
		
		if (isMaxHeap) {
			while (2*hole < array.size()) {
				child = 2*hole;
				if (child != array.size()-1 && array.get(child).compareTo(array.get(child+1)) < 0)
					child++;
				if (array.get(child).compareTo(element) > 0) {
					array.set(hole, array.get(child));
					hole = child;
				} else 
					break;
			}
			array.set(hole, element);
		} else {
			while (2*hole < array.size()) {
				child = 2*hole;
				if (child != array.size()-1 && array.get(child).compareTo(array.get(child+1)) > 0)
					child++;
				if (array.get(child).compareTo(element) < 0) {
					array.set(hole, array.get(child));
					hole = child;
				} else 
					break;
			}
			array.set(hole, element);
		}
	}
	
	@Override
	public void insert(T element)
	{
		// TODO Auto-generated method stub
		array.add(element);
		percolateUp(isMaxHeap, array.size()-1);
	}
	
	private void percolateUp(boolean isMaxHeap, int index)
	{
		int hole = index;
		T element = array.get(hole);
		//如果是最大堆,那么将上层的小者换下, 如果是最小堆,则将上面的大者换下
		if (isMaxHeap) {
			while (hole > 1 && element.compareTo(array.get(hole/2)) > 0) {
				array.set(hole, array.get(hole/2));
				hole /= 2;
			}
		} else {
			while (hole > 1 && element.compareTo(array.get(hole/2)) < 0) {
				array.set(hole, array.get(hole/2));
				hole /= 2;
			}
		}
		array.set(hole, element);
	}

	@Override
	public T get()
	{
		// TODO Auto-generated method stub
		if (isEmpty())
			return null;
		else
			return array.get(1);
	}

	@Override
	public T delete()
	{
		// TODO Auto-generated method stub
		T popitems = get();
		if (popitems == null)
			return null;
		else {
			array.set(1, array.get(array.size()-1));
			array.remove(array.size()-1);
			if (!isEmpty()) {
				percolateDown(isMaxHeap, 1);
			}
			return popitems;
		}
	}

	@Override
	public boolean isEmpty()
	{
		// TODO Auto-generated method stub
		if (array.size() == 1)
			return true;
		else
			return false;
	}

	@Override
	public void makeEmpty()
	{
		// TODO Auto-generated method stub
		array.clear();
		array.add(null);
	}

	@Override
	public String toString()
	{
		String result = isMaxHeap ? "the maxHeap is:\n" : "the minHeap is:\n";
		int height = (int)(Math.log(array.size() - 1) / Math.log(2));
		for (int i = 0; i <= height; i++) {
			int levelSize = (int)Math.pow(2, i);
			for (int j = 0; j < levelSize; j++) {
				int position = (int)Math.pow(2, i) + j;
				if (position > array.size() - 1) {
					break;
				} else {
					result += array.get(position) + " ";
				}
			}
			result += "\n";
		}
		return result;
	}
}
</span>

下面来看一下运行测试的结果,看看是否符合要求:






































简单的介绍完了堆结构之后,我们下面来看一下堆排序。

如何用堆排序呢?其实你已经想到了吧!比如最大堆为例,我们知道最大的元素在最顶端,那么每次我们取出最顶端的元素放在数组的最后,不就可以很好的完成堆排序啦!

遍历每个元素用O(logN)的时间,总共有N个元素,所以估计一下时间应该是O(NlogN)。

说到时间复杂度,需要注意的是用乱序数组构造最大堆的过程是线性时间完成的,为O(N)。你也许会说为什么不是O(nlogn)呢,因为每个元素的高度和是O(n),所以在下滤时总共用了O(n)的时间。

好了,我们看看具体的实现代码吧!

<span style="font-size:18px;">/**
 * 
 */
package charp_1;

/**
 * @author freestyle458
 *
 */
public class HeapSort {
	public static void main(String[] args)
	{
		int[] nums = new int[]{0, 12, 1, -78, 43, 78, 6 ,10};
		Integer[] items = new Integer[nums.length];
		for (int i = 0; i < nums.length; i++) {
			items[i] = nums[i];
		}
		heapSort(items);
		for (Integer i : items) {
			System.out.printf("%d ", i);
		}
		System.out.println();
	}
	
	
	public static <T extends Comparable<T>> void percolateDown(T[] a, int i , int n)
	{
		int hole = i;
		T element = a[hole];
		int child;
		while (2*hole + 1 < n ) {
			child = 2 * hole + 1;
			if (child != n - 1 && a[child].compareTo(a[child+1]) < 0) {
				child++;
			}
			if (element.compareTo(a[child]) < 0) {
				a[hole] = a[child];
				hole = child;
			} else
				break;
		}
		a[hole] = element;
	}
	
	public static <T extends Comparable<T>> void swap(T[] a, int i, int j)
	{
		T tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
	}
	
	public static <T extends Comparable<T>> void heapSort(T[] a)
	{
		for (int i = a.length/2; i >= 0; i--) 
			percolateDown(a, i, a.length);
		
		for (int i = a.length - 1; i > 0; i--) {
			swap(a, 0, i);
			percolateDown(a, 0, i);
		}
	}
}
</span>
运行结果:







介绍完了堆排序,我们来看一下优先队列,这个也是堆结构运用最多的地方!

什么是优先队列呢?我们知道一般的队列是先进先出,后进后出。而优先队列是依据元素的优先级来最先处理的。比如最大优先队列是优先级最大的元素先得到处理。

这个很符合最大堆的特点,值最大的永远在最顶端。优先队列在操作系统的任务管理中起到很重要的作用。那么用堆如何实现呢?在理解了最大最小堆的实现过程后可以很容易编写。

一般的优先队列具有:

insert(x); 插入元素

max(s);  取出最大元素

deleteMax(s); 删除并取出最大元素

increaseKey(s, x, key);  增加特定元素的值

这些方法在利用最大最小堆的基础上增加几行代码就可以了!这里就不贴出来了,有兴趣的朋友可以自己写一下哦!


















猜你喜欢

转载自blog.csdn.net/freestyle4568/article/details/50726048