天天看点

Java实现堆以及堆排序

堆排序

堆排序使用的是二叉堆,它是一棵完全二叉树。堆有大顶堆和小顶堆之分。

堆的一些性质:

① :它是一颗完全二叉树。

② :每个节点大于(小于)或等于它的任意一个孩子。

表示堆的二叉树中,除了最底层外,该树是完全充满的,而且是从左至右填充的。

如果堆的大小是提前可知道的,那么可以将堆存储在一个ArrayList或是一个数组中。(ArrayList底层实现也是一个数组啦!)

存储在数组中的顺序也是一层一层的按从左至右的存储的。

对于位置 i处的结点,它的左孩子在位置 (2i+1)处,它的右孩子在(2i+2)处,它的父亲在位置((i-1)/ 2 )处。图示:

Java实现堆以及堆排序

                                                                                                               图:堆 - 1

堆常见的操作:

(一)添加一个新结点

给堆添加一个新结点,首先将新结点添加到堆尾,然后按如下方式(伪代码)重建这棵树:

Let the last node be the current node;

While(the current node is greater than its parent){

   Swap the current node with itsparent;

   Now the current node is one levelup;

}

n  以上伪代码参考自《计算机程序设计艺术》第三卷

结点添加图示:

Java实现堆以及堆排序

图:堆 - 2

(二)删除根结点

在实际的程序中我们需要经常删除堆中元素的堆顶元素(根结点)。在删除根结点后,当前余下的元素可能就违反了堆的性质,这时就需要重建堆,以保证堆的特征不变。

重建堆的算法描述如下:

Move the last node to replace the root;

Let the root be the current node;

n  在删除根元素之后,将当前的堆末尾元素移动到根元素位置,并成为当前元素

n  然后将当前元素和自己的两个孩子结点中最大的一个结点比较大小(如果只有一个孩子结点就直接和孩子结点比较),如果最大的孩子结点小于等于当前结点,则重建堆完成,否则将当前元素和其最大的孩子结点互换,互换之后重复本步操作。

下面将用图示演示删除根元素后重建堆的过程:(以图:堆–1所示堆为例)

Java实现堆以及堆排序

                     图:堆 - 3

建堆:在以上分析的基础上我们可以很快的构建出我们自己的堆(Heap)此处Heap实现底层将采用List实现;

Java实现堆以及堆排序

Heap类Java代码:

@SuppressWarnings("rawtypes")
public class Heap<E extends Comparable>{

	private java.util.List<E> list = null;
	
	/**
	 * 默认无参构造器
	 */
	public Heap(){
		list =  new java.util.ArrayList<E>();
	}
	
	/**
	 * 根据传入的序列构建一个堆
	 * @param objects
	 */
	public Heap(E[] objects){
		list =  new java.util.ArrayList<E>();
		for(int i = 0; i < objects.length; i++){
			add(objects[i]);
		}
	}
	/**
	 * 将一个新结点添加到堆中
	 * @param newObj
	 */
	@SuppressWarnings("unchecked")
	public void add(E newObj) {
		list.add(newObj); // 将新结点添加到最后一个位置上
		int currentIndex = list.size() - 1; // currentIndex指向堆中最后一个元素,也即当前元素为最后一个元素
		while (currentIndex > 0) {
			int parentIndex = (currentIndex - 1) / 2; // 根据堆的性质找到当前结点的父结点
			// 当前元素大于其父结点,互换
			if (list.get(currentIndex).compareTo(list.get(parentIndex)) > 0) { 
				E temp = list.get(currentIndex);
				list.set(currentIndex, list.get(parentIndex));
				list.set(parentIndex, temp);
			}else{
				break;
			}
			currentIndex = parentIndex;
		} // end_while
	}
	
	/**
	 * 移除堆顶元素
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public E remove(){
		
		if(list.size() == 0) return null;

		E root = list.get(0);
		list.set(0, list.get(list.size() - 1)); // 将堆末尾的元素移动到堆顶
		list.remove(list.size() - 1);  // 删除堆末尾元素,因为此时已经移动到了堆顶
		
		int currentIndex = 0; // 将堆顶元素设置为当前元素
		while(currentIndex < list.size()){
			int leftChildIndex  = 2 * currentIndex + 1; // 堆的性质
			int rightChildIndex = 2 * currentIndex + 2;
			// 边界判断,并找到当前元素的两个孩子结点中最大的一个
			if(leftChildIndex >= list.size())
				break;
			int maxIndex = leftChildIndex;
			if (rightChildIndex < list.size()) {
				if (list.get(maxIndex).compareTo(list.get(rightChildIndex)) < 0) {
					maxIndex = rightChildIndex;
				}
			}// end_2_if
			if (list.get(currentIndex).compareTo(list.get(maxIndex)) < 0) {
				E temp = list.get(maxIndex);
				list.set(maxIndex, list.get(currentIndex));
				list.set(currentIndex, temp);
				currentIndex = maxIndex;
			}else{
				break;
			}
		} // end_while
		return root;
	}
	/**
	 * 返回当前堆的大小
	 * @return
	 */
	public int size(){
		return list.size();
	}
}
           

堆排序:利用Heap类排序

public class HeapSort {

	@SuppressWarnings("rawtypes")
	public <E extends Comparable> void heapSort(E[] list){
	
		Heap<E> heap = new Heap<E>(list);
		for(int i = list.length - 1; i >= 0; i--){
			list[i] = heap.remove();
		}
	}
	
}
           

堆排序的时间复杂度:

堆排序时间复杂度为:O(nlogn)