堆排序(Java实现)

今天看了一下堆排序的内容,写篇博客记录一下。

关于堆的定义和性质就不说了,有一点需要说明一下,在给堆中的元素进行编号的时候,为了便于描述堆的性质,采取的编号方式是从上到下、从左至右依次编号,且堆顶元素编号为1。当使用数组来存储堆的时候,因为数组元素的编号是从0开始的,这就要求我们注意数组元素编号和堆元素编号的对应问题。下面以最大堆为例来说明堆排序的过程。


1、维护堆的性质

    该方法接受一个将要被构造成堆的数组和一个下标,数组用于保存堆节点。对于堆中的任意一个节点,假设其左子树和右子树都是最大堆,如果此时该节点的值大于其左右孩子的值,那么以该节点为顶的堆就是最大堆;如果小于其中的一个或两个孩子,就需要把该节点和较大的孩子交换,交换之后以该节点为根的子树可能又不满足最大堆的性质了,此时就要对该子树递归的调用维护堆性质的方法了。


2、建堆

    该方法接受一个数组。对于堆中的每一个非叶子节点,都调用1中的维护堆性质的方法就能构造一个堆了。对于叶子节点,由于没有左子树和右子树,可以把叶子节点看做已经满足最大堆性质的特殊的子树(特殊在左右子树都为空),所以不必对叶子节点执行维护堆性质的方法。当对非叶子节点执行维护堆性质的方法的时候,要按照序号从大到小的顺序进行,这是由维护堆性质的方法决定的,当我们先对下面的节点执行维护堆性质的算法时,就能保证对上面的节点执行同样的算法的时候,该节点的左右子树都满足了最大堆性质。


3、堆排序原理

    根据最大堆的性质,堆顶元素(数组的首元素a[0])必定是最大的,因此每次将堆顶元素和堆最后的元素交换,就能实现把数组中的最大值移动到数组的最后(递增排序)。交换之后,可能又不满足最大堆的性质,此时需要将数组再次进行建堆,由于此时已经有一个元素在他最终的位置上了,因此没有必要最整个数组进行建堆,只需要对数组中的未排部分进行建堆就行了,当然你对整个数组进行建堆的话,你会发现程序在原地踏步。


下面是完整的代码:

package com.hubu.heapsort;

public class HeapUtils {
	private static int len; //用于保存堆中数组元素的个数
	
	//获得编号为i的节点的左右孩子的编号
	public int left(int i) {
		return 2 * i;
	}

	public int right(int i) {
		return 2 * i + 1;
	}

	// 维护堆的性质的方法
	public void max_heap(int a[], int i) {
		int l = left(i);
		int r = right(i);
		int largest;

		if (l <= len && a[l - 1] > a[i - 1])
			largest = l;
		else
			largest = i;
		if (r <= len && a[r - 1] > a[largest - 1])
			largest = r;
		if (largest != i) {
			//将值最大的节点换到根节点的位置
			int temp = a[i - 1];
			a[i - 1] = a[largest - 1];
			a[largest - 1] = temp;

			max_heap(a, largest);// 递归调用
		}
	}

	// 构造初始堆的方法,对除了叶子节点之外的所有节点调用维护堆的方法
	public void build_heap(int a[]) {
		for (int i = a.length / 2; i >= 1; i--)
			max_heap(a, i);
	}

	// 堆排序算法
	public void heap_sort(int a[]) {
		build_heap(a);
		
		for (int i = a.length; i >= 2; i--) {
			int temp = a[0];
			a[0] = a[i-1];
			a[i-1] = temp;

			//每次把堆顶元素拿出来之后,下次进行建堆时,就不能把最后一个元素(最大)包含进来
			len--;
			max_heap(a, 1);
		}
	}

	public static void main(String[] args) {
		int a[] = { 6, 5, 4, 3, 2, 1, 0};
		len = a.length;
		HeapUtils hu = new HeapUtils();
		hu.heap_sort(a);
		for (int i = 0; i < a.length; i++)
			System.out.print(a[i] + " ");
	}
}


时间复杂度和稳定性分析

    先来看看整个算法经历了哪几个过程。先是进行初始化的建堆操作,然后取出最大值、执行维护堆性质的算法两者交替进行。建堆操作执行Θ(n)次维护最大堆性质的方法,维护最大堆性质的方法,在堆顶节点的值最小的时候(最坏情况)需要交换lgn次(最小元素每次降一层),因此建堆操作的时间复杂度为Θ(nlgn)。取出最大值、执行维护最大堆性质算法也会执行Θ(n)次,因此整个过程的复杂度同样是Θ(nlgn),所以堆排序算法的复杂度就是Θ(nlgn)。

    从建堆的原理中我们不难发现,在建堆的过程中就有可能已经改变他们在数组中的相对顺序,看下面的这个堆


按照上面给出的算法,我们不难发现,排序完成之后两个值为3的节点的相对顺序发生了改变,这也说明堆排序不是稳定的。


你可能感兴趣的:(算法)