数据结构与算法(java):堆

1、概述

1.1 定义

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

1.2 特性

1、堆是完全二叉树,即除了树的最后一层结点不需要满的,其他的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。如图
在这里插入图片描述
2、通常是利用数组来实现的,具体方法就是将二叉树的结点按照层级顺序放入到数组中,根结点在位置1处,它的两个子结点位置为2,3,其他结点的规则如下,如果一个结点的位置为k,那么它的父结点的位置为【k/2】,而它的两个子节点的位置则分别为【2k】和【2k+1】。这样在不使用指针的情况下,也可以通过计算数组的索引在树中移动。如图

3、每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,每个结点的值小于或等于其左右孩子结点的值,称为小顶堆

在这里插入图片描述

2、堆的基本实现

这里实现的是大顶堆。
主要方法:堆的插入与删除方法,上浮和下沉算法

(1)上浮算法(rise)主要原理:循环比较当前结点和其父结点的值,当当前结点的值大于父结点的值时交换两个结点的位置。
(2)下沉算法(sink)主要原理:通过循环不断比较当前k结点和其左子结点2k以及右子结点2k+1中的较大值的元素大小,若当前结点小与其两者间的最大值,则交换当前结点与其子节点中最大值的位置。
(3)插入方法(insert):注意堆的根结点位于数组的1位置处,而非0位置处, 此外,每次插入一个值时通过上浮算法来确定新插入的值的位置。
(4)删除最大值(delmax):这里采用的主要原理是先交换最大值与最大索引处的值的位置,然后删除交换位置后的最大索引处的值,期间通过下沉算法重新排列了要删除的最大值的位置。

代码实现
public class Heap <T extends Comparable<T>>{
    //定义一个数组来存储堆中的元素
    private T[] items;
    //记录堆中元素个数
    private int Num;

    public Heap(int capacity){
        this.items = (T []) new Comparable[capacity+1]; //因为T继承了Comparable接口,所以这里是new Comparable类型数组
        this.Num = 0;
    }

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

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

//--------------------------------------------------------------------
    //往堆中插入一个元素
    public void insert(T t) {
        //因为数组数量Num初始化为0,而这里堆中第一个元素item[0]是不存放任何数值的,所以使用++Num跳过了第一个items【0】
        items[++Num] = t;
        rise(Num);
    }

//--------------------------------------------------------------------
    //上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void rise(int k){
        //通过循环不断比较当前结点的值和其父结点的值,如果当前结点大于父结点就交换两者位置
        while(k>1){
            //比较当前结点和其父结点
            if(less(k/2,k)){
                exchange(k/2,k);
                k = k/2;
            }else{
                break;
            }
        }

    }

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

        //交换索引1处元素和最大索引处的元素,让完全二叉树最右侧的元素变为临时根结点
        exchange(1,Num);

        //删除交换操作后的最大索引处的元素
        items[Num] = null;

        //元素个数减1
        Num--;

        //通过下沉算法,重新排列堆
        sink(1);

        return max;
    }

//--------------------------------------------------------------------
    //下沉算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k){
        //通过循环不断比较当前k结点和其左子结点2*k以及右子结点2*k+1处的较大值的元素大小
        //当前结点小,则交换与子节点中最大值的位置
        while(2*k<=Num){
            //获取当前结点的子结点的最大结点
            int max;
            if(2*k+1<=Num){ //判断当前结点是否有右子结点
                if(2*k<2*k+1){
                    max = 2*k+1;
                }else{
                    max = 2*k;
                }
            }else{
                max = 2*k;
            }

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

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

            //交换k的值
            k = max;
        }
    }
}

测试代码

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

		//循环删除最大值
        String result = null;
        while((result = heap.delMax()) != null){
            System.out.print(result + " ");
        }
    }
}

结果

G F E C B D A

3、堆排序

基本思想

  1. 将待排序序列构造成一个大顶堆(父结点大于两个子结点)
  2. 此时,整个序列的最大值就是堆顶的根结点
  3. 将其与末尾元素进行交换,此时末尾就为最大值
  4. 将末尾这个最大值用排除堆之外,然后将剩余n-1个元素重新构成一个堆,这样就会得到n个元素的次小值,反复执行,按顺序排除出堆的元素就能组成一个有序队列了。

问题描述:给定一个数组,使用堆排序从小到大排序好
String[] s = {“S”,“O”,“R”,“T”,“E”,“X”,“A”,“M”,“P”,“L”,“E”};
主要方法及其原理:
(1)根据原数组构造出一个堆(createHeap(Comparable[] source, Comparable[] heap)),返回值为空,参数分别是要传入的数组和堆的数组,将传入的数组复制到堆数组中,并用下沉算法调整堆中的元素
(2)下沉算法(sink(Comparable [] heap,int target,int range)),返回值空,target是要下沉调整的元素,range是调整的范围,可以看成是不断变换的堆数组的长度
(3)排序方法(sort(Comparable[] cource)),参数是要传入的数组,每次循环将最大值和堆中最大索引处的值交换,并且排除最大索引处的值,使其不再参与下沉,排除后的值仍在堆中并且处于的数组中的位置没有变,直到循环结束,排除后的值就是按照从小到大的顺序排序,最后再将堆复制到原数组中。

具体操作看代码

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

//-----------------------------------------------------------------
    //交换heap堆中两指定索引处的值
    private static void exchange(Comparable[] heap, int i, int j){
        Comparable temp = heap[i];
        heap[i] = heap[j];
        heap[j] = temp;
    }

//-----------------------------------------------------------------
    //根据原数组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){
            //交换元素
            exchange(heap,1,N);
            //排序交换后最大元素所在的索引,让它不再参与下沉
            N--;
            //对索引1处的元素进行堆的下沉调整
            sink(heap,1,N);
        }
        //
        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){
            //找出当前结点的较大的子节点
            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;
            }

            exchange(heap,target,max);
            target = max;
        }
    }
}

测试代码

 public static void main(String[] args) {
        String[] s = {"S","O","R","T","E","X","A","M","P","L","E"};
        HeapSort.sort(s);
        System.out.println(Arrays.toString(s));
    }

结果:

[A, E, E, L, M, O, P, R, S, T, X]

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值