堆排--原地排序和非原地排序

本文探讨了堆排的两种实现方式,包括借助新数组的普通堆排以及直接在原数组上进行的原地排序堆排。详细介绍了这两种方法的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

堆排

实现内容:

1.普通堆排–借助一个新数组实现

2.原地排序实现堆排–在原数组基础上实现

普通堆排:
//HeapTest.java
package heap;

import java.util.Arrays;
import java.util.Comparator;

//基于数组的堆
public class HeapTest<E>{
    //堆结构为完全二叉树,值要求为任意一个节点要大于自身的左右节点的值(最大堆)
    //故需要提供比较器进行数值比较
    private Comparator<E> comparator;
    private int size;
    private E[] elemetData;
    //设置数组初始化的的默认值
    private static final int DEFAULT_CAPTITY=10;
    //通过构造方法初始化数组及传入外部比较器
    public HeapTest(){
        this(DEFAULT_CAPTITY,null);
    }
    public HeapTest(int initCaptity){
        this(initCaptity,null);
    }
    public HeapTest(int initCaptity,Comparator<E> comparator){
        elemetData= (E[]) new Object[initCaptity];
        this.comparator=comparator;
    }
    //将任意一个数组变为最大堆的形式
    public HeapTest(E[] arr){
        elemetData= (E[]) new Object[arr.length];
        for(int i=0;i<elemetData.length;i++)
            elemetData[i]=arr[i];
        //更新size
        size=elemetData.length;
        //此处是将任意一个数组初步建立为堆结构后,从最后一个非叶子节点处向根节点一次做下沉操作,
        //原因是所有叶子节点单独来看均是一个堆,故叶子结点都是满足堆结构的,只有非叶子节点可能不满足,故此种方式可大大提高效率
        for(int i=(arr.length-1-1)/2;i>=0;i--)
            siftDown(i);
    }
    //获得当前堆元素个数
    public int getSize(){
        return size;
    }
    //判断当前堆是否为空
    public boolean isEmpty(){
        return size==0;
    }
    //比较方法
    private int compare(E o1,E o2){
        if(comparator==null)
            return ((Comparable<E>)o1).compareTo(o2);
        return comparator.compare(o1,o2);
    }
    //获得当前节点的左孩子下标
    public int leftChildIndex(int index){
        return 2*index+1;
    }
    //获得当前节点的右孩子下标
    public int rightChildIndex(int index){
        return 2*index+2;
    }
    //获得当前节点的父节点下标
    public int parentIndex(int index){
        if(index==0)
            throw new IllegalArgumentException("你没有爸爸");
        return (index-1)/2;
    }
    //向堆中添加元素,因堆由数组实现,故添加元素时从数组最后插入方便
    //但此时就不能确保该结构还是堆,故需将插入元素上浮到合适位置使得当前结构依旧满足堆结构
    public void add(E e){
        //当当前数组存储已满时,对其进行扩容
        if(size==elemetData.length)
            grow();
        //否则将插入元素添加至数组尾部
        elemetData[size++]=e;
        //将插入元素上浮以满足堆结构
        //size++走到了下一位,需传入数组最后一个元素的下标
        siftUp(size-1);
    }
    //因数组可能出现存储满的情况,故需要扩容
    private void grow(){
        int oldCap=elemetData.length;
        int newCap=oldCap+(oldCap<64?oldCap:oldCap>>1);
        if(newCap>Integer.MAX_VALUE-8)
            throw new IndexOutOfBoundsException("数组太大");
        elemetData= Arrays.copyOf(elemetData,newCap);
    }
    //元素上浮
    private void siftUp(int index){
        //当插入元素下标大于0(即不为根节点)且插入元素值大于其父节点值时,需要将其不断上浮,直到到根节点位置或小于其父节点的值才停止上浮
        while (index>0 && compare(elemetData[index],elemetData[parentIndex(index)])>0){
            //将插入元素与其父节点值进行交换(即为插入元素一次上浮操作)
            swap(index,parentIndex(index));
            //将插入元素下标更新为父节点下标,为下次上浮做准备
            index=parentIndex(index);
        }
    }
    private void swap(int indexA,int indexB){
        E temp=elemetData[indexA];
        elemetData[indexA]=elemetData[indexB];
        elemetData[indexB]=temp;
    }

    @Override
    public String toString() {
        StringBuilder res=new StringBuilder();
        for(E e:elemetData){
            if(e!=null)
                res.append(e+" ");
        }
        return res.toString();
    }
    //取得堆中最大元素
    public E getMax(){
        if(isEmpty())
            throw new IllegalArgumentException("heap is empty");
        return elemetData[0];
    }
    //取得堆中最大元素并删除
    //最大堆最大值一定是根节点,当删除根节点时需要改变堆结构,使得当前最大值称为新的根节点
    //通过对比得知,将堆中最大值删除后可将数组最后一个元素放于根节点位置,而后再将根节点与左右孩子值比较进行下沉操作
    //下沉思路:将根节点与左右孩子值对比,与较大值交换,直到叶子结点或当前节点比左右孩子值都大时停止下沉操作
    //将堆中最大值删除后返回删除的最大值
    public E extractMax(){
        E result=getMax();
        swap(0,size-1);
        elemetData[--size]=null;
        siftDown(0);
        return result;
    }
    //将二叉树下沉到合适位置
    private void siftDown(int index){
        //在堆中非叶子节点一定有左孩子但不一定有右孩子
        //将当前节点的左孩子下标暂存于临时变量j
        while (leftChildIndex(index)<size){
            int j=leftChildIndex(index);
            //若j+1<size则说明该节点有左右孩子
            if(j+1<size){
                //找出左右孩子的较大值
                if(compare(elemetData[j],elemetData[j+1])<0)
                    j++;
            }
            //此时j为左右孩子中较大值的下标
            if(compare(elemetData[index],elemetData[j])>0)
                break;
            //将当前节点与左右孩子的较大值交换
            swap(index,j);
            index=j;
        }
    }
    //替换堆顶元素
    public E prepare(E newValue){
        E result=getMax();
        elemetData[0]=newValue;
        siftDown(0);
        return result;
    }
}

//HeapSort.java
package heap;

import java.util.Random;

public class HeapSort {
    public static void main(String[] args) {
        //随机生成100个数存入数组
        long start=System.currentTimeMillis();
        int n=100;
        Random random=new Random();
        Integer[] data=new Integer[n];
        for(int i=0;i<data.length;i++){
            data[i]=random.nextInt(1000);
        }
        heapSort1(data);
        for(int temp:data)
            System.out.print(temp+" ");
        long end=System.currentTimeMillis();
        System.out.println();
        System.out.println("共耗时:"+(end-start)+"毫秒");
    }
    public static void heapSort1(Integer[] arr){
        //将整个数组传入,调整为堆结构
        HeapTest<Integer> heap=new HeapTest<>(arr);
        //将堆中最大元素依次取出从数组末尾存放,再正序输出数组元素即可得到升序数组
        for(int i=arr.length-1;i>=0;i--)
            arr[i]=heap.extractMax();
    }
}

/*
实现结果:
6 33 44 62 66 70 79 79 82 97 100 107 112 112 152 157 169 191 195 203 204 207 217 236 245 247 291 295 311 313 334 335 338 341 381 399 409 413 413 418 426 437 461 491 492 514 515 543 544 546 546 551 553 559 566 581 581 641 652 661 682 682 691 731 731 735 736 748 751 775 790 801 810 812 818 823 844 853 853 855 859 895 896 897 898 906 907 910 922 950 950 959 969 970 972 973 983 983 985 996 
共耗时:6毫秒
*/

原地排序堆排:

package heap;

import java.util.Random;

public class HeapSort {
    public static void main(String[] args) {
        long start=System.currentTimeMillis();
        int n=100;
        Random random=new Random();
        Integer[] data=new Integer[n];
        for(int i=0;i<data.length;i++){
            data[i]=random.nextInt(1000);
        }
        heapSort2(data);
        for(int temp:data)
            System.out.print(temp+" ");
        long end=System.currentTimeMillis();
        System.out.println();
        System.out.println("共耗时:"+(end-start)+"毫秒");
    }
    /**
     * 原地堆排序
     */
    public static void heapSort2(Integer[] arr){
        int length=arr.length;
        for(int i=(length-1-1)/2;i>=0;i--){
            //从最后一个非叶子节点到根节点遍历做下沉操作
            siftDown(arr,length,i);
        }
        //将堆顶元素依次交换到数组末尾,再对其他元素做调整以满足堆结构,对栈顶元素做下沉操作
        for(int i=length-1;i>=0;i--){
            //交换堆顶元素和数组末尾元素
            swap(arr,0,i);
            //再对堆顶元素做下沉操作,因最大元素交换至数组末尾后,之前元素的结构就不满足堆的要求,故需要做下沉操作以满足堆结构,以此循环直到数组有序
            siftDown(arr,i,0);
        }
    }
    //交换
    private static void swap(Integer[] arr,int indexA,int indexB){
        int temp=arr[indexA];
        arr[indexA]=arr[indexB];
        arr[indexB]=temp;
    }

    /**
     *@param arr 数组(因只能在原数组上排序)
     * @param n 长度(每次siftDown一次就减一,原因是每次siftDown一次就能保证一个元素在堆的最终位置)
     * @param k 下沉元素下标
     */
    private static void siftDown(Integer[] arr,int n,int k){
        while (2*k+1<n){
            int j=2*k+1;
            //判断当前节点是否有右孩子,并找到左右孩子的最大值
            if(j+1<n){
                if(arr[j].compareTo(arr[j+1])<0)
                    j++;
            }
            //此时j为左右孩子中最大值的下标
            //判断当前节点的值与左右孩子中的最大值的大小关系
            //若当前节点大于左右孩子的最大值也就意味着同时大于左右孩子的值,停止下沉
            if(arr[k].compareTo(arr[j])>0)
                break;
            //否则将当前节点与左右孩子最大值交换
            swap(arr,j,k);
            //改变当前节点下标,准备下一次交换
            k=j;
        }
    }
}
/*
输出结果:
15 16 29 37 38 40 51 95 101 102 108 114 120 123 129 132 142 143 145 147 150 151 152 159 184 190 206 214 219 221 235 240 255 259 261 301 302 378 389 396 405 408 413 417 457 479 493 510 534 547 549 551 560 564 575 584 593 595 651 671 679 679 709 741 743 756 763 766 775 776 781 789 790 835 858 858 859 860 862 863 863 867 877 885 887 891 905 907 907 912 918 925 938 946 949 951 956 976 982 998 
共耗时:4毫秒
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值