zoukankan      html  css  js  c++  java
  • 堆排序

     【基本思想】

      将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值,然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复执行,便能得到一个有序序列了。

      堆:具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

    同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子

    该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

    大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

    小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

    步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

      a.假设给定无序序列结构如下

    2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

    4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

    这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

    此时,我们就将一个无需序列构造成了一个大顶堆。

    步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

    a.将堆顶元素9和末尾元素4进行交换

    b.重新调整结构,使其继续满足堆定义

    c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

    后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

    再简单总结下堆排序的基本思路:

      a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

      b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

      c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

    【算法复杂度】 

    时间复杂度(平均)时间复杂度 (最坏)时间复杂度(最好)空间复杂度稳定性
    O(nlgn) O(nlgn)      O(nlgn) O(1) 不稳定

    时间复杂度>>>

    堆排序的时间,主要是消耗在构建堆和在重建堆时的反复筛选上。

    在构建堆的过程,因为我们是从完全二叉树最下层的非叶子结点开始构建的,将它与其孩子结点进行比较和有必要的互换,对于每个非叶子结点来说,其实最多2次比较和互换,故初始化堆的时间复杂度为O(n)。

    在正式排序的时候,第i次取堆顶记录和重建堆需要O(logi)的时间(完全二叉树的某个结点到根结点的距离为logi+1),并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。所以总的来说,堆排序的时间复杂度为O(nlogn)。

    算法稳定性>>>

    它是不稳定的排序方法

     

    【动图演示】

     

    【算法实现】

    /*
    ** 堆排序的C++实现
    ** 假设:对N个整数进行升序排序
    ** HeapAdjust()函数用于调整大根堆
    ** 变量index为要调的整的父节点下标
    ** 变量j为要调整的子节点下标
    */
    
    void HeapAdjust(vector<int>&seq, int index, int len){
        seq[0] = seq[index];    // 将要调整的结点的值存储到临时结点中
        for(int j = 2 * index; j <= len; j *= 2){ // 循环的调整父节点与子节点的位置关系,使堆满足定义
            if(j < len && seq[j] < seq[j + 1]) ++j;  // 选择子节点中较大的那一个
            if(seq[0] >= seq[j]) break;  // 当父节点大于等于两个子节点时,跳出
            seq[index] = seq[j];
            index = j;
        }
        seq[index] = seq[0];  // 将要移动的结点放到正确的位置
    }
    
    void HeapSort(vector<int>& seq){
        int i,length = seq.size();
        seq.insert(seq.begin(),0);  // 在序列最前面创建一个结点用来存储临时值
        for(i = length/2; i>0; i--)  // 选择最后一个非叶结点,构造大根堆
            HeapAdjust(seq, i, length);
        for(i = length; i>1; i--){  // 将根结点与无序序列的最后一个结点交换,并调整大根堆,重复这个步骤
            seq[1] ^= seq[i];
            seq[i] ^= seq[1];
            seq[1] ^= seq[i];
            HeapAdjust(seq, 1, i-1);
        }
        seq.erase(seq.begin()); // 删除临时结点
    }
  • 相关阅读:
    flask 实现增删改查
    juniper srx系列配置端口映射 转载
    form(form基础、标签渲染、错误显示 重置信息、form属性、局部钩子、全局钩子)
    postfix “ Sender address rejected: not logged in”
    Python 学习博客地址
    分页器(分页器基本操作、点击按钮分页、美化分页器)
    yuan先生博客地址
    Ajax(form表单文件上传、请求头之contentType、Ajax传递json数据、Ajax文件上传)
    Mysql容器启动失败-解决方案
    Ajax(简介、基础操作、计算器,登录验证)
  • 原文地址:https://www.cnblogs.com/nkqlhqc/p/9776228.html
Copyright © 2011-2022 走看看