排序算法(3)之堆排序——java实现

13 篇文章 0 订阅

堆(数据结构)的定义:

wiki百科中对堆的定义是

wikipedia 写道
堆(heap)亦被称为 优先队列(priority queue),通常是一个 可以被看做一棵数组对象。在队列中,调度程序反复提取队列中的第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

 

既然堆是一棵树,那么其特点也应该是递归的了。继续wikipedia:

写道
堆的实现通过构造二叉堆(binary heap),实为二叉树( 疑问:堆不应该只有二叉堆吧?)的一种,二叉堆是完全二叉树,性质如下:
1.父节点的键值总是大于等于(或小于等于)任何子节点的键值
2.每个节点的左右子树都是一个二叉堆(具有树的标志性的递归定义)

其中父节点的键值比子节点大的叫做大根堆,反之则是小根堆。 

 

小根堆例子:                                                             大根堆:(同样数据)

                           1                                                                          9

                      /          \                                                                 /        \

                    4            6                                                             7          8  

                 /     \        /     \                                                       /      \     /     \

               5       7    8       9                                                   6       5   4       1

可能大家建的堆跟图中的不一样,但是也符合堆的定义,因此可以看到对于同样一组数据,堆的构建不是唯一的,因此堆排序的不稳定的也不难理解了。

 

 堆排序的时间复杂度是O(NlogN),这个后面会介绍到。

因为其他几种堆(二项式堆,斐波那契堆)用的较少,因此通常来讲,我们习惯将堆默认为二叉堆。

 

堆是用数组来存储的,采取的是树的双亲存储结构(一种顺序存储结构),原因:堆是一颗完全二叉树,用下标即可表达父子关系,而数组具有操作简单,速度更快的优点。

 以上图中的小根堆为例:

 

1465789

 i 节点的孩子下标应该是2 * i + 1(左孩子)和 2 * i + 2(右孩子),父节点的下标应该是(i - 1)/ 2【下标从0开始】

 

 堆排序的过程:(堆的基本操作:插入删除 包含其中,不再单独介绍)

 1)建堆(以大根堆为例)(图源来自http://www.java3z.com/cwbwebhome/article/article1/1362.html?id=4745感谢原博主)


 该完全二叉树中,叶节点为30,48,93,15,35,显然,叶节点是满足堆的要求的,因此我们应该从第一个非叶节点 72 开始调整。 


 72比35大,因此不需要做处理,再看53,比左孩子小,将其与左孩子交换;再看18,比两个孩子都小,应该跟大的换,如果跟30换,那么30还要继续跟48换,从而才能保证根最大;

 

以此类推...直到根节点 

 2)排序

建堆工作已经完毕,我们将最大的元素放在了根节点,首先我们将根节点与最后一个节点(35)作交换。第一趟排序完成。93到达了最终位置。将剩余部分继续调整为堆即可,现在堆中只有35一个数字不满足堆的定义。其他记录都满足,因此只需要调整35即可。

具体的步骤就是35一直往下沉,直到满足堆的定义。不再赘述。

经过第二趟排序,我们可以得到次大的元素72,再将72与最后一个节点交换,依照以上处理方法继续处理,直到树中只有一个元素位置,排序结束。

 

算法思想如下:

 

public void heapSort(int[] a){

    //1.build the heap

    //2.exchange the first node with the last one,heapLength--;

    //3.split the biggest node(the last node after changed) with left ones,
    
    //4.adjust the first node(after changed)to fit in heap defination

    }

  

 下面来看代码,首先我们知道,图例介绍中,堆排序主要分为两部分:建堆 & 调整;我们可以看到建堆的过程其实也是在调整,刚好符合树的递归定义,因此,我们先介绍如何调整。

	static void ajustHeap(int[] heap, int length, int i) {

		int left = 2 * i + 1;//左孩子
		int right = 2 * i + 2;//右孩子
		int big = i;//较大的节点下标
		int tem;
		while (left < length || right < length) {//循环直到确定最终位置
			if (left < length && heap[left] > heap[big]) {
				big = left;
			}
			if (right < length && heap[right] > heap[big]) {
				big = right;
			}//确定较大键值的下标
			if (i == big) {//如果该节点满足要求,则跳出循环
				break;
			} else {//否则与较大键值的孩子交换,并递归往下
				tem = heap[i];
				heap[i] = heap[big];
				heap[big] = tem;
				i = big;
				left = 2 * i + 1;
				right = 2 * 2 + 2;
			}
		}
}

 配合上图中建堆过程中的调整理解。

static void buildHeap(int[] heap, int length) {
        //从第一个非叶结点开始调整
//由于堆是完全二叉树,因此如果堆的总节点个数是偶数,则最后一个叶节点一定是其父节点的左孩子
//如果堆的总结点数是奇数,则非叶节点均包含两个孩子(扯远了)
         
	int begin = length % 2 == 0 ? length / 2 : (length - 1) / 2;
	for(int i = begin; i >= 0;i--){
		ajustHeap(heap, length, i);//建堆的过程就是逐个调整的过程
	}
}

 

public static void main(String[] args) {

		int[] heap = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
		int length = heap.length;
		buildHeap(heap, length);//建堆
		System.out.println(Arrays.toString(heap));

		while (length > 1) {
			int tem = 0;
			tem = heap[length - 1];
			heap[length - 1] = heap[0];
			heap[0] = tem;//将收尾交换
			length--;//将最大节点从堆中删除
			ajustHeap(heap, length, 0);//调整堆,只需调整第一个节点即可
		}
		System.out.println(Arrays.toString(heap));
	}
}

 

 打印结果是:

写道
[9, 8, 6, 3, 5, 4, 1, 2, 7]
[1, 2, 3, 5, 4, 6, 7, 8, 9]

 从代码中可以看出,调整每个节点的时间复杂度是树的高度logN,因此简化后的时间复杂度为O(NlogN)

空间复杂度,由于存在交换键值,因此需要一个额外空间,空间复杂度为O(1)。

堆排序适合记录数很多的情况,比如从100000个记录中选出最小的前10个,用堆排序最好。如果记录数较少,则不提倡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值