我所知道的数据结构之堆

作者前言

大家好,我是阿濠,今篇内容跟大家分享的是数据结构之堆,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.

一、什么是堆

堆是一种特殊的树

只要满足以下两个条件,就可以称这颗树为堆

1.堆是一颗完全二叉树

2.每个节点必须(大于等于)或者(小于等于)其子树中每个节点的值

clipboard.png

二叉堆本质上是一种完全二叉树

分为两个类型:大顶堆与小顶堆

大顶堆

什么是最大堆呢?
最大堆任何一个父节点的值,都大于等于它左右孩子节点的值。

image

小顶堆

什么是最小堆呢?
最小堆任何一个父节点的值,都小于等于它左右孩子节点的值。

image

二叉堆的根节点叫做堆顶

最大堆和最小堆的特点决定了:
在最大堆的堆顶是整个堆中的最大元素;在最小堆的堆顶是整个堆中的最小元素

二、如何构建堆?

image

堆是一颗完全二叉树:
1.比较适合用数组存放,因为用数组存放不需要存储左右子节点的指针。
2.非常节省空间,通过下标就可以找到一个节点的左右子节点和父节点。
3.下标i的节点的左子节点的下标是i*2,右子节点的下标为i*2+1,父节点的下标为i/2
这是根节点存放在下标1的位置时的规律。

clipboard.png

把一个无序的完全二叉树调整为二叉堆,本质上就是让所有非叶子节点依次下沉

举一个无序完全二叉树的例子

我们从最后一个非叶子节点10开始。如果大于它左右孩子中最小的一个,则节点交换。

image

接下来轮到节点3,如果节点3大于它左右孩子中最小的一个,则节点3下沉。

image

接下来轮到节点1,如果节点1大于它左右孩子中最小的一个,则节点1下沉。但事实上节点1小于它的左右孩子,所以不用改变。

接下来轮到节点7,如果节点7大于它左右孩子中最小的一个,则节点7下沉。

image

节点7继续比较,继续下沉。

image

这样一来,一颗无序的完全二叉树就构建成了一个最小堆。1就是最小的

三、堆的插入和删除

我们对堆做插入删除或操作,可能会破坏堆的两大特性,我们就需要进行调整,使其重新满足堆的两大特性,这个过程,我们称之为堆化(heapify)

堆化有两种:从下往上堆化和从上往下堆化。

往堆中插入一个元素,我们可以用从下往上堆化

从下开始顺着父节点路径往上走,进行比较,然后交换。

往堆中插入元素22,如下图:

clipboard.png

往堆中删除堆顶元素,我们可以采用从上往下堆化

为了避免删除后,堆是不完全二叉树,我们将堆顶元素删除后,要将最后一个元素放到堆顶,然后在进行堆化

例如:

我们想删除堆顶元素33,就需要删除33后,将最后一个元素放在堆顶,然后进行堆化。

clipboard.png

四、代码实现

image

在撸代码之前,我们还需要明确一点:

二叉堆虽然是一颗完全二叉树,但它的存储方式并不是链式存储,而是顺序存储。换句话说,二叉堆的所有节点都存储在数组当中。

640?wx_fmt=png

我们准备将上面的例子中的树这样存储:

[1,3,2,6,5,7,8,9,10]

640?wx_fmt=jpeg

/**
 * Java数组实现堆结构 
 * 下标格式如下 
 *           0 
 *         1   2 
 *        3 4 5 6
 */
public class MyHeap {
    // find the index of left children
    // 下标为i的节点的左侧子节点的下标
    public static int left(int i) {
        return (i + 1) * 2 - 1;
    }
    // find the index of right children
    // 下标为i的节点的右侧子节点的下标
    public static int right(int i) {
        return (i + 1) * 2;
    }
    // find the index of parent
    // 下标为i的节点的父节点的下标
    public static int parent(int i) {
        return (i - 1) / 2;
    }
    // build the max heap
    // 构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

    public static void keepMaxHeap(int[] array, int k, int length) {
        int l = left(k);// k节点的左子节点
        int r = right(k);// k节点的右子节点
        int largest = k;// 假定k节点是k-l-r中的最大值节点
        if(l<length && array[l]>array[largest]){// 左子节点>最大值节点
            largest = l;
        }
        if(r<length && array[r]>array[largest]){// 右子节点>最大值节点
            largest = r;
        }
        if(largest != k){// 发现最大值节点不是k节点时 是左或右子节点 交换
            swap(array,k,largest);
            //int temp = array[k];
            //array[k]= array[largest];// 新k节点为最大值节点
            //array[largest] = temp;// 左或右子节点为原k节点值
            // 迭代左或右子节点上的原k节点值 
            // 此时这个节点往下判断左右子节点
            keepMaxHeap(array,largest,length);
        }
    }
    // heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        for(int i=1;i<=length;i++){
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
    }
    // swap the value of index i and j in the array
    public static void swap(int[] array,int i,int j){
        int temp = array[i];
        array[i]= array[j];
        array[j] = temp;
    }
    // print the array
    public static void printArray(int[] array){
        for(int i : array){
            System.out.print(i+" ");
        }
        System.out.println("");
    }
}
//堆排序的Java实现 作者 yangdau
public class HeapTest {
    public static void main(String[] args) {
        int[] array = {1,3,2,6,5,7,8,9,10};
        // 从小到大排序使用大顶堆
        MyHeap.buildMaxHeap(array,array.length);
        System.out.println("生成大顶堆后的数据是: ");
        //输出数组
        MyHeap.printArray(array);
        
        MyHeap.heapSort(array);
        System.out.println("使用堆排序后的数据是: ");
        //输出数组
        MyHeap.printArray(array);
    }
}

图片.png

// 构建某一数组的最大堆结构
public static void buildMaxHeap(int[] array,int length){
    // the last node index is array.length-1
    // and his parent index is (array.length-1-1)/2
    for(int i=parent(length-1);i>=0;i--){
       //传入length=9,parent返回(9-1-1/2)=3
        keepMaxHeap(array,i,length);
    }
}
将从数组6开始,它的下标是3
public static void keepMaxHeap(int[] array, int k, int length) {
    int l = left(k);// k节点的左子节点
    int r = right(k);// k节点的右子节点
    int largest = k;// 假定k节点是k-l-r中的最大值节点
    if(l<length && array[l]>array[largest]){// 左子节点>最大值节点
        largest = l;
    }
    if(r<length && array[r]>array[largest]){// 右子节点>最大值节点
        largest = r;
    }
    if(largest != k){// 发现最大值节点不是k节点时 是左或右子节点 交换
        swap(array,k,largest);
        //int temp = array[k];
        //array[k]= array[largest];// 新k节点为最大值节点
        //array[largest] = temp;// 左或右子节点为原k节点值
        // 迭代左或右子节点上的原k节点值 
        // 此时这个节点往下判断左右子节点
        keepMaxHeap(array,largest,length);
    }
}
  1. 进行判断如果l的下标小于长度并且该下标的值大于k,则最大值的值为l
  2. 进行判断如果r的下标小于长度并且该下标的值大于k,则最大值的值为r
  3. 进行判断如果最大值的下标k不是本身k时,进行左或右节点交换,进行确定最大值

数组6的左节点l下标7,右节点r下标8,假设最大值就是6本身k下标3

进行判断l的下标小于长度9并且9大于6,则最大值的值为l 进行判断r的下标小于长度9并且10的值大于9,则最大值的值为r 进行判断最大值的下标k不是3,右节点交换,进行再确定最大值

图片.png

这时再进行确定最大值,此时最大值k的下标是8,它的左节点l下标是17,右节点r下标是18,进行判断都不成立,本身是最大值,所以数组则i--=2,它的下标2

获取2的左节点l下标5,右节点r下标6,假设最大值就是2本身k下标2,

进行判断l的下标小于长度9并且7大于k,则最大值的值为l 进行判断r的下标小于长度9并且8的值大于7,则最大值的值为r 进行判断最大值的下标k不是2,右节点交换,进行确定最大值

图片.png

这时再进行确定最大值,此时最大值k的下标是6,它的左节点l下标是14,右节点r下标是15,进行判断都不成立,本身是最大值,所以数组则i--=1,它的下标1

获取3的左节点l下标3,右节点r下标4,假设最大值就是3本身k下标1,

进行判断l的下标小于长度并且10大于k,则最大值的值为l 进行判断r的下标小于长度并且5的值小于10,则最大值的值为l 进行判断最大值的下标k不是1,左节点交换,进行确定最大值

图片.png

这时再进行确定最大值,此时最大值k的下标是3,它的左节点l下标是7,右节点r下标是8,进行判断左节点小于长度9并且9大于3则最大值为l,进行判断r的下标小于长度并且6小于9,进行左节点交换则最大值为l,判断最大值下标不是本身3,再次确认最大值。

图片.png

这时再进行确定最大值,此时最大值k的下标是7,它的左节点l是15,右节点r是16,进行判断都不成立,本身是最大值,所以数组则i--=0,它的下标0

获取1的左节点10下标1,右节点r下标2,假设最大值就是1本身k下标0

进行判断l的下标小于长度并且10大于k,则最大值的值为l 进行判断r的下标小于长度并且8的值小于10,则最大值的值为l 进行判断最大值的下标k不是0,左节点交换,进行确定最大值

图片.png

这时再进行确定最大值,此时最大值k的下标是1,它的左节点l下标是3,右节点r下标是4,进行判断左节点小于长度并且9大于1则最大值为l,进行判断r的下标小于长度并且5小于9,进行左节点交换则最大值为l,判断最大值下标不是本身1,再次确认最大值。

图片.png

这时再进行确定最大值,此时最大值k的下标是3,它的左节点l下标是7,右节点r下标是8,进行判断左节点小于长度并且3大于1则最大值为l,进行判断r的下标小于长度并且6大于3,进行右节点交换则最大值为r,判断最大值下标不是本身3,再次确认最大值。

图片.png

这时再进行确定最大值,此时最大值k的下标是8,它的左节点l是17,右节点r是18,进行判断都不成立,本身是最大值,所以数组结束

图片.png
123.gif

图片.png
640?wx_fmt=jpeg

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:
        for(int i=1;i<=length;i++){
            //10 9 8 6 5 7 2 3 1 
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-1的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

// 构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=1,9,8,6,5,7,2,3,10
        //length此时=8
        //此时i=3
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

2020-02-20_171912.png

// 构建某一数组的最大堆结构
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:9,6,8,3,5,7,2,1,10
        for(int i=1;i<=length;i++){
            //i=2
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-2的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

// 构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=1,6,8,3,5,7,2,9,10
        //length此时=7
        //此时i=2
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

2020-02-20_173453.png

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:8,6,7,3,5,1,2,9,10
        for(int i=1;i<=length;i++){
            //i=3
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-3的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=2,6,7,3,5,1,8,9,10
        //length此时=6
        //此时i=2
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

2020-02-20_174803.png

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:7,6,2,3,5,1,8,9,10
        for(int i=1;i<=length;i++){
            //i=4
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-4的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=1,6,2,3,5,7,8,9,10
        //length此时=5
        //此时i=1
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

2020-02-20_184655.png

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:6,5,2,3,1,7,8,9,10    
        for(int i=1;i<=length;i++){
            //i=5
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-5的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=1,5,2,3,6,7,8,9,10
        //length此时=4
        //此时i=1
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

2020-02-20_200603.png

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:5,3,2,1,6,7,8,9,10
        for(int i=1;i<=length;i++){
            //i=6
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-6的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=1,3,2,5,6,7,8,9,10
        //length此时=3
        //此时i=1
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

图片.png

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:3,1,2,5,6,7,8,9,10
        for(int i=1;i<=length;i++){
            //i=7
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-7的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

构建某一数组的最大堆结构
    public static void buildMaxHeap(int[] array,int length){
        // the last node index is array.length-1
        // and his parent index is (array.length-1-1)/2
        //array此时=2,1,3,5,6,7,8,9,10
        //length此时=2
        //此时i=1
        for(int i=parent(length-1);i>=0;i--){
            keepMaxHeap(array,i,length);
        }
    }

图片.png

// heap sort the array
    public static void heapSort(int[] array){
        int length = array.length;
        //数组:2,1,3,5,6,7,8,9,10
        for(int i=1;i<=length;i++){
            //i=8
            swap(array,length-i,0);
            buildMaxHeap(array, length-i);
        }
        
    }
从数组长度-8的下标开始与0下标开始进行交换再进行大顶堆操作

图片.png

堆排序基本思想:
1.将待排序序列构造成一个大顶堆,此时整个序列的最大值就是堆顶的根节点。
2.将其与末尾元素进行交换,此时末尾就为最大值。
3.然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列

图片.png

堆排序的速度测试

// heap sort the array  
public static void heapSort(int[] array){  
  int length = array.length;  
  SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  Date time1=new Date();  
  String timeStr1=sim.format(time1);  
  System.out.println("开始时间:"+timeStr1);  
  for(int i=1;i<=length;i++){  
    swap(array,length-i,0);  
    buildMaxHeap(array, length-i);  
  }  
  Date time=new Date();  
  String timeStr=sim.format(time);  
  System.out.println("完成时间:"+timeStr);  
}
public static void main(String[] args) {  
    int[] array =new int[800000];  
    for (int i=0;i<800000;i++){  
       array[i]=(int)(Math.random()*8000000);  
    }  
  
    System.out.println("排序前");  
    MyHeap.heapSort(array);  
    
    // 从小到大排序使用大顶堆  
    System.out.println("排序后");  
    MyHeap.buildMaxHeap(array,array.length);  
    MyHeap.heapSort(array);  
}

图片.png

五、堆的时间程度

建堆的复杂度是O(n),将输入数据依次插入空堆中,这需要n次连续插入操作。

排序的复杂是O(logn),我们删除堆顶元素,堆化,第二小的大堆顶了,再删除,再堆化,...这些删除的元素是不是正好有序的?

我们直接把堆顶的元素与第n个元素交换位置,把(n-1)个元素堆化,再把堆顶元素与第(n-1)个元素交换位置,这时把(n-2)个元素堆化......进行下去,最后,数组中的元素就整个变成倒序的了,也就排序完了。

123.gif

我们知道删除一个元素的时间复杂度是O(log n),那么删除n个元素正好是:O(n log n)

由于堆排序过程中存在堆顶元素和最后一个节点互换的操作,有可能改变原始相对顺序,所以堆排序不是稳定的排序算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值