精华内容
下载资源
问答
  • 堆排序演示过程

    2014-03-07 11:07:59
    详细的演示堆排序过程,有利于你对堆有个精确的理解。
  • 本代码主要演示堆排序的排序方法,代码在centos7中测试通过。 编译方法使用g++ heapsort.cpp -o heapsort,运行heapsort即可
  • 堆排序动画演示

    2018-01-10 20:02:59
    仔细讲解了堆排序的过程,通过动画进行演示,适合用作课堂展示,也适合自己学习。
  • 堆排序 c语言 演示

    2012-01-16 21:59:05
    自己做的 很辛苦的。堆排序课设的代码,用c语言做的 数据结构堆排序的算法演示~~~可以借鉴或 参考
  • 动图演示堆排序

    多人点赞 热门讨论 2021-09-02 13:51:27
    文章目录前言顺序存储特点清楚数据结构----堆堆排序向下调整法建堆排序堆排序总代码测试 前言 上一小节,博主介绍了树状数据结构,其中提到了二叉树的顺序存储与链式存储.而二叉树的顺序存储我们用的最多的就是 ...

    前言

    上一小节,博主介绍了树状数据结构,其中提到了二叉树的顺序存储与链式存储.而二叉树的顺序存储我们用的最多的就是 堆排序以及 选出前k个数(最小或最大),但是今天博主要介绍的就是 堆排序


    顺序存储特点

    用一个数组从每一层开始,从上到下,从左到右进行存储二叉树中每一个结点中的值.如果有空结点,也需要占一个位置.

    image-20210901224059586

    有人会问,这样进行存储我们怎么知道双亲结点(父节点)与其左右孩子的关系呢?答案是 二叉树中双亲节点与孩子结点下标满足一定的关系,可以用公式进行表示出来

    左右孩子结点与双亲结点的下标关系如下:

    • leftchild = parent * 2 + 1
    • rightchild = parent * 2 + 2

    双亲结点与孩子结点的下标的关系如下

    • parent = ( leftchild - 1 ) / 2
    • parent = ( rightchild - 2 ) / 2

    但是大家仔细想想双亲结点与孩子结点的关系是否可以优化一下?没错,优化如下:

    • parent = (child - 1) / 2,理由是在C语言中,运算符/遵循向下取整,所以上面的两个式子可以用下面一个式子代替.

    清楚数据结构----堆

    堆是二叉树中的一些特殊数据,其中分为大堆与小堆,并且只有符合大堆或者小堆的特性的二叉树才能叫做堆.

    • 大堆 二叉树中所有的双亲结点值(父结点)都大于其对应的孩子结点的值.
    • 小堆 二叉树中所有的双亲结点值(父结点)都小于其对应的孩子结点的值.

    如下图所示,展示两种结构:

    image-20210901230241445

    大家发现无论是大堆还是小堆,都有一个特性吗?什么特性呢?

    最值都是在数组索引为0处. 大家记住这个特点,后面会用到哦.


    堆排序

    即随机给出一个数组,我们想要利用堆的特性进行排序,该怎么进行排序呢?

    比如有数组num[] = {4,5,1,3,9,7,8,6,2,8,4};

    我们既然想要利用堆特性进行对此数组排序,那么我们的第一步一定是把此数组变成堆(该过程称为建堆),大家想想有什么办法把它变成堆?

    答案是:向下调整法


    向下调整法

    向下调整法的前提是除了根结点以外,其所有子树都符合小堆或者大堆的特性.

    比如数组 num[] = {6,7,8,6,2,1,3};其树状结构如图:

    image-20210901231756346

    对于向上面的数组便可以使用向下调整法,我们下面以要构建小堆为例.

    向下调整法的介绍:

    假设有一个数组num[] = {6,3,2,5,4,3,7,8,9,6,8,5,9,8,8,9,9,8,7,7,9,7,6,6};(符合向下调整法的前提),因为除了根结点之外,其余都符合小堆特性,所以为了构建一个完整的堆,我们就需要把根结点移动到相应的位置,其移动过程示意图如下:

    动画

    我们仔细观察上述动图过程,发现向下调整法步骤如下:

    1. 判断左右孩子中谁最小.

    2. 如果双亲结点比最小孩子大,就交换两个结点值,否则调整完毕

    3. 一直重复此操作,若调整到已没有孩子结点,则调整结束

    第一步:判断左右孩子结点谁更小.

    child = parent*2+1; //我们先假设左孩子更小.
    if(child+1<n && num[child+1] < num[child])   //n是数组长度
    {
        child++;
    } 
    //如果右孩子更小,就把最小孩子更新到右孩子.
    //之所以有条件child+1<n,是考虑上图二叉树最后只有一个孩子结点时候,那么默认最小值就只要左孩子,不需要与右孩子比较
    

    第二步:判断是否交换双亲结点与孩子结点值

    void Swap(int*a,int*b)
    {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    
    if(num[parent] > num[child])
    {
        Swap(&num[parent] , &num[child]);
        parent = child;              //交换值以后,重新更新双亲结点
        child = parent*2+1;          //交换值以后,重新默认新的左孩子为最小值.
    }
    else
    {
        break; //否则结束循环.
    }
    

    第三步:重复上述步骤

    while(child < n) //child<n 代表没有子节点了,就结束调整
    {
        //伪代码
        第一步代码;
        第二步代码;
    }
    

    所以向下调整法的代码步骤为:

    void Swap(int*a,int*b)
    {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
        
    void AdjustDown(int num[],int n,int parent)   //n是数组长度
    {
        child = parent*2+1; //我们先假设左孩子更小.
        
    	while(child<n)
        {
            if(child+1<n && num[child+1] < num[child])
            {
                child++;
            } 
            
            if(num[parent] > num[child])
            {
                Swap(&num[parent] , &num[child]);
                parent = child;              //交换值以后,重新更新双亲结点
                child = parent*2+1;          //交换值以后,重新默认新的左孩子为最小值.
            }
            else
            {
                break; //否则结束循环.
            }
        }
    }
    

    建堆

    向下调整法中,我们已经发现了,想要向下调整必须满足向下调整的条件,但是我们想要的是对随机数组进行排序,所以除了向下调整外,我们还应该怎样做,才能达到真正的建堆操作??.

    答案: 我们反向行走,从最底层,最右边的子树开始进行调整,然后从右向左,从下到上.

    比如有数组num[] = {4,3,9,8,1,6,7,5,2,9,7,6,1,3};,我们的向下调整数据步骤如下:

    动画

    所以建堆代码如下:

    for(int parent = (n-1-1)/2;parent>=0;parent--)  //n-1是最后一层最右边结点的索引.(n-1-1)/2就是其双亲结点索引
    {
        AdjustDown(num,n,parent);
    }
    

    排序

    既然我们已经对随机数组建立好堆结构,那么剩下的就是进行**堆排序.**但是到这一步后就产生了一个误区,什么误区呢?

    我们想要排升序,就应该建立小堆.

    我们想要排降序,就应该建立大堆. 对吗?答案是不对,这会让时间复杂度变得极高.

    正确的答案是,如果想要排升序,就应该建立大堆,想要排降序,就应该建立小堆.如果不相信的人,大家可以试试升序建小堆,看看怎样进行排序,就会发现及其复杂,由于篇幅有限,博主就不再赘述.

    由于我们已经建立好小堆,我们就以排列降序为例,仍是数组num[] = {4,3,9,8,1,6,7,5,2,9,7,6,1,3};

    我们利用堆结构中最值总是在索引为0处特点进行排序.首先把最小值和最后一个值进行交换,然后又对[0,n-2]索引中的数进行向下调整,不断重读此步骤,如图(下面只是演示了排序过程中的部分步骤,因为后续步骤都是一样的进行重复):

    动画

    所以按照上述步骤,我们的堆排序(降序)过程代码如下:

    void HeapSort(int num[],int n)
    {
        //建小堆
        for(int parent = (n-1-1)/2;parent>=0;parent--)
        {
             AdjustDown(num,n,parent);
        }
        
        for(int end = n-1;end>0;end--)  //end不用为0是因为最后还剩一个时候不用再交换.
        {
            Swap(&num[0],&num[end]);
          	AdjustDown(num,end,0);
        }
    }
    

    堆排序总代码

    void Swap(int* a, int* b)
    {
    	int tmp = *a;
    	*a = *b;
    	*b = tmp;
    }
    
    void AdjustDown(int num[],int n,int parent)
    {
    	int leftchild = parent * 2 + 1;
    	while (leftchild < n)
    	{
    		if (leftchild + 1 < n && num[leftchild] > num[leftchild + 1])
    		{
    			leftchild++;
    		}
    
    		if (num[parent] > num[leftchild])
    		{
    			Swap(&num[parent],&num[leftchild]);
    			parent = leftchild;
    			leftchild = parent * 2 + 1;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
    
    void HeapSort(int num[], int n)
    {
    	//建小堆
    	for (int parent = (n - 1 - 1) / 2; parent >= 0; parent--)
    	{
    		AdjustDown(num, n, parent);
    	}
    
        
        //交换首位值,然后排除最后一个位置,重新向下调整
    	for (int end = n - 1; end > 0; end--)  //end不用为0是因为最后还剩一个时候不用再交换.
    	{
    		Swap(&num[0], &num[end]);
    		AdjustDown(num, end, 0);
    	}
    }
    

    测试

    数组num[] = {4,3,9,8,1,6,7,5,2,9,7,6,1,3};,使用上述代码进行堆排序以后的结果为:

    image-20210902134848312

    排序成功!!!

    展开全文
  • 堆排序-flash演示

    2011-11-16 14:41:27
    堆排序-flash演示 可自己输入数据............
  • 动图演示堆排序.pdf

    2021-09-14 15:53:57
    动图演示堆排序.pdf
  • ppt制作的堆排序过程,每一步都演示的很详细,可帮助大家理解堆的定义,理清思路,有需要的就下载吧。
  • 普通队列:先进先出,后进后出 优先队列:出队顺序和入队顺序无关,和优先级有关 例如:在N个元素中选择前M个元素,用排序的方法复杂度为NlogN,而使用则为NlogM ...

    一、堆和优先队列

    什么是优先队列?

    普通队列:先进先出,后进后出
    优先队列:出队顺序和入队顺序无关,和优先级有关

        动态选择优先级最高的人物执行,优先队列是动态执行的,每次都会更新队列。例如:在N个元素中选择前M个元素,用排序的方法复杂度为NlogN,而使用堆则为NlogM。优先队列的主要操作是:入队,出队(取出游戏那几最高的元素)

    优先队列的实现方法

    优先队列实现的比较:

    入队出队
    普通数组O(1)O(n)
    顺序数组(排序后的数组)O(n)O(1)
    O(lgn)O(lgn)

       由表可以看出,普通数组实现优先队列入队时直接加入到数组末尾,复杂度为O(1);出队时遍历整个数组,复杂度为O(n)。

       顺序数组因为是不断维护有序新的数组,出队直接取出队首即可,复杂度为O(1);而入队时则需要找到入队插入合适的顺序,复杂度为O(n)。

       用堆这种数据结构实现优先队列要比另外两种平均复杂度要低一些。

       最极端的情况下,对于总共N个请求,使用普通数组挥着书序数组,最差情况:O(n^2),而使用堆:O(nlgn)。

    二、堆的基本实现

        上面说到堆的时间复杂度为O(nlgn),可以想到堆是一个树型结构。最经典的实现就是二叉堆,这个二叉树有两个特点:
        1.二叉堆的父节点永远大于两个子节点(最大堆);
        2.二叉堆是一个完全二叉树(完全二叉树:除最后一层外,其余层都是满的,最后一层的子节点也都在最左边)。
        注意:二叉堆并不意味着层数越高数值越大!

    如图所示即为一个二叉堆:
    在这里插入图片描述

    用数组存储二叉堆

        因为二叉堆是一个完全二叉树,说以可以用数组来实现。当我们从上到下,从左到右对二叉堆的每一个结点进行编号时可以发现,左结点的序号是父节点的2倍,右节点是父节点序号的2倍+1。可以用如下公式就可以推出左右孩子结点的序号:
    在这里插入图片描述
        首先编写一个堆的骨架,代码如下(c++):

    template<typename T>
    class MaxHeap{
     public:
         // 构造函数, 构造一个空堆, 可容纳capacity个元素
        MaxHeap(int capacity){
            data = new T[capacity+1];//第一个元素不用
            count=0;//初始化数量为0
        }
    
        //析构函数释放数组
        ~MaxHeap(){
            delete[] data;
        }
    
        //获取堆元素的个数
        int size(){
            return count;
        }
    
        //堆是否为空
        bool isEmpty(){
            return count==0;
        }
        
        //获取元素值
        T getValue(int index){
            return data[index];
        }
    
     private:
        T* data;
        int count;//堆的元素个数
    };
    

    向最大堆中添加元素Shift Up

        向堆中添加一个元素相当于在数组最后添加一个元素,然后调节新加入元素的位置。

        Shift Up操作即新加入的元素不断的和自己的父节点进行比较,如果不符合堆的定义就与父节点进行交换。如下动图所示,最大堆中添加了一个52的结点:
    在这里插入图片描述

        在原代码中添加shiftUp(int k)函数,insert(T item)函数。insert(T item)函数为public,shiftUp(int k)函数为private。

        shiftUp(int k)函数主要代码:

    void shiftUp(int k){
        while( k>1 && data[k/2]<data[k]){
            swap(data[k/2],data[k]);
            k=k/2;
        }
    }
    

        insert(T item)函数主要代码(动态添加数组容量,容量增加原来的一半):

    //插入元素
    void insert(T item){
        //动态增加容量,当插入元素超过容量时,容量增加原来的一半
        if(count+1>capacity+1){
            T* data2=new T[capacity+1];
            for(int i=0;i<=count;i++){
                data2[i]=data[i];
            }
            
            if(capacity==1){
                capacity=capacity*2;
            }else{
                capacity=capacity+capacity/2;
            }
            data=new T[capacity+1];
            for(int i=1;i<=count;i++){
                data2[i]=data[i];
            }
            delete[] data2;
        }
        
        //插入元素
        data[count+1]=item;
        count++;
        shiftUp(count);//调整
    }
    

    向最大堆中取出元素Shift Down

        堆的出队只能取出优先级最大的元素,即根节点的元素。将最后一个元素放到根节点的位置,数量count减一,再将这个元素向下调整。

        Shift Down操作即将元素与它的左右孩子比较,当它比左右孩子小时,则与左右结点中较大的进行交换。如下动图所示,最大堆中取出元素:
    在这里插入图片描述
        在原代码中添加shiftDown(int k)函数,extractMax()函数。extractMax()函数为public,shiftDown(int k)函数为private。

        shiftDown(int k)函数主要代码:

    void shiftDown(int k){
    
        while(2*k<=count){//左结点小于元素数目
            //先比较左右两个结点,确定编号j
            int j=k*2;
            if(j+1<=count&&data[j+1]>data[j])
                j++;
            //如果当前结点比左右结点大,则退出循环
            if(data[k]>data[j])
                break;
            //否则data[k]和data[j]交换位置
            swap(data[k],data[j]);//(交换函数可以做优化,改成赋值的形式,具体见排序那篇博客)
            k=j;
        }
    
    }
    

        extractMax()函数主要代码:

    // 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
    T extractMax(){
        assert( count > 0 );//元素数目要大于0
        T ret=data[1];//要取出的数
        swap(data[1],data[count])//和最后一个元素交换(交换操作可以改成赋值操作)
        count--;
        shiftDown(1);
    
        return ret;
    }
    

    三、堆排序和Heapify

    基础堆排序

        实现堆排序可以将数组的元素全部插入到中,再将堆中的元素全部重新取出放入数组中即得到有序的队列。

       无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)。整个堆排序的整体时间复杂度为O(nlogn)。

    template<typename T>
    void heapSort1(T arr[], int n){
    
        MaxHeap<T> maxheap = MaxHeap<T>(n);
        for( int i = 0 ; i < n ; i ++ )
            maxheap.insert(arr[i]);
    
        for( int i = n-1 ; i >= 0 ; i-- )
            arr[i] = maxheap.extractMax();
    
    }
    

    优化的堆排序和Heapify

        将整个数组构建成一个堆有更好的方式,将一个数组构造成一个堆的过程叫做Heapify。

        假设有如下图所示的数组,这个数组自动形成了一个二叉树,但还不是二叉堆。可以观察得到:
        1.每一个叶子结点都构成一个堆(因为只有一个元素);
        2.第一个非叶子结点序号为总数count / 2(如下图所示,第一个非叶子结点序号为10/2=5)。

       Heapify的过程,从后向前的依次讨论不是叶子的结点,对它们依次进行Shift Down操作,使以改结点为根节点的二叉树为一个二叉堆。动画演示如下:
    在这里插入图片描述
       堆中重新编写一个构造函数,构造函数代码如下:

    // 构造函数, 通过一个给定数组创建一个最大堆
    // 该构造堆的过程, 时间复杂度为O(n)
    MaxHeap(T arr[], int n){
        data = new T[n+1];
        capacity=n;
        
        //数组的值赋值到data中
        for(int i=0;i<n;i++){
            data[i+1]=arr[i];
        }
        count=n;
        
        for(int i=count/2;i>=1;i--){
            shiftDown(i);
        }
    }
    

       重新编写堆排序的函数,heapSort2借助我们的heapify过程创建堆。

       此时, 创建堆的过程时间复杂度为O(n), 将所有元素依次从堆中取出来, 实践复杂度为O(nlogn)。

       堆排序的总体时间复杂度依然是O(nlogn), 但是比上述heapSort1性能更优, 因为创建堆的性能更优。heapSort2代码如下:

    template<typename T>
    void heapSort2(T arr[], int n){
    
        MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
        for( int i = n-1 ; i >= 0 ; i-- )
            arr[i] = maxheap.extractMax();
    
    }
    

       heapSort1的效率要比heapSort2的效率低,堆排序的效率还是不如归并排序和快速排序。

       结论:将n个元素逐个插入到一个空堆中,算法复杂度是O(nlogn),而Heapify的过程算法复杂度为O(n)。

    四、原地堆排序

       之前说到的堆排序是将数组依次插入到堆中进行排序,整个过程又额外增加了n个空间,而实际上排序过程完全可以在原地进行,不需要额外的空间。

       具体步骤:一个数组可以看成一个完全二叉树

       步骤一:先通过Heapify的过程构造成一个堆,设构造后的堆第一个元素是v,v即是最大的元素,设最后一个元素为w,将v和w交换,此时蓝色的区域是排序的部分,橙色的部分便不满足二叉堆。
       步骤二:对橙色部分进行Shift Down操作使之变成一个二叉堆,此时该部分变成红色,继续执行步骤一。

       执行步骤如下图所示:
    在这里插入图片描述
       二叉堆的序号变成从0开始,最后一个飞叶子结点的索引为(count-2)/2,使用的索引公式如下图所示:
    在这里插入图片描述
    原地堆排序heapSort3实现代码如下:

    // n:数组元素个数,k:对第k个元素进行shiftDown
    // 相对于shiftDown()只是索引从0开始了
    template<typename T>
    void shiftDown2(T arr[], int n, int k){
        while(2*k+1<=n){//左结点小于元素数目
            //先比较左右两个结点,确定编号j
            int j=k*2+1;
            if(j+1<n&&arr[j+1]>arr[j])
                j++;
            //如果当前结点比左右结点大,则退出循环
            if(arr[k]>arr[j])
                break;
            //否则data[k]和data[j]交换位置
            swap(arr[k],arr[j]);
            k=j;
        }
    }
    
    // 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序
    template<typename T>
    void heapSort3(T arr[], int n){
        // 注意,此时我们的堆是从0开始索引的
        // 从(最后一个元素的索引-1)/2开始
        // 最后一个元素的索引 = n-1
        for(int i=(n-2)/2;i>=0;i--){// Heapify的过程构造成一个堆
            shiftDown2(arr, n, i);
        }
    
        for(int i=n-1;i>0;i--){
            swap(arr[0],arr[i]);
            shiftDown2(arr, i, 0);
        }
    }
    

    五、排序算法总结

    平均时间复杂度原地排序额外空间稳定排序
    插入排序O(n^2)O(1)
    归并排序O(nlogn)O(n)
    快速排序O(nlogn)O(logn)
    堆排序O(nlogn)O(1)

    排序算法的稳定性
       稳定排序:对于相等元素,排序后,原来靠前的元素依然靠前。即相等元素的相对位置没有发生变化。如图所示:
    在这里插入图片描述
       插入排序是稳定的,是因为元素在和前一个元素比较时,当与前一个元素相等时就不进行交换了。动画演示如下图:
    在这里插入图片描述
       归并排序是稳定的,是因为元素在比较时,如果遇到相同的元素,就先和左边的元素替换。如下图所示,左右的元素3是相同的,先替换左边的3。动画演示如下图:
    在这里插入图片描述

    六、索引堆

    堆的局限性

       1.在一般的堆中,需要经常交换两个元素。如果元素十分复杂,比如每个位置上存的是一篇10万字的文章。那么交换它们的位置将产生大量的时间消耗。
       2.由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。
       3.可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效)
       于是就有了索引堆的概念。

    索引堆的基本实现

       将数据和索引分开存储,真正构建堆的是由索引构成的。如下图所示,圆圈里存的是索引号:
    在这里插入图片描述
       当数组变成堆时,就变成了如下图所示:
    在这里插入图片描述
       数组对应的数据data没有改变,改变的是索引index的值。上图index的顺序就是堆的顺序,堆的元素索引为10,对应的元素值为62,以此类推。

    索引堆的两大重要特点
    1 比较的是真实元素的大小,交换的是对应的索引index的位置,真实的data数组并没有任何改变。数据和索引是分开存储的,这意味着索引数组是连续的,数据数组可以不连续。这一点我想了好久才明白(汗。。。)
    2 访问数据元素,必须先找到其索引,即先找到index[]的值
    注意:data[]数组我们是从1开始存储的,但是真实的索引是从0开始的

       索引堆在图论算法中求最短路径以及最小生成树中都有应用。

       相对于基本堆的实现,索引堆比它多了索引数组

    int *indexes;   // 最大索引堆中的索引数组
    

       相应的构造函数和析构函数对索引数组进行初始化:

    // 构造函数, 构造一个空的索引堆, 可容纳capacity个元素
    IndexMaxHeap(int capacity){
    
        data = new Item[capacity+1];
        indexes = new int[capacity+1];
    
        count = 0;
        this->capacity = capacity;
    }
    
    ~IndexMaxHeap(){
        delete[] data;
        delete[] indexes;
    }
    

    插入函数:(数据和索引是分开存储的,这意味着索引数组是连续的,数据数组可以不连续)

    // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
    void shiftUp( int k ){
    
        //先拿到索引,再比较索引对应的数据
        while( k > 1 && data[indexes[k/2]] < data[indexes[k]] ){
            swap( indexes[k/2] , indexes[k] );
            k /= 2;
        }
    }
    
    // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
    // 传入的i对用户而言,是从0索引的
    void insert(int i, T item){
        assert( count + 1 <= capacity );
        assert( i + 1 >= 1 && i + 1 <= capacity );
    
        i += 1;//从1开始索引
        data[i] = item;//数据数组可能不是连续的
        indexes[count+1] = i;//索引数组是连续的
        count++;
    
        shiftUp(count);
    }
    

    取出元素:

    // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
    void shiftDown( int k ){
    
        while( 2*k <= count ){
            int j = 2*k;
            if( j + 1 <= count && data[indexes[j+1]] > data[indexes[j]] )
                j += 1;
    
            if( data[indexes[k]] >= data[indexes[j]] )
                break;
    
            swap( indexes[k] , indexes[j] );
            k = j;
        }
    }
    
    // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据
    T extractMax(){
        assert( count > 0 );
    
        T ret = data[indexes[1]];//找到索引对应的值
        swap( indexes[1] , indexes[count] );//将索引交换
        count--;
        shiftDown(1);
        return ret;
    }
    

    最大索引堆的一些特殊操作:
    取出最大元素的索引:

    // 从最大索引堆中取出堆顶元素的索引
    int extractMaxIndex(){
        assert( count > 0 );
    
        int ret = indexes[1] - 1;// 对用户而言,是从0索引的
        swap( indexes[1] , indexes[count] );
        count--;
        shiftDown(1);
        return ret;
    }
    

    将最大索引堆中索引为i的元素修改为newItem,修改后元素先找到其在堆中对应的位置,再先后进行shiftUp和shiftDown操作,代码如下:

    // 将最大索引堆中索引为i的元素修改为newItem
        void change( int i , T newItem ){
    
            i += 1;//索引堆是从1开始的
            data[i] = newItem;
    
            // 找到indexes[j] = i, j表示data[i]在堆中的位置
            // 之后shiftUp(j), 再shiftDown(j)
            for( int j = 1 ; j <= count ; j ++ )//遍历整个索引数组,找到data[i]在堆中的位置j
                if( indexes[j] == i ){
                    shiftUp(j);
                    shiftDown(j);
                    return;
                }
        }
    

    索引堆的优化

       上面将最大索引堆中索引为i的元素修改为newItem的过程中,重新维护index数组时是遍历整个index数组找到该索引在堆中的位置。

       其实可以用反向查找的思路,单独设置一个rev数组来存放索引在堆中的位置。如下图所示,索引1在堆中的位置就是8,以此类推:
    在这里插入图片描述
       rev数组和index数组之间的关系如下图所示:
    在这里插入图片描述
    在原来索引堆中添加反向索引数组:

    int *reverse;   // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    

    相应的构造函数和析构函数修改:

    // 构造函数, 构造一个空的索引堆, 可容纳capacity个元素
    IndexMaxHeap(int capacity){
    
        data = new Item[capacity+1];
        indexes = new int[capacity+1];
        reverse = new int[capacity+1];
        for( int i = 0 ; i <= capacity ; i ++ )
            reverse[i] = 0;//堆从索引为1开始,所以初始化为0是表示不存在
    
        count = 0;
        this->capacity = capacity;
    }
    
    ~IndexMaxHeap(){
        delete[] data;
        delete[] indexes;
        delete[] reverse;
    }
    

    插入函数:

    // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
        // 传入的i对用户而言,是从0索引的
        void insert(int i, Item item){
            assert( count + 1 <= capacity );
            assert( i + 1 >= 1 && i + 1 <= capacity );
    
            // 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。
            assert( !contain(i) );
    
            i += 1;
            data[i] = item;
            indexes[count+1] = i;
            reverse[i] = count+1;
            count++;
    
            shiftUp(count);
        }
    

    取出元素对应的代码如下:

    // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
    void shiftDown( int k ){
    
        while( 2*k <= count ){
            int j = 2*k;
            if( j + 1 <= count && data[indexes[j+1]] > data[indexes[j]] )
                j += 1;
    
            if( data[indexes[k]] >= data[indexes[j]] )
                break;
    
            swap( indexes[k] , indexes[j] );
            reverse[indexes[k]] = k;
            reverse[indexes[j]] = j;
            k = j;
        }
    }
    
    // 从最大索引堆中取出
    堆顶元素, 即索引堆中所存储的最大数据
    Item extractMax(){
        assert( count > 0 );
    
        Item ret = data[indexes[1]];
        swap( indexes[1] , indexes[count] );
        reverse[indexes[count]] = 0;
        reverse[indexes[1]] = 1;
        count--;
        shiftDown(1);
        return ret;
    }
    

    将最大索引堆中索引为i的元素修改为newItem,change函数如下:

        // 看索引i所在的位置是否存在元素
        bool contain( int i ){
            assert( i + 1 >= 1 && i + 1 <= capacity );
            return reverse[i+1] != 0;
        }
    
    // 将最大索引堆中索引为i的元素修改为newItem
        void change( int i , Item newItem ){
    
            assert( contain(i) );
            i += 1;
            data[i] = newItem;
    
            // 找到indexes[j] = i, j表示data[i]在堆中的位置
            // 之后shiftUp(j), 再shiftDown(j)
    //        for( int j = 1 ; j <= count ; j ++ )
    //            if( indexes[j] == i ){
    //                shiftUp(j);
    //                shiftDown(j);
    //                return;
    //            }
    
            // 有了 reverse 之后,
            // 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置
            shiftUp( reverse[i] );
            shiftDown( reverse[i] );
        }
    

    七、和堆相关的问题

       回到之前说的那几个问题,操作系统中可以使用堆实现动态选择优先级最高的任务执行,当有新任务加入时就把其加入到堆中。
       在10000个元素中找到前100个元素,可以将这10000个元素依次插入到容量为100的最小堆中,当放满元素后,每插入一个元素,将最小的元素出队,这样遍历完10000个元素后,堆中的元素就是前100的元素。

       以后要解决的问题:多路归并排序,最大最小队列(同时得到最大值和最小值)(同时拥有一个最大堆和一个最小堆),二项堆,斐波那契堆???

    附录:

    堆的完整代码:

    template<typename T>
    class MaxHeap{
      private:
        T* data;
        int count;//堆的元素个数
        int capacity;
    
        void shiftUp(int k){
            while( k>1 && data[k/2]<data[k]){
                swap(data[k/2],data[k]);
                k=k/2;
            }
        }
    
        void shiftDown(int k){
    
            while(2*k<=count){//左结点小于元素数目
                //先比较左右两个结点,确定编号j
                int j=k*2;
                if(j+1<=count&&data[j+1]>data[j])
                    j++;
                //如果当前结点比左右结点大,则退出循环
                if(data[k]>data[j])
                    break;
                //否则data[k]和data[j]交换位置
                swap(data[k],data[j]);
                k=j;
            }
    
        }
     
     public:
         // 构造函数, 构造一个空堆, 可容纳capacity个元素
        MaxHeap(int capacity){
            data = new T[capacity+1];//第一个元素不用
            count=0;//初始化数量为0
            this->capacity = capacity;
        }
        
        // 构造函数, 通过一个给定数组创建一个最大堆
        // 该构造堆的过程, 时间复杂度为O(n)
        MaxHeap(T arr[], int n){
            data = new T[n+1];
            capacity=n;
            
            //数组的值赋值到data中
            for(int i=0;i<n;i++){
                data[i+1]=arr[i];
            }
            count=n;
            
            for(int i=count/2;i>=1;i--){
                shiftDown(i);
            }
        }
    
        //析构函数释放数组
        ~MaxHeap(){
            delete[] data;
        }
    
        //获取堆元素的个数
        int size(){
            return count;
        }
    
        //堆是否为空
        bool isEmpty(){
            return count==0;
        }
    
        //获取元素值
        T getValue(int index){
            return data[index];
        }
    
        //插入元素
        void insert(T item){
    	    //动态增加容量,当插入元素超过容量时,容量增加原来的一半
    	    if(count+1>capacity+1){
    	        T* data2=new T[capacity+1];
    	        for(int i=0;i<=count;i++){
    	            data2[i]=data[i];
    	        }
    	        
    	        if(capacity==1){
    	            capacity=capacity*2;
    	        }else{
    	            capacity=capacity+capacity/2;
    	        }
    	        data=new T[capacity+1];
    	        for(int i=1;i<=count;i++){
    	            data2[i]=data[i];
    	        }
    	        delete[] data2;
    	    }
    	    
    	    //插入元素
    	    data[count+1]=item;
    	    count++;
    	    shiftUp(count);//调整位置
    	}
    
        // 从最大堆中取出堆顶元素, 即堆中所存储的最大数据
        T extractMax(){
            assert( count > 0 );//元素数目要大于0
            T ret=data[1];//要取出的数
            swap(data[1],data[count]);
            count--;
            shiftDown(1);
    
            return ret;
        }
    };
    

    heapSort1(T arr[], int n):

    template<typename T>
    void heapSort1(T arr[], int n){
    
        MaxHeap<T> maxheap = MaxHeap<T>(n);
        for( int i = 0 ; i < n ; i ++ )
            maxheap.insert(arr[i]);
    
        for( int i = n-1 ; i >= 0 ; i-- )
            arr[i] = maxheap.extractMax();
    
    }
    

    heapSort2(T arr[], int n):

    template<typename T>
    void heapSort2(T arr[], int n){
    
        MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
        for( int i = n-1 ; i >= 0 ; i-- )
            arr[i] = maxheap.extractMax();
    
    }
    

    heapSort3(T arr[], int n):

    // n:数组元素个数,k:对第k个元素进行shiftDown
    // 相对于shiftDown()只是索引从0开始了
    template<typename T>
    void shiftDown2(T arr[], int n, int k){
        while(2*k+1<=n){//左结点小于元素数目
            //先比较左右两个结点,确定编号j
            int j=k*2+1;
            if(j+1<n&&arr[j+1]>arr[j])
                j++;
            //如果当前结点比左右结点大,则退出循环
            if(arr[k]>arr[j])
                break;
            //否则data[k]和data[j]交换位置
            swap(arr[k],arr[j]);
            k=j;
        }
    }
    
    // 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序
    template<typename T>
    void heapSort3(T arr[], int n){
        // 注意,此时我们的堆是从0开始索引的
        // 从(最后一个元素的索引-1)/2开始
        // 最后一个元素的索引 = n-1
        for(int i=(n-2)/2;i>=0;i--){// Heapify的过程构造成一个堆
            shiftDown2(arr, n, i);
        }
    
        for(int i=n-1;i>0;i--){
            swap(arr[0],arr[i]);
            shiftDown2(arr, i, 0);
        }
    }
    

    索引堆的完整代码:

    // 最大索引堆
    template<typename Item>
    class IndexMaxHeap{
    
    private:
        Item *data;     // 最大索引堆中的数据
        int *indexes;   // 最大索引堆中的索引, indexes[x] = i 表示索引i在x的位置
        int *reverse;   // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    
        int count;
        int capacity;
    
        // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
        void shiftUp( int k ){
    
            while( k > 1 && data[indexes[k/2]] < data[indexes[k]] ){
                swap( indexes[k/2] , indexes[k] );
                reverse[indexes[k/2]] = k/2;
                reverse[indexes[k]] = k;
                k /= 2;
            }
        }
    
        // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
        void shiftDown( int k ){
    
            while( 2*k <= count ){
                int j = 2*k;
                if( j + 1 <= count && data[indexes[j+1]] > data[indexes[j]] )
                    j += 1;
    
                if( data[indexes[k]] >= data[indexes[j]] )
                    break;
    
                swap( indexes[k] , indexes[j] );
                reverse[indexes[k]] = k;
                reverse[indexes[j]] = j;
                k = j;
            }
        }
    
    public:
        // 构造函数, 构造一个空的索引堆, 可容纳capacity个元素
        IndexMaxHeap(int capacity){
    
            data = new Item[capacity+1];
            indexes = new int[capacity+1];
            reverse = new int[capacity+1];
            for( int i = 0 ; i <= capacity ; i ++ )
                reverse[i] = 0;
    
            count = 0;
            this->capacity = capacity;
        }
    
        ~IndexMaxHeap(){
            delete[] data;
            delete[] indexes;
            delete[] reverse;
        }
    
        // 返回索引堆中的元素个数
        int size(){
            return count;
        }
    
        // 返回一个布尔值, 表示索引堆中是否为空
        bool isEmpty(){
            return count == 0;
        }
    
        // 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
        // 传入的i对用户而言,是从0索引的
        void insert(int i, Item item){
            assert( count + 1 <= capacity );
            assert( i + 1 >= 1 && i + 1 <= capacity );
    
            // 再插入一个新元素前,还需要保证索引i所在的位置是没有元素的。
            assert( !contain(i) );
    
            i += 1;
            data[i] = item;
            indexes[count+1] = i;
            reverse[i] = count+1;
            count++;
    
            shiftUp(count);
        }
    
        // 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据
        Item extractMax(){
            assert( count > 0 );
    
            Item ret = data[indexes[1]];
            swap( indexes[1] , indexes[count] );
            reverse[indexes[count]] = 0;
            reverse[indexes[1]] = 1;
            count--;
            shiftDown(1);
            return ret;
        }
    
        // 从最大索引堆中取出堆顶元素的索引
        int extractMaxIndex(){
            assert( count > 0 );
    
            int ret = indexes[1] - 1;
            swap( indexes[1] , indexes[count] );
            reverse[indexes[count]] = 0;
            reverse[indexes[1]] = 1;
            count--;
            shiftDown(1);
            return ret;
        }
    
        // 获取最大索引堆中的堆顶元素
        Item getMax(){
            assert( count > 0 );
            return data[indexes[1]];
        }
    
        // 获取最大索引堆中的堆顶元素的索引
        int getMaxIndex(){
            assert( count > 0 );
            return indexes[1]-1;
        }
    
        // 看索引i所在的位置是否存在元素
        bool contain( int i ){
            assert( i + 1 >= 1 && i + 1 <= capacity );
            return reverse[i+1] != 0;
        }
    
        // 获取最大索引堆中索引为i的元素
        Item getItem( int i ){
            assert( contain(i) );
            return data[i+1];
        }
    
        // 将最大索引堆中索引为i的元素修改为newItem
        void change( int i , Item newItem ){
    
            assert( contain(i) );
            i += 1;
            data[i] = newItem;
    
            // 找到indexes[j] = i, j表示data[i]在堆中的位置
            // 之后shiftUp(j), 再shiftDown(j)
    //        for( int j = 1 ; j <= count ; j ++ )
    //            if( indexes[j] == i ){
    //                shiftUp(j);
    //                shiftDown(j);
    //                return;
    //            }
    
            // 有了 reverse 之后,
            // 我们可以非常简单的通过reverse直接定位索引i在indexes中的位置
            shiftUp( reverse[i] );
            shiftDown( reverse[i] );
        }
    
    };
    
    展开全文
  • 【经典排序算法】堆排序(动图演示 + C 语言实现) 文章目录【经典排序算法】堆排序(动图演示 + C 语言实现)1、动图演示2、排序思想3、时间/空间复杂度4、代码实现 (C语言) 1、动图演示 2、排序思想   堆它是...

    【经典排序算法】堆排序(动图演示 + C 语言代码实现)

      👩‍💻 👉 【经典排序算法】十大经典排序算法汇总篇


    1、动图演示

    在这里插入图片描述


    2、排序思想

      堆它是选择排序的一种,它是通过堆来进行选择数据。(升序要建大堆,降序建小堆)

      首先是创建堆,即把待排序的序列转换成堆的形式。然后将根节点与最后一个节点交换。交换之后会打乱堆的规律,需要对前(n-1)个节点进行调整,使之重新成为堆。接下来交换根节点与倒数第二个节点 …… 重复上述调整,直到序列有序为止。(建堆、交换、调整)


    3、时间/空间复杂度

      时间复杂度: O ( n log ⁡ n ) O(n\log_n) O(nlogn)

      空间复杂度: O ( 1 ) O(1) O(1)

      稳定性:不稳定


    4、代码实现 (C语言)

    #define LeftChild(i) (2 * (i) + 1)     //做孩子节点
    //将以节点 arr[i] 为根节点的构成最小堆 
    void HeapAdjust(int arr[], int i, int size)
    {
    	int child, temp;
    	for (temp = arr[i]; LeftChild(i) < size; i = child)
    	{
    		child = LeftChild(i);
    		if (child != size - 1 && arr[child + 1] < arr[child])
    			child++;                   //选出左右孩子节点中小的节点
    		if (temp > arr[child])         //将较小的孩子节点换到父节点位置
    			arr[i] = arr[child];
    		else
    			break;
    	}
    	arr[i] = temp;
    }
    void HeapSort(int arr[], int size)
    {
    	int i, temp;
    	//初始化堆
    	for (i = size / 2 - 1; i >= 0; i--)
    		HeapAdjust(arr, i, size);
    	//将最后一个元素与堆顶元素交换,重建堆
    	for (i = size - 1; i > 0; i--)
    	{
    		temp = arr[0];      
    		arr[0] = arr[i];
    		arr[i] = temp;
    		HeapAdjust(arr, 0, i);
    	}
    }
    

    展开全文
  • 堆排序基于二叉堆结构即完全二叉树,可利用最大堆和最小堆的组建方式来进行排序,这里就来深入解析堆排序的算法思想及Java代码的实现演示
  • 本人编写的堆排序及堆的插入删除等操作演示,用的是java swing,详情可以查看 http://blog.csdn.net/cdnight/article/details/11714005 假如您对堆排序不是很熟悉,可以查看 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,547
精华内容 9,018
关键字:

堆排序演示