堆的定义(Heap)
堆是一个特殊的数据结构,可以看作是用数组实现的二叉树。
它是一个完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不 是满的,那么要求左满右不满。
数组实现的具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。
堆的每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟二叉查找树是有区别的。
怎么去确定结点位置呢?如果一个结点的位置为k,则它的父结点的位置为k/2,而它的两个子结点的位置则分别为2k和2k+1。这样在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。
堆与树之间的区别
结点的排序:在二叉查找树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此,在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大,就像是从大到小或者从小排序的一个数组。
内存占用:普通的树所占用的内存会更多,因为它需要指针,指向左右子结点,而堆不用,只需要储存一个数组就可以了,因为它规定了子父结点之间的关系。
平衡以及作用:普通的树更强调平衡性,只有在平衡的时候,树的快速查找才能体现出来,而堆并不需要,它更多的是利用堆排序来进行增删,它们的职能是不一样的。
堆的API设计(最大堆)
public class Heap<T extends Comparable<T>> {
private T[] items;
private int N;
public Heap(int capacity) {
//capacity+1是因为索引从1开始储存数据
this.items = (T[]) new Comparable[capacity + 1];
this.N = 0;
}
// 判断索引i处的值是否小于索引j处的值
private boolean less(int i,int j){
return items[i].compareTo(items[j])<0;
}
// 交换i处和j处的值
private void exchange(int i,int j){
T temp = items[i];
items[i]=items[j];
items[j]=temp;
}
// 往堆中插入元素
public void insert(T t){
items[++N]=t;
//添加之后进行上浮排序
swim(N);
}
// 删除堆中最大的元素,并返回这个最大元素
public T delMax(){
//就是将第一个元素和最后一个元素交换,再让最后索引为null,最后下沉第一个元素
T max = items[1];
exchange(1,N);
items[N] = null;
N--;
// 通过下沉算法调整堆的排序
sink(1);
return max;
}
// 上浮算法,使得添加的元素在合适的位置
public void swim(int k){
// 通过循环不断比较与父结点的大小,若比父结点大,就换位置上浮
while (k > 1) {
if (less(k/2,k)){
exchange(k/2,k);
}
k=k/2;
}
}
// 下沉算法
private void sink(int k){
// 通过循环不断比较当前结点k与其左子结点2k和右子结点2k+1中的较大值
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;
}
exchange(k,max);
k=max;
}
}
}
堆排序
排序原理:堆排序分为两个过程,一个是堆有序的构建,二是堆的排序。
- 首先在堆索引的一半处开始倒叙遍历,对得到的每一个元素做下沉操作,得到一个有序的完全二叉树(此时只是树形结构上的有序,而不是堆的有序)。
- 再通过将首结点与末结点交换,在排除此时末结点的范围内对首结点进行下沉,往复筛选全部元素的方法,实现堆排序。
堆排序的API设计
public class HeapSort {
//对source数组中的数据从小到大排序
public static void sort(Comparable[] source) {
//创建一个比原数组大1的数组
Comparable[] heap = new Comparable[source.length + 1];
//构造堆
createHeap(source,heap);
//堆排序
//定义一个变量,记录heap中未排序的所有元素中最大的索引
int N = heap.length-1;
while(N!=1){
//交换heap中索引1处的元素和N处的元素
exchange(heap,1,N);
N--;
//对索引1处的元素在0~N范围内做下沉操作
sink(heap,1,N);
}
//heap中的数据已经有序,拷贝到source中
System.arraycopy(heap,1,source,0,source.length);
}
// 根据原数组source,构造出堆heap
private static void createHeap(Comparable[] source,Comparable[] heap){
System.arraycopy(source,0,heap,1,source.length);
//2.从heap索引的一半处开始倒叙遍历,对得到的每一个元素做下沉操作
for (int i=(heap.length)/2;i>0;i--){
sink(heap,i,heap.length-1);
}
}
//判断heap堆中索引i处的元素是否小于索引j处的元素
private static boolean less(Comparable[] heap,int i,int j){
return heap[i].compareTo(heap[j])<0;
}
//交换heap堆中i索引和j索引处的值
private static void exchange(Comparable[] heap,int i, int j){
Comparable tem= heap[i];
heap[i]=heap[j];
heap[j]=tem;
}
// 在堆heap中对目标元素进行下沉,范围为range
private static void sink(Comparable[] heap,int target,int range){
//有子结点
while (2 * target <= range) {
int max=0;
//有两个子结点
if (2 * target + 1 <= range) {
if (less(heap,2*target,2*target+1)){
max=2*target+1;
}else {
max=2*target;
}
}else {
max=2*target;
}
if (less(heap,target,max)){
exchange(heap,target,max);
}
// 为了继续下一个循环
target=max;
}
}
}