• 算法【排序一】


    算法系统学习首篇之排序

    排序在商业数据处理和现代科学计算中的重要性不言而喻。它能够应用于日常事物处理、组合优化、天体物理学、分子动力学、语言学、基因组学、天气预报和其他相关领域。 20世纪科学与工程领域的十大算法之一就是一种排序算法——快速排序。

    在标准库中已经实现排序函数,再学习排序算法仍有重要实际意义。再重温排序算法之前,我并没有意识到。

    1. 对排序算法的分析将有助于全面理解比较算法性能的方法。
    2. 类似的算法思想也能有效解决其他类型问题。
    3. 排序算法常常是解决其他问题的第一步。

    初级排序(选择排序,冒泡排序,插入排序)

    • 选择排序

      这是一种最简单的排序算法: 首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置。其次,在除去第一个元素剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,知道整个数组排序。该方法不断地选择剩余元素中的最小者。

      排序算法类模板的基本API接口包括:排序函数sort(),比较函数less(), 交换函数exch(),判断有序性函数isSorted()以及打印输出函数print().

    #include <iostream>
    using namespace std;
    
    template <class T> class SortMethod
    {
    public:
        SortMethod(){}
        ~SortMethod(){}
        static bool isSortd(T a[] ,int N)
        {
            for(int i = 1;i < N; i++)
                if(less(a[i],a[i-1]))
                    return false;
    
            return true;
        }
    
        static void print(T a[], int N)
        {
            for (int i = 0 ; i < N; i++)
            {
                cout<< a[i]<<" ";
            }
            cout<<endl;
        }
    
        static void select_sort(T a[], int N); // 选择排序
        static void bubble_sort(T a[], int N); // 冒泡排序
        static void insert_sort(T a[], int N); // 插入排序
    
    private:
        static bool less(T a, T b)
        {
            return a < b;
        }
    
        static void exch(T a[], int i, int j)
        {
            T t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    
    };
    
    // 选择排序
    template<class T> void SortMethod<T>::select_sort(T a[], int N)
    {
        for (int i = 0 ; i < N; i++)
        {
            int min = i;
            for(int j = i+1; j < N; j++)
                if(less(a[j],a[min])) min = j;
    
            exch(a,i,min);
        }
    }
        调用方法:
    
        char ch[12] = {'S','O','R','T','E','X','A','M','P','L','E'};
        SortMethod<char>::print(ch,11);
        SortMethod<char>::select_sort(ch,11);
        bool sorted = SortMethod<char>::isSortd(ch,11);
        SortMethod<char>::print(ch,11);
        cout<<"sorted: "<<sorted<<endl;
    
        int a[10] = {8,9,1,7,2,3,5,4,6,0};
        SortMethod<int>::print(a,10);
        SortMethod<int>::select_sort(a,10);
        sorted = SortMethod<int>::isSortd(a,10);
        SortMethod<int>::print(a,10);
        cout<<"sorted: "<<sorted<<endl;
        输出 :
        S O R T E X A M P L E
        A E E L M O P R S T X
        sorted: 1
        8 9 1 7 2 3 5 4 6 0
        0 1 2 3 4 5 6 7 8 9
        sorted: 1
        请按任意键继续. . .
    

    结论: 对于长度为N的数组,选择排序大约需要 N2/2 N 2 / 2 次比较和 N N 次交换。


    • 冒泡排序

      不同于选择排序,冒泡排序每次比较相邻的元素。如果第一个比第二个大,就交换他们两个。
      对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
      针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。

    // 冒泡排序
    template<class T> void SortMethod<T>::bubble_sort(T a[], int N)
    {
        for (int i = 0; i < N; i++)
        {
            for(int j = 1; j < N-i; j++)
            {
                if(less(a[j],a[j-1]))
                    exch(a,j-1,j);
            }
        }
    }

    结论: 对于长度为N的数组,冒泡排序大约需要N2/2次比较,最坏情况下,同样需要 N2/2 N 2 / 2 次交换。


    • 插入排序

      在玩扑克牌的时候,我们一般都是使用的插入排序思想。即将每一张牌插入到其他已经有序的牌中的适当位置。在计算机实现中,为了要给新插入的元素腾出位置,需要将比待插入元素大的其他所有元素都向右移动一位。

    
    // 插入排序
    template<class T> void SortMethod<T>::insert_sort(T a[], int N)
    {
        for(int i = 1; i < N; i++ )
        {
            // 将a[i] 插入到 a[i-1], a[i-2],a[i-3],...之中
            for(int j = i; j > 0 && less(a[j],a[j-1]); j--)
            {
                exch(a,j,j-1); // 细细体会,很有意思的过程
            }
        }
    }
    

    结论: 对于随机排列的长度为N的不重复数组,平均情况下插入排序需要~ N2/4 N 2 / 4 次比较以及~ N2/4 N 2 / 4 次的交换。最坏情况下与冒泡法相同。


    • 希尔排序

      希尔排序是一种基于插入排序的快速的排序算法。对于大规模的乱序数组插入排序很慢,因为它只会交换相邻的元素。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,最最终用插入排序将局部有序的数组排序。
      希尔排序的思想是是数组中任意间隔为h的元素都是有序的。这样的数组成为h有序数组。比如说,h=3, 则 0 3 6 … 3*n 等位置的元素是有序的, 1 4 7 … 3*n+1 位置元素是有序的, 2 5 8 … 3*n+2等位置的元素同样也是有序的。一个h有序数组实际上是有h个有序的子数组组成的数组。
      希尔排序更高效的原因是它权衡了子数组的规模和有序性。透彻理解希尔排序的性能至今任然是一项挑战。我们先看算法实现,再来讨论。

    // 希尔排序
    template<class T> void SortMethod<T>::shell_sort(T a[], int N)
    {
        int h = 1;
        while(h < N/3) h = 3*h + 1;
        while(h >= 1)
        {
            // 将数组变为h有序
            for(int i = h; i < N; i++)
            {
                // 将a[i] 插入到a[i-h],a[i-2*h],a[i-3*h],...之中
                for (int j = i; j >= h && less(a[j],a[j-h]); j -= h) 
                    exch(a,j,j-h);
            }
    
            h = h/3;
        }
    }

    在这里,1,4,13,40,121,364,1093,…. 称为递增数列。
    为什么这里选择这样的递增数列?如何选择更好的递增序列? 回答这个问题并不简单。 有很多论文研究了各种不同的递增序列,但都无法证明某个序列是“最好的”。其不仅取决于h的值,还取决于h之间的数学性质,如公因子等。一般情况下也选择递增数列如 N/2 N/4 N/8 … 1 ( 逆序排列 )。 在实际应用中,这两种递增数列基本够用。

    希尔排序的重要意义是突破了排序算法复杂度为 O(N2) O ( N 2 ) 的限制,迈出了寻找更高效排序算法的重要一步。

    结论: 使用递增数列1,4,13,40,121,364… ( h = 3*h + 1) 的希尔排序所需要的比较次数不会超过N的若干倍乘以递增序列的长度(即while 循环次数)。


    算法性能比较

    这里给出比较两种算法性能的具体实现,科学方法就是要通过实践来检验真理。理论上的算法效率是否在实际使用中得到确认?

    // 比较方法类
    class SortCompare
    {
    public:
    
        static double timeRandomData(string sortType, int N, int T)
        {
            double total = 0.0;
            int *a = new int[N];
    
            assert(a != NULL);
    
            std::default_random_engine generator;  
            std::uniform_int_distribution<int> dis(0,500);
    
            for(int t =0 ; t< T; t++) // 重复T次
            {
                for (int i =0 ; i < N; i++) // 数组长度N
                {
                    a[i] = dis(generator);
                }
    
                total += time(sortType,a,N);
            }
    
            delete []a;
    
            return total;
        }
    
    private:
    
        static double time(string sortType, int a[], int N)
        {
            DWORD start_time,stop_time;
            start_time  = GetTickCount();
    
            if(sortType.compare("select") == 0 )
                SortMethod<int>::select_sort(a,N);
            else if(sortType.compare("bubble") == 0)
                SortMethod<int>::bubble_sort(a,N);
            else if(sortType.compare("insert") == 0 )
                SortMethod<int>::insert_sort(a,N);
            else if(sortType.compare("shell") == 0 )
                SortMethod<int>::shell_sort(a,N);
    
            stop_time = GetTickCount();
    
            return (stop_time - start_time)*1.0/1000;
        }
    };
    
    // 调用
    
        int N = 10000;
        int T = 100;
        string type1 = "shell";
        string type2 = "insert";
        double t1 = SortCompare::timeRandomData(type1,N,T);
        double t2 = SortCompare::timeRandomData(type2,N,T);
    
        cout<<"For "<< N << " random Ints "<<endl;
        cout<<type1<<" cost :"<<t1<<" s and " <<type2 <<" costs :"<<t2<<" s."<<endl;

    运行结果:

    For 10000 random Ints
    shell cost :826 ms and insert costs :112280 ms.
    请按任意键继续…

    大胆使用大数组测试希尔排序。

    初级排序结束。

  • 相关阅读:
    子类继承和调用父类的构造方法 (转)
    数组复制 System.arraycopy 与 Arrays.copyof()
    ArrayList的使用方法 (转)
    Eclipse 的debug 用法 (转)
    for each
    二维数组 排序 随机数 练习
    react 之 reflux 填坑
    react & vue 项目创建的方式
    数组实例的 copyWithin()
    es6的正则扩展笔记之修饰符
  • 原文地址:https://www.cnblogs.com/brother-louie/p/13976561.html
Copyright © 2020-2023  润新知