堆
1、概述
1.1 定义
堆是计算机科学中一种特殊的数据结构的统称,堆通常被看做是一棵完全二叉树的数组对象。
1.2 特性
1、堆是完全二叉树,即除了树的最后一层结点不需要满的,其他的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。如图
2、通常是利用数组来实现的,具体方法就是将二叉树的结点按照层级顺序放入到数组中,根结点在位置1处,它的两个子结点位置为2,3,其他结点的规则如下,如果一个结点的位置为k,那么它的父结点的位置为【k/2】,而它的两个子节点的位置则分别为【2k】和【2k+1】。这样在不使用指针的情况下,也可以通过计算数组的索引在树中移动。如图
3、每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,每个结点的值小于或等于其左右孩子结点的值,称为小顶堆。
2、堆的基本实现
这里实现的是大顶堆。
主要方法:堆的插入与删除方法,上浮和下沉算法
(1)上浮算法(rise)主要原理:循环比较当前结点和其父结点的值,当当前结点的值大于父结点的值时交换两个结点的位置。
(2)下沉算法(sink)主要原理:通过循环不断比较当前k结点和其左子结点2k以及右子结点2k+1中的较大值的元素大小,若当前结点小与其两者间的最大值,则交换当前结点与其子节点中最大值的位置。
(3)插入方法(insert):注意堆的根结点位于数组的1位置处,而非0位置处, 此外,每次插入一个值时通过上浮算法来确定新插入的值的位置。
(4)删除最大值(delmax):这里采用的主要原理是先交换最大值与最大索引处的值的位置,然后删除交换位置后的最大索引处的值,期间通过下沉算法重新排列了要删除的最大值的位置。
代码实现
public class Heap <T extends Comparable<T>>{
//定义一个数组来存储堆中的元素
private T[] items;
//记录堆中元素个数
private int Num;
public Heap(int capacity){
this.items = (T []) new Comparable[capacity+1]; //因为T继承了Comparable接口,所以这里是new Comparable类型数组
this.Num = 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) {
//因为数组数量Num初始化为0,而这里堆中第一个元素item[0]是不存放任何数值的,所以使用++Num跳过了第一个items【0】
items[++Num] = t;
rise(Num);
}
//--------------------------------------------------------------------
//上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void rise(int k){
//通过循环不断比较当前结点的值和其父结点的值,如果当前结点大于父结点就交换两者位置
while(k>1){
//比较当前结点和其父结点
if(less(k/2,k)){
exchange(k/2,k);
k = k/2;
}else{
break;
}
}
}
//--------------------------------------------------------------------
//删除堆中最大的元素,并返回这个最大元素
public T delMax(){
T max = items[1];
//交换索引1处元素和最大索引处的元素,让完全二叉树最右侧的元素变为临时根结点
exchange(1,Num);
//删除交换操作后的最大索引处的元素
items[Num] = null;
//元素个数减1
Num--;
//通过下沉算法,重新排列堆
sink(1);
return max;
}
//--------------------------------------------------------------------
//下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k){
//通过循环不断比较当前k结点和其左子结点2*k以及右子结点2*k+1处的较大值的元素大小
//当前结点小,则交换与子节点中最大值的位置
while(2*k<=Num){
//获取当前结点的子结点的最大结点
int max;
if(2*k+1<=Num){ //判断当前结点是否有右子结点
if(2*k<2*k+1){
max = 2*k+1;
}else{
max = 2*k;
}
}else{
max = 2*k;
}
//比较当前结点和较大结点的值
if(!less(k,max)){
break;
}
//交换k索引的值和max索引处的值
exchange(k,max);
//交换k的值
k = max;
}
}
}
测试代码
public class HeapTest {
public static void main(String[] args) {
Heap<String> heap = new Heap<String>(10);
heap.insert("A");
heap.insert("B");
heap.insert("C");
heap.insert("D");
heap.insert("E");
heap.insert("F");
heap.insert("G");
//循环删除最大值
String result = null;
while((result = heap.delMax()) != null){
System.out.print(result + " ");
}
}
}
结果
G F E C B D A
3、堆排序
基本思想
- 将待排序序列构造成一个大顶堆(父结点大于两个子结点)
- 此时,整个序列的最大值就是堆顶的根结点
- 将其与末尾元素进行交换,此时末尾就为最大值
- 将末尾这个最大值用排除堆之外,然后将剩余n-1个元素重新构成一个堆,这样就会得到n个元素的次小值,反复执行,按顺序排除出堆的元素就能组成一个有序队列了。
问题描述:给定一个数组,使用堆排序从小到大排序好
String[] s = {“S”,“O”,“R”,“T”,“E”,“X”,“A”,“M”,“P”,“L”,“E”};
主要方法及其原理:
(1)根据原数组构造出一个堆(createHeap(Comparable[] source, Comparable[] heap)),返回值为空,参数分别是要传入的数组和堆的数组,将传入的数组复制到堆数组中,并用下沉算法调整堆中的元素
(2)下沉算法(sink(Comparable [] heap,int target,int range)),返回值空,target是要下沉调整的元素,range是调整的范围,可以看成是不断变换的堆数组的长度
(3)排序方法(sort(Comparable[] cource)),参数是要传入的数组,每次循环将最大值和堆中最大索引处的值交换,并且排除最大索引处的值,使其不再参与下沉,排除后的值仍在堆中并且处于的数组中的位置没有变,直到循环结束,排除后的值就是按照从小到大的顺序排序,最后再将堆复制到原数组中。
具体操作看代码
代码实现
public class HeapSort {
//判断heap堆中索引i处的元素是否小于索引j处的元素
private static boolean less(Comparable[] heap, int i, int j){
return heap[i].compareTo(heap[j])<0;
}
//-----------------------------------------------------------------
//交换heap堆中两指定索引处的值
private static void exchange(Comparable[] heap, int i, int j){
Comparable temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
//-----------------------------------------------------------------
//根据原数组source构造出堆heap,对堆中元素做下沉跳整
private static void createHeap(Comparable[] source, Comparable[] heap){
//把source中的元素拷贝到heap中,heap中元素形成一个无需堆
System.arraycopy(source,0,heap,1,source.length);
//对堆中的元素做下沉调整(从长度的一半开始往索引1处开始扫描)
for(int i = (heap.length)/2; i>0; i--){
sink(heap,i, heap.length-1);
}
}
//-----------------------------------------------------------------
//对source数组中的数据从小到大排序
public static void sort(Comparable[] source){
//构建堆
Comparable[] heap = new Comparable[source.length+1];
createHeap(source,heap);
//定义一个变量记录为排序的元素中最大的索引
int N = heap.length-1;
//通过循环交换1索引处和排序的元素中最大的索引处的元素
while(N!=1){
//交换元素
exchange(heap,1,N);
//排序交换后最大元素所在的索引,让它不再参与下沉
N--;
//对索引1处的元素进行堆的下沉调整
sink(heap,1,N);
}
//
System.arraycopy(heap,1,source,0,source.length);
}
//-----------------------------------------------------------------
//在heap堆中,对target处的元素做下沉,让小的往下走,范围是0~range,
private static void sink(Comparable[] heap, int target, int range){
while(2*target<=range){
//找出当前结点的较大的子节点
int max;
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;
}
//2、比较当前结点的值和较大子结点的值
if (!less(heap, target, max)) {
break;
}
exchange(heap,target,max);
target = max;
}
}
}
测试代码
public static void main(String[] args) {
String[] s = {"S","O","R","T","E","X","A","M","P","L","E"};
HeapSort.sort(s);
System.out.println(Arrays.toString(s));
}
结果:
[A, E, E, L, M, O, P, R, S, T, X]