1 堆的基本概念
堆也叫优先队列,堆是一种特殊的完全二叉树数据结构,堆分为两种,最大堆,最小堆。
最大堆:根节点大于左右两个子节点的完全二叉树
最小堆:根节点小于左右两个子节点的完全二叉树
堆可以用数组来存储,a[i]处存根节点,a[2*i] a[2*i + 1]分别存左子树的根节点,右子树的根节点。i从1开始
所以对于一个堆,结点i,其父结点为a[i/2],左子节点a[2*i],右子节点a[2*i + 1]
2 最大堆
根节点大于左右两个子节点的完全二叉树叫最大堆
1 堆的上浮
对于最大堆来说,如果某个结点比其父结点要大,那么我们应该调整该结点的位置,从而使其满足堆的结构。调整的思路很简单,就是将与其父结点交换。这样一直循环下去,直到所有结点满足堆的结构。这个过程叫做堆的上浮
/**
* 堆的上浮,解决子节点比父结点大的问题
* @param k 节点k上浮
*/
private void swim(int k){
//子节点比父结点大
while (k > 1 && less(k/2,k)){
//交换两个节点
exch(k/2,k);
k = k/2;
}
}
时间复杂度O(logN)
2 堆的下沉
对于最大堆来说,如果我们发现某个结点小于子节点(同时小于两个结点,或者小于一个结点)。这个时候我们也需要调整堆结构。调整思路就是将该结点与子节点中较大的结点做交换,递归下去。直到满足堆的结构。
/**
* 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
* @param k
*/
private void sink(int k){
if (k > count){
return;
}
int i = 2 * k;
if (i > count ){
return;
}
//只有左子节点 i = count;
if ((i + 1 ) > count){
if (less(k,i)){
exch(k,i);
}
return;
}
//两个孩子都大于父节点
if (less(k,i) || less(k,i + 1)){
if (less(i,i+1)){
exch(k,i+1);
sink(i+1);
}else {
exch(k,i);
sink(i);
}
}
}
时间复杂度O(logN)
3 堆的插入
只要将插入的数放在数组末尾,然后上浮这个结点即可
/**
* 插入一个元素,并上浮
* @param t
*/
public void insert(T t){
pq[++count] = t;
swimBetter(count);
}
时间复杂度O(logN)
4 堆的删除
这里删除指的是删除最大的元素,即删除顶点元素。删除思路很简单:获取数组第一个元素,然后将第一个元素与最后一个元素交换,并下沉第一个元素即可。
/**
* 删除最大的元素
* @return
*/
public T delMax(){
T t = (T) pq[1];
//删除最大的,然后将末尾元素交换,并下沉
exch(1,count);
pq[count] = null;
count--;
sinkBetter(1);
return t;
}
时间复杂度O(logN)
5最大堆实现
接下来我们看堆的完整代码:
**
* @author Created by qiyei2015 on 2018/3/25.
* @version: 1.0
* @email: 1273482124@qq.com
* @description:
*/
public class BaseHeap<T extends Comparable<T>> {
/**
* 长度需N + 1
*/
protected Comparable[] pq;
/**
* 容量 pq[1....N]
*/
protected int N;
/**
* 堆中元素个数
*/
protected int count;
/**
* 构造方法
*/
public BaseHeap() {
pq = new Comparable[0];
}
/**
* 创建一个初始容量为max的堆
* @param max
*/
public BaseHeap(int max) {
pq = new Comparable[max + 1];
count = 0;
N = max;
}
/**
* 数组创建
* @param array
*/
public BaseHeap(Comparable[] array) {
this.pq = new Comparable[array.length + 1];
System.arraycopy(array,0,pq,1,array.length);
N = array.length;
count = N;
}
/**
* 时候为null
* @return
*/
public boolean isEmpty(){
return count == 0;
}
/**
* 返回堆中元素个数
* @return
*/
public int size(){
return count;
}
/**
* 比较 i < j
* @param i
* @param j
* @return
*/
protected boolean less(int i ,int j){
return pq[i].compareTo(pq[j]) < 0;
}
/**
* 交换两个数
* @param i
* @param j
*/
protected void exch(int i,int j){
Comparable temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
}
}
/**
* @author Created by qiyei2015 on 2018/3/25.
* @version: 1.0
* @email: 1273482124@qq.com
* @description: 最大堆
*/
public class MaxPQ<T extends Comparable<T>> extends BaseHeap{
/**
* 无参构造方法
*/
public MaxPQ() {
super();
}
/**
* 创建一个初始容量为max的堆
* @param max
*/
public MaxPQ(int max) {
super(max);
}
/**
* 用数组创建堆,时间复杂度 O(N)
* @param a
*/
public MaxPQ(Comparable[] a){
super(a);
//最后一个父结点是 count / 2 [count/2...1]这个结点区间的结点都下沉,就是堆了
for (int i = count/2; i >= 1 ; i--){
sinkBetter(i);
}
}
/**
* 插入一个元素,并上浮
* @param t
*/
public void insert(T t){
pq[++count] = t;
swimBetter(count);
}
/**
* 删除最大的元素
* @return
*/
public T delMax(){
T t = (T) pq[1];
//删除最大的,然后将末尾元素交换,并下沉
exch(1,count);
pq[count] = null;
count--;
sinkBetter(1);
return t;
}
/**
* 堆的上浮,解决子节点比父结点大的问题
* @param k 节点k上浮
*/
private void swim(int k){
//子节点比父结点大
while (k > 1 && less(k/2,k)){
//交换两个节点
exch(k/2,k);
k = k/2;
}
}
/**
* 堆的上浮,解决子节点比父结点大的问题,少交换,优化堆的上浮过程
* @param k 节点k上浮
*/
private void swimBetter(int k){
Comparable temp = pq[k];
//子节点比父结点大
while (k > 1 && less(k/2,k)){
//父结点移到子节点 子节点暂存 不用每次都去新建一个临时变量来交换
pq[k] = pq[k/2];
pq[k/2] = temp;
k = k/2;
}
}
/**
* 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
* @param k
*/
private void sink(int k){
if (k > count){
return;
}
int i = 2 * k;
if (i > count ){
return;
}
//只有左子节点 i = count;
if ((i + 1 ) > count){
if (less(k,i)){
exch(k,i);
}
return;
}
//两个孩子都大于父节点
if (less(k,i) || less(k,i + 1)){
if (less(i,i+1)){
exch(k,i+1);
sink(i+1);
}else {
exch(k,i);
sink(i);
}
}
// //判断有左孩子,有孩子就行
// while (2 * k <= N){
// int j = 2 * k; //此轮循环中 k 与j交换
// if ((j +1) <= N && less(j,j+1)){
// j++; //更新为右孩子
// }
// //父结点大于子节点
// if (!less(k,j)){
// break;
// }
// exch(k,j);
// k = j; //更新k的位置
// }
}
/**
* 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
* @param k
*/
private void sinkBetter(int k) {
Comparable temp = pq[k];
//判断有左孩子,有孩子就行
while (2 * k <= count) {
int j = 2 * k; //此轮循环中 k 与j交换
if ((j + 1) <= count && less(j, j + 1)) {
j++; //更新为右孩子
}
//父结点大于子节点
if (!less(k, j)) {
break;
}
//将子节点移到父结点,父结点移到子节点 不用每次都去新建一个临时变量来交换
pq[k] = pq[j];
pq[j] = temp;
k = j; //更新k的位置
}
}
}
3 最小堆
与最大堆类似,我们只需要在堆的上浮和下沉改变一下条件,即可创建最小堆
1 最小堆上浮
结点比父节点小,该结点与父节点交换
/**
* 堆的上浮,如果子节点比父节点小,就交换
* @param k 节点k上浮
*/
private void swim(int k){
//子节点比父结点大
while (k > 1 && less(k,k/2)){
//交换两个节点
exch(k/2,k);
k = k/2;
}
}
时间复杂度O(logN)
2 最小堆下沉
父结点大于子节点,该结点与子结点中较小的节点交换
/**
* 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换,并下沉子节点
* @param k
*/
private void sink(int k){
if (k > count){
return;
}
int i = 2 * k;
if (i > count ){
return;
}
//只有左子节点 i = count;
if ((i+1) > count){
if (less(i,k)){
exch(k,i);
}
return;
}
//父节点与较小的子节点交换
if (less(i,k) || less(i+1,k)){
//将父节点与较小的节点交换
if (less(i,i+1)){
exch(k,i);
sink(i);
}else {
exch(k,i+1);
sink(i+1);
}
}
}
时间复杂度O(logN)
3 最小堆实现
完整最小堆代码:
package com.qiyei.heap;
/**
* @author Created by qiyei2015 on 2018/4/16.
* @version: 1.0
* @email: 1273482124@qq.com
* @description: 最小堆
*/
public class MinPQ<T extends Comparable<T>> extends BaseHeap {
/**
* 无参构造方法
*/
public MinPQ() {
super();
}
/**
* 创建一个初始容量为max的堆
* @param n
*/
public MinPQ(int n) {
super(n);
}
/**
* 用数组创建堆,时间复杂度 O(N)
* @param a
*/
public MinPQ(Comparable[] a){
super(a);
for (int i = count/2; i >= 1 ; i--){
sinkBetter(i);
}
}
/**
* 插入一个元素
* @param t
*/
public void insert(T t){
pq[++count] = t;
swimBetter(count);
}
/**
* 删除最小的元素
* @return
*/
public T delMin(){
T t = (T) pq[1];
exch(1,count);
pq[count] = null;
count--;
sinkBetter(1);
return t;
}
/**
* 堆的上浮,如果子节点比父节点小,就交换
* @param k 节点k上浮
*/
private void swim(int k){
//子节点比父结点大
while (k > 1 && less(k,k/2)){
//交换两个节点
exch(k/2,k);
k = k/2;
}
}
/**
* 堆的上浮,如果子节点比父节点小,就交换。优化堆的上浮过程
* @param k 节点k上浮
*/
private void swimBetter(int k){
Comparable temp = pq[k];
//子节点比父结点大
while (k > 1 && less(k,k/2)){
//父结点移到子节点 子节点暂存 不用每次都去新建一个临时变量来交换
pq[k] = pq[k/2];
pq[k/2] = temp;
k = k/2;
}
}
/**
* 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换,并下沉子节点
* @param k
*/
private void sink(int k){
if (k > count){
return;
}
int i = 2 * k;
if (i > count ){
return;
}
//只有左子节点 i = count;
if ((i+1) > count){
if (less(i,k)){
exch(k,i);
}
return;
}
//父节点与较小的子节点交换
if (less(i,k) || less(i+1,k)){
//将父节点与较小的节点交换
if (less(i,i+1)){
exch(k,i);
sink(i);
}else {
exch(k,i+1);
sink(i+1);
}
}
}
/**
* 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换
* @param k
*/
private void sinkBetter(int k) {
Comparable temp = pq[k];
//判断有左孩子,有孩子就行
while (2 * k <= count) {
int j = 2 * k; //此轮循环中 k 与j交换
if ((j + 1) <= count && less(j+1, j)) {
j++; //更新为右孩子
}
//父结点大于子节点
if (!less(j, k)) {
break;
}
//将子节点移到父结点,父结点移到子节点 不用每次都去新建一个临时变量来交换
pq[k] = pq[j];
pq[j] = temp;
k = j; //更新k的位置
}
}
}
4 堆排序
有了最大最小堆,堆排序变得非常简单。首先根据数组构造堆,然后依次取堆顶元素即可,对于最大堆,需要把倒序赋值数组。
堆排序的时间复杂度是O(nlogn)
/**
* 堆排序
* @param array
*/
@Override
public void sort(Comparable[] array) {
//时间复杂度O(n)
MaxPQ maxPQ = new MaxPQ(array);
for (int i = array.length - 1 ; i >= 0 ; i--){
//时间复杂度O(logn)
array[i] = maxPQ.delMax();
}
}
最后:
源代码github https://github.com/qiyei2015/Algorithms heap部分