数据结构学习:堆

一、概念

  • 堆是一种完全二叉树(适合用数组存储)
  • 堆中每个节点的值都必须大于等于(或小于等于)其子树中每个节点的值
  • 对于每个节点的值都大于等于子树中每个节点的值的堆,叫"大顶堆",对于每个节点的值都小于等于子树中每个节点的值,叫"小顶堆"

二、如何实现一个堆?

  • 一般二叉树都是通过链表或者数组来存储,针对完全二叉树,用数组存储比较好,因为不需存储左右节点的指针,单纯通过数组下标就可找到

三、代码实现

//实现堆主要包括建堆和删除堆顶元素
package DataStructer.Tree;

public class HeadTree_zhu {
    //用数组实现堆
    private int[] a;
    private int n; //堆可以存储的最大元素个数
    private int count; //数组中元素个数

    public HeadTree_zhu(int capacity){
        this.a = new int[capacity + 1]; //下标为0的位置不存储元素(可以存储),所以要+1
        this.n = capacity;
        this.count = 0;
    }

    //往堆中存储元素
    public void insert(int data){
        if (count >= n){
            return; //堆中元素已满,不能再插入
        }

        //往数组的最后一个位置插入元素
        count++;
        int i = count;
        a[i] = data;

        //构造的是大顶堆,采用的是从上往下的堆化方式,i / 2表示父节点
        while (i / 2 > 0 && a[i] > a[i / 2]){
            swap(a,i,i / 2);
            i = i / 2;
        }
    }

    //删除堆顶元素
    public void delete(){
        if (count == 0){
            return; //堆中元素为空,直接返回
        }

        a[1] = a[count];
        count--;
        heapify(a,count,1);
    }

    private void heapify(int[] a,int n,int i){
        while (true){
            int index = i;
            if (2 * i <= n && a[i] < a[2 * i]){
                index = 2 * i;
            }
            if (2 * i + 1 <= n && a[index] < a[2 * i + 1]){
                index = 2 * i + 1;
            }
            if (index == i){
                break;
            }
            swap(a,index,i);
            i = index;
        }
    }

    public static void swap(int[] a,int i,int j){
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

四、堆应用之堆排序

package DataStructer.Tree;

public class HeapSort_zhu {

    public static void sort(int[] a){
        /* 
        1)建堆:在不建立新数组的情况下,把原先的数据转化为一个大顶堆;找到最后一个非叶子节点,依次堆化到根节点
        2)排序:大顶堆的堆顶元素肯定是最大的额,把堆顶元素放到最后一个位置,剩余元素再进行堆化,又把堆顶元素放到倒数第二个位置...依次类推,直到剩1个元素
        */
        buildHeap(a);//原先的数组转化为大顶堆
        int k = a.length - 1;
        while (k > 0){
            swap(a,k,0);
            k--;
            heapify(a,k,0);
        }
    }

    public static void buildHeap(int[] a){
        //最后一个非叶子节点,依次堆化到根节点
        for (int i = (a.length - 1) / 2;i >= 0;i--){
            heapify(a,a.length - 1,i);
        }
    }

    public static void heapify(int[] a,int n,int i){
        //构造的是大顶堆,采用的是从上而下的堆化方法
        while (true){
            int index = i;
            if (i * 2 + 1 <= n && a[i] < a[2 * i + 1]){
                //与左节点进行比较
                index = 2 * i + 1;
            }
            if (i * 2 + 2 <= n && a[index] < a[i * 2 + 2]){
                index = i * 2 + 2;
            }
            if (index == i){
                break;
            }
            swap(a,index,i);
        }
    }

    public static void swap(int[] a,int i,int j){
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

五、实际开发中,为什么快排比堆排(时间复杂度都是O(nlogn),且都为原地排序)要好?

  1. 堆排序数据访问方式没有快排友好,对于快排来说,数据是顺序访问的;对于堆排来说,数据是跳着访问的;堆排一个重要的过程就是堆化,会依次访问下标为1、2、4、8等元素,这样对CPU缓存是不友好的
  2. 对于同样的数据,堆排序算法的数据交换次数要多于排序。堆排中有一个建堆的过程,会打乱数据原有的相对顺序,比如一组有序的数据通过建堆后,可能会更加无序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱夜来香A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值