zoukankan      html  css  js  c++  java
  • 常用排序算法(2)

    常用排序算法(2) - 堆排序,归并排序

    堆排序

    算法描述

    堆排序的实现就是利用优先队列的原理达到O(n log n)的复杂度进行排序。那么对于一个无序数组,首先我们要做的是建立一个二叉堆,这个阶段需要O(n)的复杂度。然后对这个已经建立好的堆进行deleteMax操作,就可以把最大元素移除放到另外一个数组里,那么只要循环n次就可以得到一个排好序的新队列。每次deleteMax操作的复杂度是O(log n),所以n次循环就是O(n log n)的复杂度。

    同时可以看出来,如果每次删除一个元素,那么数组就会空出一个位置,如果这个删除不是真的删除,而是放到最后一个空间,那么就不需要再用额外的数组,所以可以把建堆的操作和deleteMax操作都当做一个下滤的操作来处理。

    • 建堆流程:

      • 找出第一个非叶子节点,并依次往前遍历并把比子节点小的数字下滤
      • 直到根节点,此时,所有节点都将比其两个子节点要大
      • 完成一个最大堆的建立
    • 下滤流程:

      • 找出x节点的子节点
      • 找出子节点中较大的节点,如果该节点比当前节点大,交换
      • 经过交换后,可以保证该节点比两个子节点都要大,而更小的节点是子节点之一
      • 然后再把更小的节点作为当前节点,循环上面的步骤
      • 每次操作都可以保证更小的节点会被过滤向后,所以当所有操作完成后,可以保证从x到队列尾部的最小节点是叶子节点
    • 排序流程:

      • 先建立好堆,保证最大的元素在队列头部,最小的元素在队列尾部(n-1位置)
      • 交换队列头尾,此时可以保证最大元素在队列末尾(n-1位置)
      • 然后n减小,重复以上步骤,直到n为1时,可以把整个数组排序完毕

    实现

    //堆排序
    template <typename Comparable>
    void HeapSort(vector<Comparable>& a)
    {
        //先通过下滤操作创建一个堆
        for (int i = a.size() / 2 - 1; i >= 0; --i)
            percDown(a, i, a.size());
    
        //将当前最大元素摆放到队列末尾,并通过一次下滤操作重新构建堆
        for (int j = a.size() - 1; j > 0; --j)
        {
            std::swap(a[0], a[j]);
            percDown(a, 0, j);
        }
    
    }
    
    int leftChile(int x)
    {
        return 2 * x + 1;
    }
    
    template <typename Comparable>
    void percDown(vector<Comparable>& a, int i, int n)
    {
        int child = 0;
        Comparable tmp = std::move(a[i]);
    
        for (; leftChild(i) < n; i = child)
        {
            child = leftChild(i);
            if (child < n - 1 && a[child] < a[child + 1]) ++child;
            if (tmp < a[child]) a[i] = std::move(a[child]);
            else break;
        }
        a[i] = std::move(tmp);
    }
    

    归并排序

    算法描述

    归并排序也是一个O(n log n)复杂度的排序算法,而且使用的比较次数几乎是最优的。算法的基本操作是合并两个已经有序的数组,可以只通过一趟遍历,每次取两个数组中最前的元素中较小的一个,并放入第三个数组中,当两个数组都为空时,第三个数组就完成了合并。这样,单独两个有序数组的合并可以看得出来时间是线性的。

    • 如果数组长度n = 1, 此时只有一个元素,那么该数组必然是有序的。
    • 如果长度大于1,递归的将前半部分和后半部分各自归并排序,得到排序后的两部分数据
    • 对这两部分数据进行合并,使用上述遍历完成的方法即可。

    算法是经典的分治算法实现,将大问题分为更小部分的问题,最后再将结果结合起来,最终得到大问题的解法。

    //归并排序
    template <typename Comparable>
    void mergeSort(vector<Comparable>& a)
    {
        vector<Comparable> tmp(a.size());
        mergeSort(a, 0, a.size() - 1);
    }
    
    //内部方法
    template <typename Comparable>
    void mergeSort(vector<Comparable>& a, vector<Comparable>& tmp, int left, int right)
    {
        if (left >= right) return;
    
        int mid = (left + right) / 2;
        mergeSort(a, tmp, left, mid);
        mergeSort(a, tmp, mid + 1, right);
        merge(a, tmp, left, mid + 1, right);
    }
    
    template <typename Comparable>
    void merge(vector<Comparable>& a, vector<Comparable>& tmp, int left, int mid, int right)
    {
        int rightPos = mid;
        int tmpPos = left;
        int num = right - left + 1;
    
        //挑选前后部分中数值较小的那个
        while (left < mid && rightPos <= right)
        {
            if (a[left] <= a[rightPos]) tmp[tmpPos++] = std::move(a[left++]);
            else tmp[tmpPos++] = std::move(a[rightPos++]);
        }
    
        //把剩余数字放入到输出数组
        while (left < mid) tmp[tmpPos++] = std::move(a[left++]);
        while (rightPos <= right) tmp[tmpPos++] = std::move(a[rightPos++]);
    
        //复制回原数组
        for (int i = 0; i < num; ++i, --right)
        {
            a[right] = std::move(tmp[right]);
        }
    }
    

    后记

    在JAVA中,泛型排序的比较操作代价(比较不容易内联)比移动操作要大(因为元素是引用的赋值,而不是对象拷贝),由于归并排序使用的比较次数是最少的,所以标准JAVA库中默认使用归并排序。而对于C++来说,默认情况下拷贝对象的代价是比较大的,而对象比较相对代价没有这么大(编译器会主动内联),所以通常来说尽量使用拷贝少的排序算法(例如STL中的快速排序),而C++11的移动语义可能会改变这种现状。

    --摘自《数据结构与算法分析——C++语言描述》

  • 相关阅读:
    【转】Eclipse中查看jar包中的源码
    maven No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
    Android问题-DelphiXE8新建AVD出现“no system images installed for this target”
    Android教程-DelphiXE Android自适应屏幕办法
    教程-在F9后提示内存错误,点击了乎略,之后怎么取消乎略?
    PC问题-该虚拟机似乎正在使用中
    PC问题-(仅供备用)取消磁盘的自动扫描
    Delphi实例-IdTCPServer和IdTCPClient的使用(支持文件发送)
    Android教程-DelphiXE Android的所有权限按照分类总结说明
    C++问题-UniqueAppObject.cpp(147): error C3861: “GUXClientInit”: 找不到标识符
  • 原文地址:https://www.cnblogs.com/zhqherm/p/12008692.html
Copyright © 2011-2022 走看看