堆排序&优先队列

本文详细介绍了堆排序的概念,包括堆的定义、维护堆性质的方法、建堆过程及堆排序的步骤。同时,讨论了如何使用最大堆实现最大优先队列,包括其主要操作如返回最大元素、删除最大元素、更新元素值和插入元素的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、堆排序
1、堆

  (二叉)堆是一个数组,它可以被近似看成是一颗完全二叉树。树上的每一个结点都对应数组的一个元素。除了最底层,该树是完全充满的,该树在数组中是从左向右填充的。表示堆的数组A通常包括两个属性:数组长度A.length和堆的元素个数A.heapSize。给定一个结点的下标i,可以知道它的父结点、左孩子和右孩子的下标分别为i/2、2i和2i+1(下标从1开始)。
在这里插入图片描述最大堆(大根堆):对于除根结点的任意结点,它的值小于等于它的父结点。
最小堆(小根堆):对于除根结点的任意结点,它的值大于等于它的父结点。

2、维护堆的性质

  使用下面的方法来维护最大堆的性质,它的输入是一个数组A和一个下标i,假定根结点为left(i)和right(i)的二叉树都是最大堆(即结点i的左右子树都是最大堆)但这时A[i]可能小于它的左右结点,这样就违背了最大堆的性质。下面的方法通过让A[i]的值在最大堆中“逐级下降”,从而使得以下标i为根结点的子树重新遵循最大堆的性质。

private void maxHeap(List<Integer> t,int i,int heapSize){
	int lagest = i;	//代表i结点和它的左右孩子中最大值的下标
	int l = i*2;	//左孩子下标
	int r = i*2+1;	//右孩子小标
	//heapSize为堆的大小
	if( i <= heapSize && t.get(i) < t.get(l) )
		lagest = l;
	else
		lagest = i;
	if( lagest <= heapSize && t.get(lagest) < t.get(r))
		lagest = r;
	if( lagest != i){	//交换最大值和根结点的值
		int vis = t.get(largest);
		t.set(largest, t.get(i));
		t.set(i, vis);
		maxHeap(t,largest,heapSize);	//由于交换可能使得i结点的孩子不满足最大堆的性质
	}
}
3、建堆

  对于一个大小n的数组(下标从1开始),它的最后一个非叶结点是n/2(向下取整,下同),即n/2+1、n/2+2…一直到n都是叶结点。建堆就是从最后一个非结点开始自底向上维护堆的性质。

public void buildMaxHeap(List<Integer> t){
	int len = t.size()-1;	//由于下标从1开始,对于有一个有t个元素的list,除去list[0]外还有t-1个
	for(int i = len/2; i >= 1 ; i--){
		maxHeap(t,i,len);
	}
}
4、堆排序

  知道了建堆和维护堆的方法就可以开始堆排序了。堆排序就是交换堆的根结点和最后一个叶子结点的位置,即交换数组第一个元素和最后堆元素的位置,并把堆的大小减一,即把数组的最后一个元素不算在堆的范围内,heapSize由此减一。交换之后除了根结点外其他结点都满足最大堆的性质,所以要调用一次maxHeap维护最大堆的性质,然后又重新交换元素。

public void headSort(List<Integer> t>{
	buildMaxHeap(t);	//首先建立最大堆
	int len = t.size()-1;	//数组元素长度
	int heapSize = len;		//初始堆大小
	for(int i = t.size()-1; i > 1 ; i--){
		int vis = t.get(i);
		t.set(i,t.get(1));
		t.set(1,vis);
		heapSize--;
		maxHeap(t,1,heapSize);
	}
}
二、优先队列

  这里用最大堆实现最大优先队列,假设数组中的元素已经符合最大堆的性质。一般一个最大优先队列支持一下操作:
1、返回最大元素(优先级最高的元素)
这个直接返回根结点,即数组的第一个元素。

public int maxIMum(List<Integer> t){
	return t.get(1);
}

2、去掉返回最大的元素
类似于堆排序,交换根结点和最后一个叶结点,然后将堆得大小减一并维护堆的性质。

public int extractMax(List<Integer> t){
	if( heapSize < 1)	//堆中无元素
		return -1;
	int max = t.get(1);
	t.set(1,t.get(heapSize));	//将堆中最后一个叶结点放到根的位置
	heapSize--;	//去掉最后一个叶结点,相当于去掉了最大的元素
	heapMax(t,1,heapSize)	//维护堆的性质
	return max;
}

3、将某个元素x的值增加k,要求k>=x。
增加某个结点值,不会影响他的后代结点是最大堆的性质,但是可能影响他的父结点。所以如果x的值增加到k后大于它的父结点的值,那么交换它的它父结点,由于它的父结点本来就大于原来的x,所以交换后也不会影响后代是最大堆的性质,就这样一直向上交换直到不大于它的父结点或到达根结点。

public void increaseKey(List<Integer> t, int i,int key){
	if(t.get(i) > key)
		return;
	t.set(i,key);
	while( i > 1 && t.get(i/2) < t.get(i) ){
		int vis = t.get(i/2);
		t.set(i/2,t.get(i));
		t.set(i,vis);
		i = i/2;
	}
}

4、插入一个结点到堆中
使用的方法是首先假设这个结点是一个很小额值,所以直接将其插入到堆的最后面,然后增大这个结点的值。

public void insert(List<Integer> t,int x){
	if( t.size() > heapSize )
		t.set(heapSize+1,x);
	else
		t.add(x);
	heapSize++;
	increatesKey(heapSize,x);
}

以上代码皆未验证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值