文章目录
什么是堆
堆(heap)是计算机科学中的一种特别的树状数据结构。若是满足以下特性,即可称为堆:“给定堆中任意节点 P 和 C,若 P 是 C 的母节点,那么 P 的值会小于等于(或大于等于) C 的值”。
若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);
若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。
在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。
堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;
堆的性质
任意节点小于(或大于)它的所有后裔,
最小元(或最大元)在堆的根上(堆序性)。
堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
常见的堆有二叉堆、斐波那契堆等。
堆节点的访问
通常堆是通过一维数组来实现的。在数组起始位置为0的情形中:
父节点i的左子节点在位置:
父节点i的右子节点在位置:
子节点i的父节点在位置:
若用A表示堆的一维数组,那么A具有两个属性:
若数组的起始位置为1,即树的根节点为A[1];
给定一个节点的下标为i,则他的父节点,左孩子和右孩子 的下标为:
父节点的下标:
左孩子的下标:
右孩子的下标:
节点的高度:该节点到叶节点最长简单路径上边的数目
最大堆的性质:(除了根节点)
最小堆的性质:(除了根节点)
当用数组表示存储了n个元素的堆时,叶节点的下标分别是
最大堆和最小堆的应用
最大堆通常用于堆排序算法
最小堆通常用于构造优先队列
堆排序的概念
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
堆排序是对简单选择排序的改进
简单选择排序是从n个记录中找出一个最小的记录,需要比较n-1次。但是这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多。
堆排序的原理
维基百科:若以升序排序说明,把数组转换成最大堆积(Max-Heap Heap),这是一种满足最大堆积性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]。
重复从最大堆积取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆积维持最大堆积性质。
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。
问题:
- 如何由一个无序序列构建成一个堆?
- 如何在输出堆顶元素后,调整剩余元素成为一个新的堆?
堆排序的特点
具有空间原址性:任何时候都只需要常数个额外空间存储临时数据
维护堆的性质MAX-HEAPIFY(A,i)
MAX-HEAPIFY让A[i]在最大堆中**“逐级下降**”,从而避免了当LEFT[i]和RIGHT[i]都是最大堆,而A[i]小于其孩子的情况。
伪代码
思想:
- 从每次调用MAX-HEAPIFY都选出 A[i], A[LEFT[i]], A[RIGHT[i]] 中的最大值
- 如果最大值就是A[i]那么满足最大堆,否则交换A[i]和A[largest],交换后下标为largest的值为原来的A[i],以该节点为根节点的子树又可能违反最大堆的性质,所以需要对该子树进行递归调用MAX-HEAPIFY.
MAX-HEAPIFY(A,i)
l = LEFT(i)
r = RIGHT(i)
if l ≤ A.heap-size and A[l] > A[i]
largest = l
else
largest = i
if r ≤ A.heap-size and A[r] > A[largest]
largest = r
if largest ≠ i
exchange A[i] with A[largest]
MAX-HEAPIFY(A,largest)
时间复杂度:
建堆BUIL-MAX-HEAP(A)
我们已经知道对于一个堆数组大小为n的堆,该堆的叶子节点的子数组元素为
那么我们采用自底向上的方法,利用前面维护堆的性质的函数MAX-HEAPIFY,将一个大小为n = A.length的数组A[1…n]转为最大堆。
那么这里的自底向上的方法就是从最底层的叶节点开始建堆,
首先每个叶节点可以看作只有一个元素的堆(肯定满足最大堆的性质),
然后对除叶节点以外的其他每个节点都调用一次MAX-HEAPIFY(A,i),用于检查是否符合最大堆的性质*(这里的调用也是从下往上调用,i从floor(n/2)到1,最后到根节点)*
伪代码
时间复杂度:
每次调用MAX-HEAPIFY的时间复杂度为o(lgn)
BUILD-MAX-HEAP需要o(n)次这样的调用
因此总的时间复杂度为:
但是实际上还可以更加精确地计算得到时间复杂度为o(n),也就是说我们可以在线性时间内,将一个无序数组构造成为一个最大堆。
同理我们也可以在线性时间内构造一个最小堆
堆排序算法
HEAPSORT(A)
总的思想就是:
- 首先先将一个无序数组构建成为一个最大堆BUILD-MAX-HEAP(A)
- 然后从堆地最底层开始遍历,将根节点A[1]和树地最后一个A[i]交换,然后将堆的有效元素减一(相当于每次剔除当前堆中的最大的元素,也就是每次剔除当前堆的根节点)exchange A[1] with A[i]; A.heapsize = A.heapsize - 1
- 然后将剩余的元素从新的根节点出发重新维护最大堆的性质MAX-HEAPIFY(A,1),
- 那么最后得到的序列就是一个升序序列
堆排序算法的实现
import java.util.ArrayList;
import java.util.Arrays;
public class HeapSort1 {
private static int heapSize = 0;
/**
* 左孩子的下标
* @param i
* @return
*/
public int leftIndexSearch(int i){
return 2*i+1;
}
/**
* 右孩子的下标
* @param i
* @return
*/
public int rightIndexSearch(int i){
return 2*i+2;
}
/**
* 父节点的下标
* @param i
* @return
*/
public int parentIndexSearch(int i){
return (int) Math.floor((i-1)/2);
}
/**
* 元素交换
* @param arr
* @param i
* @param j
*/
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 维护堆的性质
* @param arr
* @param i
*/
public void maxHeapify(int[] arr,int i){
System.out.println(Arrays.toString(arr));
System.out.println("当前节点:"+i);
int leftIndex = leftIndexSearch(i);
System.out.println("左孩子:"+leftIndex);
int rightIndex = rightIndexSearch(i);
System.out.println("右孩子"+rightIndex);
int heapSize = arr.length;
int largetsIndex = 0;
if(leftIndex <= heapSize -1 && arr[leftIndex] > arr[i]){
System.out.println("leftIndex <= heapSize -1 && arr[leftIndex]:"+arr[leftIndex]+" > arr[i]:"+arr[i]);
largetsIndex = leftIndex;
System.out.println(" largetsIndex = leftIndex;largestIndex:"+largetsIndex);
}else {
System.out.println("不满足leftIndex <= heapSize -1 && arr[leftIndex]> arr[i]:");
largetsIndex = i;
System.out.println(" largetsIndex = i;largestIndex:"+largetsIndex);
}
if(rightIndex <= heapSize - 1 && arr[rightIndex] > arr[largetsIndex]){
System.out.println("rightIndex <= heapSize -1 && arr[rightIndex]:"+arr[rightIndex]+" > arr[largetsIndex]:"+arr[largetsIndex]);
largetsIndex = rightIndex;
System.out.println(" largetsIndex = rightIndex;largestIndex:"+largetsIndex);
}
if(largetsIndex != i){
System.out.println("largetsIndex != i 交换i:"+i+"和largestIndex:"+largetsIndex);
swap(arr,i,largetsIndex);
System.out.println(Arrays.toString(arr));
System.out.println("递归调用maxHeapify");
maxHeapify(arr,largetsIndex);
}
}
/**
* 建堆
* @param arr
*/
public void buildMaxHeap(int[] arr){
for(int i = (arr.length-1)/2; i >= 0; i--){
maxHeapify(arr,i);
}
}
/**
* 堆排序
* @param arr
*/
public ArrayList<Integer> heapSort(int[] arr){
ArrayList<Integer> list = new ArrayList<>();
int heapSize = arr.length;
buildMaxHeap(arr);
System.out.println("构建最大堆:");
System.out.println(Arrays.toString(arr));
for(int i = arr.length - 1; i >= 0; i--){
System.out.println("
"+"最后一个元素i:"+i+"和根节点arr[0]交换");
swap(arr,0,i);
list.add(arr[heapSize-1]);
System.out.println(Arrays.toString(arr));
heapSize = heapSize - 1;
System.out.println("heapSize:"+heapSize);
arr = Arrays.copyOfRange(arr,0,heapSize );
System.out.println("新的arr"+Arrays.toString(arr));
maxHeapify(arr,0);
System.out.println(Arrays.toString(arr));
}
return list;
}
public static void main(String[] args) {
HeapSort1 heapSort1 = new HeapSort1();
int[] arr = { 4,1,3,2,16,9,10,14,8,7 };
System.out.println("排序之前:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("
");
// 堆排序
ArrayList<Integer> list = heapSort1.heapSort(arr);
System.out.println();
System.out.println("排序之后:");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
}
}
排序之前:
4 1 3 2 16 9 10 14 8 7
构建最大堆:
[16, 14, 10, 8, 7, 9, 3, 2, 4, 1]
排序之后:
16 14 10 9 8 7 4 3 2 1
//简洁代码
import java.util.ArrayList;
import java.util.Arrays;
public class HeapSort1 {
private static int heapSize = 0;
/**
* 左孩子的下标
* @param i
* @return
*/
public int leftIndexSearch(int i){
return 2*i+1;
}
/**
* 右孩子的下标
* @param i
* @return
*/
public int rightIndexSearch(int i){
return 2*i+2;
}
/**
* 父节点的下标
* @param i
* @return
*/
public int parentIndexSearch(int i){
return (int) Math.floor((i-1)/2);
}
/**
* 元素交换
* @param arr
* @param i
* @param j
*/
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 维护堆的性质
* @param arr
* @param i
*/
public void maxHeapify(int[] arr,int i){
int leftIndex = leftIndexSearch(i);
int rightIndex = rightIndexSearch(i);
int heapSize = arr.length;
int largetsIndex = 0;
if(leftIndex <= heapSize -1 && arr[leftIndex] > arr[i]){
largetsIndex = leftIndex;
}else {
largetsIndex = i;
}
if(rightIndex <= heapSize - 1 && arr[rightIndex] > arr[largetsIndex]){
largetsIndex = rightIndex;
}
if(largetsIndex != i){
swap(arr,i,largetsIndex);
maxHeapify(arr,largetsIndex);
}
}
/**
* 建堆
* @param arr
*/
public void buildMaxHeap(int[] arr){
for(int i = (arr.length-1)/2; i >= 0; i--){
maxHeapify(arr,i);
}
}
/**
* 堆排序
* @param arr
*/
public ArrayList<Integer> heapSort(int[] arr){
ArrayList<Integer> list = new ArrayList<>();
int heapSize = arr.length;
buildMaxHeap(arr);
System.out.println("构建最大堆:");
System.out.println(Arrays.toString(arr));
for(int i = arr.length - 1; i >= 0; i--){
swap(arr,0,i);
list.add(arr[heapSize-1]);
heapSize = heapSize - 1;
arr = Arrays.copyOfRange(arr,0,heapSize );
maxHeapify(arr,0);
}
return list;
}
public static void main(String[] args) {
HeapSort1 heapSort1 = new HeapSort1();
int[] arr = { 4,1,3,2,16,9,10,14,8,7 };
System.out.println("排序之前:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("
");
// 堆排序
ArrayList<Integer> list = heapSort1.heapSort(arr);
System.out.println();
System.out.println("排序之后:");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
}
}
排序之前:
4 1 3 2 16 9 10 14 8 7
构建最大堆:
[16, 14, 10, 8, 7, 9, 3, 2, 4, 1]
排序之后:
16 14 10 9 8 7 4 3 2 1
优先队列
优先队列:是一种用来维护一组元素构成的集合S的数据结构,其中每个元素都有一个相关的值,称为关键字,一个最大优先队列支持以下操作:
INSERT(S,x):把元素x插入集合S中,等价于S = S
MAXIMUM(S):返回S中具有最大键字的元素
INCREASE-KEY(S,x, k):将元素x的关键字值增加到k
假设A为最大堆
//返回数组A中最大值(根节点)
HEAP-MAXIMUM(A)
return A[1]
//抽取并返回最大值,重新构建最大堆
HEAP-EXTRACT-MAX(A)
if(A.heap-size < 1)
error "heap underflow"
max = A[1]
//将堆中的最后一个元素赋值给A[1]
A[1] = A[A.heap-size]
//减小堆的长度
A.heap-size = A.heap-size - 1
//对新堆重新维护最大堆的性质
MAX-HEAPIFY(A,1)
return max
//将元素i的关键字值增加到key(这里i为下标,key为具体的值)
HEAP-INCREASE-KEY(A,i,key)
if key < A[i]
error "new key is smaller than current key"
A[i] = key
//当i不为根节点并且A[i]的值比父节点A[PARENT(i)]的值还要大
while i > 1 and A[PARENT(i)] < A[i]
//那么就交换A[i]和父节点 A[PARENT(i)]
exchange A[i] with A[PARENT(i)]
//更新i的下标为父节点的下标
i = PARENT(i)
MAX-HEAP-INSERT(A,key)
//增加A的长度
A.heap-size = A.heap-size + 1
//将A的最后一个数设置为负无穷,因为负无穷肯定比key小
A[A.heap-size] = -∞
//调用增加方法将最后一个元素增加到Key
HEAP-INCREASE-KEY(A,A.heap-size,key)
《算法导论第三版》