堆排序

堆排序是一种基于完全二叉树的排序算法,分为大根堆和小根堆。通过建立和调整堆,确保堆满足最大堆或最小堆的性质,然后逐步提取最大或最小元素以实现排序。percDown是维护最大堆的关键方法,递归的MAX-HEAPIFY进一步确保堆的正确性。堆排序的时间复杂度在最坏、最好和平均情况下均为O(N*logN)。

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

先说说堆

堆分为大根堆和小根堆,都是完全二叉树。
大根堆:要求每个节点不大于其父节点
小根堆 : 要求每个节点不小于其父节点

复习一下完全二叉树
  • 除了最后一层外,其他各层都达到该层节点的最大数,最后一层如果不满,该层所有节点都全部靠左排
  • 一颗二叉树,深度为k,对这棵树编号,如果所有的编号都和满二叉树的编号对应,则这棵树是完全二叉树

堆排序思想

堆排序的话,最重要还是建立堆,然后调整堆,使得堆满足大根堆(或小根堆)的定义。
我们定义了一个维护堆的方法percDown,这个方法维护最大堆的性质。
建立堆的过程其实是生成了一个数组,这个数组满足最大堆,但是这个数组不满足非降序的顺序。所以我们要把最大的元素一个一个提取,然后提取一个进行一次堆调整
最后生成出来的数组就是排序好的

堆排序的实现

堆排序先建堆,也就是将输入数组A[1..N]建成最大堆,注意这里下标是1开始,A[0]我们是用来作为一个哨兵使用。
首先其实是一个建堆过程,这个过程我们让i从A.length/2开始,
因为一个堆有一半的节点都是叶节点,而且一般都是存到数组的后半部分。
所以我们的i从一半长度开始即可。

如图:
这里写图片描述

我们需要从最后一个元素开始排序,因为其实我们是每次都把最大的那个数取出来,然后减小堆的size,然后再执行一个percDown调整堆,然后产生一个新的大顶堆,然后又继续这个操作,不断直到堆已经完全转为一个数组。 所以其实我们只是对0到i-1进行percDown,i到N-1是排好序的,所以是倒着的

    public void heapsort(int[] A){
        if(A==null)
            return;
        if(A.length<1)
            return;
        int N=A.length;
        //Build heap
        for(int i=A.length/2;i>=0;i--){
            percDown(A,i,A.length);
        }
        //swap
        for(int i=N-1;i>0;i--){//A[0]是最大的元素,A[i]是当前堆的最后一个元素
            swap(A,0,i);
            //产生新的大顶堆,结束后堆的规模减1, i means the length of heap 
            percDown(A,0,i);//交换完之后重新调整一下0 和 i之间的节点,把A[0]下移到合适的位置
        }
    }
技巧

一般我们在堆排序的时候,二叉堆实质我们是用一个数组存储。
利用下标 childNode=parentNode*2 来访问子节点。

最核心的percDown的实现。

思想:
我们要维护一个大根堆的性质,
percDown函数传入数组和一个指定的下标i,
i表示当前的父节点,然后我们通过Child=2*i访问这个子节
点,我们比较当前这个父节点和子节点,
如果父节点比最大的子节点都大那么不需要交换。
如果子节点比父节点大而且没有超出数组,则把子节点上移到父节点的位置。
然后继续循环,比较下一级,即上一次的Child是当前的父节点,仍然是和它的子节点比较。
仍然小于子节点则继续把子节点上移,直到最后当前节点比子节点都大或者超出数组范围。
最后交换一开始的节点最后的i的child节点

public void percDown(int[] A,int i,int N){//i means parent!actually

        int child;
        int tmp;
        for(tmp=A[i];(2*i+1)<N;i=child){//i=child 下沉到child
            child=2*i+1;
            if(child!=N-1&&A[child+1]>A[child])
                child++;
            if(tmp>=A[child]) //比最大的儿子还要大,不需要交换
                break;
            else//tmp 是小的,因为是大顶堆所以需要把tmp下移到合适的位置然后把child上移
                A[i]=A[child]; //把最大的child上移
        }
        //tmp store the initial value.
        A[i]=tmp;//actually here i means child,notice i==child!
    }

递归思想MAX-HEAPIFY

还有一种是递归实现,仍然是比较当前节点A[i]和他的孩子节点,如果当前节点是最大的,则以该节点为根的子树已经是最大堆,结束。 否则最大元素是它的某个孩子节点,需要交换A[largest]和当前A[i]。但是交换后,以A[i]的孩子节点largest为根的子树可能又会违反最大堆的性质,所以这时候递归调用MAX-HEAPIFY

伪码描述

MAX-HEAPIFY(A,i)
    l=2*i;
    r=2*i+1;
    if(l<=A.heap-szie && A[l]>[i])
        largest=l
    else
        largest=i
    if(r<=A.heap-size && A[r]>A[largest]
        largest=r
    if(largest!=i)
        swap(i,largest)
        MAX-HEAPIFY(A,largest)

性能分析

最坏 最好 平均 都是
O(N*logN)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值