数据结构之堆

1.堆

1.1堆的定义

堆是计算机科学中一类特殊的数据结构的统称,堆通常可以 被看做是一棵完全二叉树的数组对象。

1.2堆的特性:

它是完全二叉树,除了树的最后一层结点不需要是满的,其 它的每一层从左到右都是满的,如果最后一层结点不是满的, 那么要求左满右不满。

在这里插入图片描述

它通常用数组来实现。 具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子 结点则分别在位置4,5,6和7,以此类推。

在这里插入图片描述

如果一个结点的位置为k,则它的父结点的位置为 [k/2],而它的两个子结点的位置则分别为 2k2k+1

这样,在不 使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就 令k等于2k或2k+1。

每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个 子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。

1.3 堆的API设计

在这里插入图片描述

  • insert插入方法的实现

堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只 能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些 方法让刚才插入的这个数据放入到合适的位置。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以,如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它的父结点a[k/2]的大小,然后根据结果完成
数据元素的交换,就可以完成堆的有序调整。

  • delMax删除最大元素方法的实现

由堆的特性我们可以知道,索引1处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要 有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足 堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。
所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断的拿着当前结点a[k]与它的子结点a[2k] 和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。

1.4.1 堆的实现代码

public class Heap<T extends Comparable<T>> {
    //存储堆中的元素
    private T[] items;
    //记录堆中元素的个数
    private int N;

    public Heap(int capacity) {
        this.items= (T[]) new Comparable[capacity+1];
        this.N=0;
    }

    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i,int j){
        return items[i].compareTo(items[j])<0;
    }

    //交换堆中i索引和j索引处的值
    private void exch(int i,int j){
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    //往堆中插入一个元素
    public void insert(T t){
        items[++N]=t;
        swim(N);
    }

    //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void swim(int k){
        //通过循环,不断的比较当前结点的值和其父结点的值,如果发现父结点的值比当前结点的值小,则交换位置
        while(k>1){
            //比较当前结点和其父结点

            if (less(k/2,k)){
                exch(k/2,k);
            }

            k = k/2;
        }

    }

    //删除堆中最大的元素,并返回这个最大元素
    public T delMax(){
        T max = items[1];

        //交换索引1处的元素和最大索引处的元素,让完全二叉树中最右侧的元素变为临时根结点
        exch(1,N);
        //最大索引处的元素删除掉
        items[N]=null;
        //元素个数-1
        N--;
        //通过下沉调整堆,让堆重新有序
        sink(1);
        return max;
    }

    //使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k){
        //通过循环不断的对比当前k结点和其左子结点2*k以及右子结点2k+1处中的较大值的元素大小,如果当前结点小,则需要交换位置
        while(2*k<=N){
            //获取当前结点的子结点中的较大结点
            int max;//记录较大结点所在的索引
            if (2*k+1<=N){
                if (less(2*k,2*k+1)){
                    max=2*k+1;
                }else{
                    max=2*k;
                }
            }else {
                max = 2*k;
            }


            //比较当前结点和较大结点的值
            if (!less(k,max)){
                break;
            }

            //交换k索引处的值和max索引处的值
            exch(k,max);

            //变换k的值
            k = max;

        }


    }

    public static void main(String[] args) {
        Heap<String> heap = new Heap<String>(20);
        heap.insert("A");
        heap.insert("B");
        heap.insert("C");
        heap.insert("D");
        heap.insert("E");
        heap.insert("F");
        heap.insert("G");


        String del;
        while((del=heap.delMax())!=null){
            System.out.print(del+",");
        }

    }
}

1.4.2堆排序

给定一个数组:
String[] arr = {“S”,“O”,“R”,“T”,“E”,“X”,“A”,“M”,“P”,“L”,“E”}
请对数组中的字符按从小到大排序。

实现步骤

  1. 构造堆;
  2. 得到堆顶元素,这个值就是最大值;
  3. 交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
  4. 对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
  5. 重复2~4这个步骤,直到堆中剩一个元素为止。
    在这里插入图片描述
1.4.3堆构造过程

堆的构造,最直观的想法就是另外再创建一个和新数组数组,然后从左往右遍历原数组,每得到一个元素后,添加到新数组中,并通过上浮,对堆进行调整,最后新的数组就是一个堆。上述的方式虽然很直观,也很简单,但是我们可以用更聪明一点的办法完成它。创建一个新数组,把原数组0length-1的数据拷贝到数组的1length处,再从新数组长度的一半处开始往1索引处扫描(从右往左),然后对扫描到的每一个元素做下沉调整即可。

1.4.4 堆排序过程

public class HeapSort {
    //判断heap堆中索引i处的元素是否小于索引j处的元素
    private static  boolean less(Comparable[] heap, int i, int j) {
        return heap[i].compareTo(heap[j])<0;
    }

    //交换heap堆中i索引和j索引处的值
    private static  void exch(Comparable[] heap, int i, int j) {
        Comparable tmp = heap[i];
        heap[i] = heap[j];
        heap[j] = tmp;
    }



    //根据原数组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){
            //交换元素
            exch(heap,1,N);
            //排序交换后最大元素所在的索引,让它不要参与堆的下沉调整
            N--;
            //需要对索引1处的元素进行对的下沉调整
            sink(heap,1, N);
        }

        //把heap中的数据复制到原数组source中
        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){
            //1.找出当前结点的较大的子结点
            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;
            }

            exch(heap,target,max);

            target = max;
        }
    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值