【数据结构】基础算法:排序——优先队列的原理与实现

优先队列

优先级队列为,首部元素最大,总是删除当前最大元素。并在尾部插入元素。

优先队列的两种操作:删除最大元素和插入元素。

优先队列的一些重要的应用场景包括:

  • 模拟系统,其中事件的键即为发生的时间,而系统需要按照时间顺序处理所有事件;
  • 任务调度,其中键值对应的优先级决定了应该首先执行哪些任务;
  • 数值计算,键值代表计算错误,而我们需要按照键值指定的顺序来修正它们。
  • 堆排序,通过插入一列元素然后一个个地删掉其中最小的元素,我们可以用优先队列实现排序算法。
    在这里插入图片描述

初级实现

4种基础数据结构:有序或无序的数组或链表,可以简单实现优先队列

数组实现(无序)

在栈的压栈和弹栈操作基础上,可以实现无序的优先队列:
1、insert() 方法的代码和栈的push()方法完全一样。
2、要实现删除最大元素,我们可以添加一段类似于选择排序的内循环的代码,将最大元素和边界元素交换然后删除它, 和我们对栈的 pop() 方法的实现一样。

数组实现(有序)

另一种方法就是在 insert() 方法中添加代码,将所有较大的元素向右边移动一格以使数组保持有序(和插入排序一样)。这样,最大的元素总会在数组的一边,优先队列的删除最大元素操作就和栈的 pop() 操作一样了。

链表表示法

对于优先队列,我们刚刚讨论过的所有初级实现中,插入元素和删除最大元素这两个操作之一在最坏情况下需要线性时间来完成。基于数据结构堆的实现能够保证这两种操作都能更快地执行。
在这里插入图片描述

堆的定义

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

定义。当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。

定义。二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不使用数组的第一个位置)。

(将二叉堆简称为堆)在一个堆中,位置 k 的结点的父结点的位置为 ⌊ k / 2 ⌋ \lfloor k / 2\rfloor k/2,而它的两个子结点的位置则分别为 2k 和 2k+1。这样在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从 a[k] 向上一层就令 k 等于 k/2,向下一层则令 k 等于 2k 或 2k+1。

如果我们用指针来表示堆有序的二叉树,那么每个元素都需要三个指针来找到它的上下结点(父结点和两个子结点各需要一个)

在这里插入图片描述

命题 P。一棵大小为 N 的完全二叉树的高度为 ⌊ l g N ⌋ \lfloor lgN\rfloor lgN

用堆实现优先队列

1. 数据的储存

我们用长度为 N+1 的私有数组 pq[] 来表示一个大小为 N 的堆,我们不使用 pq[0], 堆元素放在 pq[1] 至pq[N] 中。在排序算法中,我们只通过私有辅助函数 less() 和 exch() 来访问元素,进行元素间的比较和交换。

2. 堆有序化过程

1、当堆底添加了一个新元素,或者某节点的优先级上升,此时堆的有序性被打破,我们需要由下至上恢复堆的有序——上浮

上浮操作:不断的交换某节点与其父节点,将节点上移直到遇到比它更大的父节点。只要记住位置 k 的结点的父结点的位置是 ⌊ k / 2 ⌋ \lfloor k / 2\rfloor k/2

private void swim(int k) { 
	while(k>1&&less(k/2,k)) {
		exch(k/2,k); 
		k=k/2;
	}

2、某个结点变得比它的两个子结点或是其中之一更小了,而打破了堆的有序状态,则可以由上至下恢复堆的有序——下沉

下沉操作:只需要将它与两个子节点之间较大者相互交换。将结点向下移动直到它的子结点都比它更小或是到达了堆的底部

private void sink(int k) {
	while(2*k<=N) {
		int j = 2*k; 
		if(j<N && less(j,j+1)) j++;
		if(!less(k,j)) break;
		exch(k,j);
		k=j;
	}
}

在这里插入图片描述

基于堆的插入、删除最大元素操作

在这里插入图片描述
插入元素。 我们将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置

删除最大元素。 我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置

// 基于堆的完全二叉树实现
public class MaxPQ <Key extends Comparable<Key>> {
	               //只能接收该类型及其子类
	private Key[] pq; //存储于pq[1..N]中,pq[0]没有使用
	private int N=0;
	
	//含参构造器
	public MaxPQ(int maxN) {
		//直接创建泛型数组是不被允许的,需要使用类型转换
		pq = (Key[]) new Comparable[maxN+1];
	}
	
	//判断是否为空
	public boolean isEmpty() {
		return N==0;
	}
	
	//返回大小
	public int size()
	{ return N;	}
	
	//插入操作
	public void insert(Key v) {
		if(N==pq.length) resize(2*pq.length); //动态改变堆大小
		pq[++N] = v;
		swim(N);
	}
	
	//删除最大值
	public Key delMax() {
		Key max = pq[1]; // 最大元素在根结点处
		exch(1,N--);     //将其与最后一个节点交换
		pq[N+1]=null;    //防止对象游离
		sink(1);         //下沉,恢复节点有序性
		if(N>0&&N==pq.length/4) resize(pq.length/2);
		return max;
	}
	
	//堆实现的比较方法
	private boolean less(int i,int j) 
	{	return pq[i].compareTo(pq[j])<0;}
	
	//堆实现的交换方法
	private void exch(int i,int j)
	{    Key t=pq[i]; pq[i]=pq[j]; pq[j]=t;	}

	//上浮(由下至上的堆有序化)实现
	private void swim(int k) { 
		//如果k>1且其父节点比子节点小,那么交换
		while(k>1&&less(k/2,k)) {
			exch(k/2,k); 
			//子节点转换为父节点
			k=k/2;
		}
	}
	
	//下沉(由上至下的堆有序化)实现
	private void sink(int k) {
		while(2*k<=N) {
			//父节点与较大的那个节点交换
			int j = 2*k; 
			if(j<N && less(j,j+1)) j++;
			if(!less(k,j)) break;
			exch(k,j);
			k=j;
		}
	}
	
	// 动态调整数组大小
	private void resize(int max) {
		//将大小为N的堆移动到一个新的大小为max的数组
		Key[] temp = (Key[])new Comparable[max];
		for (int i=0;i<N;i++) {
			temp[i]=pq[i];
		}
		pq=temp;
	}
}

堆排序

堆排序: 我们可以把任意优先队列变成一个排序算法:如将使用一个查找最大(最小)元素的优先队列并重复删除最大(最小)元素。

实际上用无序数组实现的查找最小元素的优先队列这么做相当于进行一次选择排序。

堆排序可以分为两个阶段:
1. 堆的构造阶段
我们将原始数组重新组织安排进一个堆中;
2.下沉排序阶段
我们从堆中按递减顺序取出所有元素并得到排序结果。

1. 堆的构造

构造堆从左至右遍历数组,用 swim() 保证扫描指针左侧的所有元素已经是一棵堆有序的完全树即可,就像连续向优先队列中插入元素一样。

更高效的方法是使用sink()下沉方法: 如果一个结点的两个子结点都已经是堆了,那么在该结点上调用 sink() 可以将它们变成一个堆。

2. 下沉排序

堆排序的主要工作都是在第二阶段完成的。这里我们将堆中的最大元素删除,然后放入堆缩小后数组中空出的位置。

在这里插入图片描述
在这里插入图片描述

堆排序算法的实现

package Sort;
//堆排序算法的实现
//对a[0]到a[N-1]排序
public class HeapSort {
	public static void Sort(Comparable[] a) {
		int N = a.length-1;
		//首先使用sink构造二叉堆
		for(int k=N/2;k>=0;k--)//对于所有非叶节点
		{
			sink(a,k,N);
		}
		//然后将最大元素放支末尾,并恢复堆有序
		while(N>0) {
			exch(a,0,N--);
			sink(a,0,N);
		}
	}
	
	private static void sink(Comparable[] a,int k,int N) {
		//将k节点下沉,直到叶节点
		while(2*k<=N) {
			int j=2*k;
			if(j<N && less(a[j],a[j+1])) j++;
			if(!less(a[k],a[j])) break;
			exch(a,k,j); //交换
			k=j;
		}
	}
	
	private static boolean less(Comparable v,Comparable w) {
		return v.compareTo(w)<0;
	}
	private static void exch(Comparable[] a,int v,int w) {
		Comparable t=a[v]; a[v]=a[w]; a[w]=t;
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值