基于堆的优先队列和堆排序

基于堆的优先队列和堆排序

代码参考《算法》第四版
堆有序就是每一个结点元素值大于子结点元素值。
向一个有序堆中,在堆尾部采用上浮法插入排序,可以使堆再次有序。
向一个有序堆中,将堆首部结点元素替换成要插入的元素,再对首结点采用下沉法排序,最后堆也是有序的。

package algorithms.sort;
/*基于堆的最大优先队列
 * 算法和数据结构中,要细致到每一个条件和判断,因为每一个条件和判断对应另一番处理逻辑,另一番场景。
 * 每一个方法背后可能有复杂的操作逻辑,每一个方法都要测试。
 * 使堆有序的方法:从左向右遍历数组,上浮法逐个元素插入,可使堆有序
 *             对无序堆,对一半有子节点的根节点递归使用下沉排序,可使得堆有序
 * */
public class MaxPQ2 {
    private int[] pq; //pq数组第一位置元素不使用,MaxPQBasedOnDui的存储结构,不会暴露给外部
    private int N; //N是有序队列的长度,也是数组pq最后一个元素的索引; 可以限制从pq中取值,因为pq中剩余位置元素值为0;
    public MaxPQ2(int maxSize) {
    	this.pq = new int[maxSize + 1]; //maxSize为优先队列底层最大可用长度
    	this.pq[0] = 0; //pq数组第一位置元素不使用
    	this.N = 0;
    }
    //优先队列长度
    public int size() {
    	return this.N;
    }
    //优先队列是否为空
    public boolean isEmpty() {
    	return N == 0;
    }
    //优先队列是否存满,即是否超出底层数组pq的最大长度
    public boolean isFull() {
    	return N >= pq.length - 1; //pq.length - 1 = maxSize
    }
    //顺序插入元素到pq中,不是用最大优先队列中的堆结构存储数据
    private void insertSequence(int v) { 
    	this.pq[++N] = v;
    }
    //insertSequence()测试, isFull()没有问题后,添加isFull()判断
    private void insertSequenceIfNotFull(int v) { 
    	if(!isFull())
    	  this.pq[++N] = v;
    }
    //测试,实际不会暴露给外部
    private int[] pq() {
    	return this.pq;
    }
    
    //插入操作, 上浮法构造有序堆,保证为最大优先队列;数组从左向右遍历,类似于插入排序(向优先队列插入元素)
    public void insert(int val) {
    	if(!isFull()) {
    		 pq[++N] = val; //数组中添加元素
        	 swim(N);       //并上浮到合适位置
    	} 
    }
    //插入一组元素,构造有序堆。
    public void insertAll(int[] a) {  
    	for(int v : a) { 
    	    if(!isFull()) { //即insert()方法
    		    pq[++N] = v;
    	        swim(N);
    		}
    	    else break; //跳出之后的for循环,因为之后的循环已经没有意义
       	} 
    }
    
    //下沉法构造有序堆; 递归地对一半带有子节点的每一个节点进行下沉排序,从而使整个堆有序;数组从右向左遍历
    //针对不是有序堆的数组
    public void sink() { //从根节点出发,不含有子节点的节点无法使用下沉排序,只能等待父节点下沉操作时被访问到
    	for(int k = this.N/2; k >= 1; k--)  //k=1时仍执行一次下沉操作
    		sink(k); 
    }
    //获取最大值元素
    //时间复杂度为1,因为插入的时候付出了额外的代价,并且堆结构优于无序数据的插入过程
    public int peek() {
    	return pq[1];
    } 
    //获取优先队列中某一位置元素
    public int get(int i) {
    	if(i > N - 1) //超过优先队列长度
    		throw new NullPointerException();
    	return pq[i + 1];
    }
    //删除最大值,delMax()方法破坏了堆的有序性
    public int delMax() {
    	int max = pq[1];
    	exch(1, N--); //堆首元素和最后一个元素进行交换,缩减优先队列索引长度,不被索引的元素不会被再次索引到,因为N变小,下次插入时会被覆盖掉
    	sink(1); //此时堆首元素再进行下沉操作,下沉到合适位置
    	return max;
    }
    //按照堆结构顺序打印优先队列元素
    public void print() {
    	for(int i = 1; i <= N; i++)
     	   System.out.print(pq[i]+" ");
    } 
    //堆排序。使用下沉法对有序堆排序,使数组有序、
    public void sort() {
    	int M = N;
    	//下沉法将堆无序构造为有序堆(针对最大优先队列这步已经操作过了)
    	for(int k = N/2; k >= 1; k--)
    		sink(k);
    	//在有序堆的基础上采用下沉法使得数组有序
    	while(M > 1) { 
    	   exch(1, M--);//1, M--都是当前限定长度数组中的首尾元素 
    	   sink(1, M); //除了堆首元素外其他节点都是堆有序的,所以将堆首元素下沉到合适位置就行。(仅考虑3个节点情况会非常清晰这一点)
    	}
    } 
    //此方法仅适用于有序堆,使数组有序
    public void sortOnlyForSequenceHeap() {
    	int M = this.N;
    	while(M >= 1) { 
    		exch(1, M--);
        	sink(1, M);
    	} 
    }
    
    //下沉方法,把pq中指定位置元素下沉到合适位置,此方法中较小值下沉,较大值上浮。
    private void sink(int k) { //操作的是pq数组, k为当前元素索引
    	if(k < 1) return;
    	while(2*k <= N) {//=时还可进行一次比较;2*k<N时,有左右两个子节点;2*k=N时,只有左子节点;2*k>N时,没有子节点;
    		int j = 2*k; //j是偶数,是左子节点;2*k<N时,有左右两个子节点
    		if(j < N && less(j, j + 1)) j++; //有左右两个子节点时,进行比较
    		if(less(j, k))  break;
    		exch(k, j); 
    		k = j;
    	}
    }
    //在指定(限定)的pq数组范围1~M内排序指定元素,其中 M<=N 
    private void sink(int k, int M) { //操作的是pq数组, k为当前元素索引
    	if(k < 1) return;
    	while(2*k <= M) {//=时还可进行一次比较;2*k<N时,有左右两个子节点;2*k=N时,只有左子节点;2*k>N时,没有子节点;
    		int j = 2*k; //j是偶数,是左子节点;2*k<N时,有左右两个子节点
    		if(j < M && less(j, j + 1)) j++; //j < M不要写错,有左右两个子节点时,进行比较
    		if(less(j, k))  break;
    		exch(k, j); 
    		k = j;  //继续往下比较
    	}
    }
    //上浮方法,将指定位置的元素上浮到合适位置,此方法中较大值上浮,较小值下沉
    private void swim(int k) {//操作的是pq数组, k为当前元素索引
    	while(k > 1 && less(k/2, k)) { //k/2为当前节点k的父节点;
    		exch(k/2, k);
    		k = k/2;
    	} 
    }
    //数组元素比较大小
    private boolean less(int i, int j) { //操作的是pq数组
    	return pq[i] < pq[j]; 
    }
    //数组元素交换
    private void exch(int i, int j) { //操作的是pq数组
    	int temp = pq[i];
    	pq[i] = pq[j];
    	pq[j] = temp;
    }
	public static void main(String[] args) {
		//测试pq
		MaxPQ2 maxpq = new MaxPQ2(5);
		for(int i : maxpq.pq())
			System.out.print(i + " ");
        //测试isEmpty()
		System.out.println(maxpq.isEmpty()); 
		
		//测试isFull()
		int k = 5; //5会填满数组,6会超过数组存储长度
		for(int i = 0; i < k; i++)
		   maxpq.insertSequence(i);
		   //insertSequenceIfNotFull(i); //k = 6 时可用
		for(int i : maxpq.pq())
			System.out.print(i + " ");
		System.out.println(maxpq.isFull());
		//测试size()
		System.out.println(maxpq.size());  
		
		MaxPQ2 maxpq1 = new MaxPQ2(5);
		//测试insert()
		for(int i = 0; i < 5; i++)
		   maxpq1.insert(i);
		for(int i : maxpq1.pq())
			System.out.print(i + " ");
		System.out.println();
		//测试print()方法
		maxpq1.print();
		
		int[] a = {12,9,28,13,4,5,11,1,71};  
		MaxPQ2 maxpq2 = new MaxPQ2(a.length + 1);
		for(int v : a)
			maxpq2.insert(v);
		System.out.println();
		System.out.print("maxpq2 = ");
		maxpq2.print();
		//测试peek()方法
		System.out.println(" ; max = " + maxpq2.peek());
		
		//测试get()方法
				System.out.println("maxpq2.get(8) = " + maxpq2.get(8));
				//System.out.println(maxpq2.get(9)); 
				
		//测试insertAll()方法 
		MaxPQ2 maxpq3 = new MaxPQ2(a.length + 1);
		maxpq3.insertAll(a);
		System.out.print("maxpq3 = ");
		maxpq3.print();
		System.out.println();
		
		
		int len = maxpq3.size();
		System.out.print("maxpq3.delMax() = ");
		for(int i = 0; i < len; i++)
		     System.out.print(maxpq3.delMax() + " "); //并不是按大小顺序输出的 
		
		int[] b = {12,9,28,13,4,5,11,1,71};  
		MaxPQ2 maxpq4 = new MaxPQ2(b.length + 1);
		maxpq4.insertAll(b);
		System.out.println();  
		maxpq4.sort();
		maxpq4.print(); 
	}

}




输出:

0 0 0 0 0 0 true
0 0 1 2 3 4 true
5
0 4 3 1 0 2 
4 3 1 0 2 
maxpq2 = 71 28 12 13 4 5 11 1 9  ; max = 71
maxpq2.get(8) = 9
maxpq3 = 71 28 12 13 4 5 11 1 9 
maxpq3.delMax() = 71 28 13 12 11 9 5 4 1 
1 4 5 9 11 12 13 28 71 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值