堆的原理与实现以及排序

1.堆的构建

1.1 堆的定义
堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。
堆的特性:
1. 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不 是满的,那么要求左满右不满。
2. 它通常用数组来实现。
具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置 1 ,它的子结点在位置 2 3 ,而子结点的子 结点则分别在位置4,5,6 7 ,以此类推。

 

 

如果一个结点的位置为 k ,则它的父结点的位置为 [k/2], 而它的两个子结点的位置则分别为 2k 2k+1 。这样,在不 使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k] 向上一层,就令 k 等于 k/2, 向下一层就 令k 等于 2k 2k+1
3. 每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个 子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。
1.3 堆的实现
1.4.1 insert 插入方法的实现
堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只 能往数组中从索引0 处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等 于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些 方法让刚才插入的这个数据放入到合适的位置。

 

 

 

1.4.2 delMax 删除最大元素方法的实现
由堆的特性我们可以知道,索引 1 处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要 有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1 处,充当根结点,但是它有可能不满足 堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。

 

 

 

 

所以,当删除掉最大元素后,只需要将最后一个元素放到索引 1 处,并不断的拿着当前结点 a[k] 与它的子结点 a[2k] 和a[2k+1] 中的较大者交换位置,即可完成堆的有序调整。

 

代码实现:

 

public class Heap<T extends Comparable<T>>{
	//存储堆中的元素
	private T[] items;
	//记录堆中元素的个数
	private int N;
//	构造方法
	public Heap(int capacity) {
		this.N=0;
		this.items=(T[]) new Comparable[capacity];
	}
//	判断堆中索引i处的元素是否小于索引j处的元素
	private boolean less(int i,int j) {
		return items[i].compareTo(items[j])<0;
	}
//	交换元素
	private void exch(int i,int j) {
		T tmp=items[i];
		items[i]=items[j];
		items[j]=tmp;
	}
//	往堆中插入一个元素
	public void insert(T t) {
		items[++N]=t;
		swim(N);//让元素t上浮
	}
//	使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
	private void swim(int k) {
		//如果已经到了根结点,就不需要循环了
		while(k>1) {
			//比较当前结点和其父结点
			if(less(k/2,k)) {
				//父结点小于当前结点,需要交换
				exch(k/2,k);
			}
			k=k/2;
		}
	}
	//删除堆中最大的元素,并返回这个最大元素
	public T delMax() {
		T max=items[1];
		//比较当前结点和其父结点
		exch(1,N);
		//删除最后位置上的元素
		items[N]=null;
		//个数-1
		N--;
		sink(1);//让根结点下沉
		return max;
	}
	//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
	private void sink(int k) {
		//如果当前已经是最底层了,就不需要循环了
		while(2*k<=N) {
			//找到子结点中的较大者
			int max;
			//若右子结点存在,找到子结点最大值
			if(2*k+1<=N) {
				if(less(2*k,2*k+1)) {
					max=2*k+1;
				}else {
					max=2*k;
				}
			}else {//右结点不存在,直接将左结点点赋值给最大值
				max=2*k;
			}
			//比较当前结点和子结点中的较大者,如果当前结点不小,则结束循环
			if(!less(k,max)) {
				break;
			}
			//当前结点小,则交换,
			exch(k,max);
			k=max;
		}
	}
}

 2.堆排序

堆排序过程
对构造好的堆,我们只需要做类似于堆的删除操作,就可以完成排序。
1. 将堆顶元素和堆中最后一个元素交换位置;
2. 通过对堆顶元素下沉调整堆,把最大的元素放到堆顶 ( 此时最后一个元素不参与堆的调整,因为最大的数据已经到 了数组的最右边)
3. 重复 1~2 步骤,直到堆中剩最后一个元素。

 

 

 

 

 

 

 

 代码实现:

public class HeapSort {
	private static boolean less(Comparable[] head,int i,int j) {
		return head[i].compareTo(head[j])<0;
	}
	private static void exch(Comparable[] head,int i,int j) {
		Comparable tmp=head[i];
		head[i]=head[j];
		head[j]=tmp;
	}
	//根据原数组source,构造出堆heap
	private static void createHeap(Comparable[] source,Comparable[] head) {
		//1.把source中的数据拷贝到heap中,从heap的1索引处开始填充
		System.arraycopy(source, 0, head, 1, source.length);
		//2.从heap索引的一半处开始倒叙遍历,对得到的每一个元素做下沉操作
		for(int i=head.length/2;i>0;i--) {
			sink(head,i,head.length-1);
		}
	}
	//对source数组中的数据从小到大排序
	public static void sort(Comparable[] source) {
		//1.创建一个比原数组大1的数组
		Comparable[] head=new Comparable[source.length+1];
		//2.构造堆
		createHeap(source,head);
		//3.堆排序
		//3.1定义一个变量,记录heap中未排序的所有元素中最大的索引
		int N=head.length-1;
		
		while(N!=1) {
			//3.2交换heap中索引1处的元素和N处的元素
			exch(head,1,N);
			N--;
			//3.3对索引1处的元素在0~N范围内做下沉操作
			sink(head,1,N);
		}
		//4.heap中的数据已经有序,拷贝到source中
		System.arraycopy(head, 1, source, 0, source.length);
	}
	//在heap堆中,对target处的元素做下沉,范围是0~range
	private static void sink(Comparable[] head,int target,int range) {
		while(2*target<=range) {
			int max;
			if(2*target+1<=range) {
				if(less(head,2*target,2*target+1)) {
					max=2*target+1;
				}else {
					max=2*target;
				}
			}else {
				max=2*target;
			}
			if(!less(head,target,max)) {
				break;
			}
			exch(head,target,max);
			target=max;
		}
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luck&Strive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值