七、排序,選擇、冒泡、希爾、歸併、快速排序實現

臣有一事不知當不當講發表於2018-04-12

一、排序的基本概念

排序:將“無序”的資料元素調整為“有序”的資料。

排序演算法的穩定性:如果待排列的排序表中有兩個元素R1、R2,其對應的關鍵字Key1 = Key2,且在排序前R1就在R2的前面,如果使用某一種排序演算法後,R1仍然在R2的前面,則稱這個排序演算法是穩定的,否則是不穩定的。

注:對於不穩定的演算法,只需舉出一個範例即可正面不穩定性。

多關鍵字排序:按照優先順序,分別進行排序。

排序中的關鍵操作:

比較:任意兩個資料元素通過比較操作確定先後順序;

交換:資料元素之間需要交換才能得到預期效果。

排序的審判:

1、時間效能:差異體現在比較和交換的數量,主要看迴圈次數

2、空間:儲存空間的多少,必要時可以:空間換時間

3、演算法的實現複雜性:過於複雜可能會影響可讀性和可維護性。

排序的時間效能是區分排序演算法好壞的主要因素。

交換函式模板的實現

    template<typename T>
    static void Swap(T& a, T& b)  //定義交換函式  靜態的
    {
        T c(a);
        a = b;
        b =c;
    }

二、選擇排序與插入排序

選擇排序:先比較、再交換,即選定最小(最大)的資料。

    template < typename T>
    static void Select(T array[], int len, bool min2max = true)  //不穩定的排序演算法
    {
        for(int i=0; i<len; i++)
        {
            int min = i;

            for(int j=i+1; j<len; j++)
            {
                if( min2max ? (array[min] > array[j]) : (array[min] < array[j]) )
                {
                    min = j;  //當前最小值的位置
                }
            }

            if( min != i)
            {
                Swap(array[i], array[min]);
            }
        }
    }

插入排序:先比較,再插入,再後移。

    template < typename T>
    static void Insert(T array[], int len, bool min2max = true) //穩定的排序演算法
    {
       for(int i=1; i<len; i++)
       {
           int k = i;
           T e = array[i];  //要插入的資料

           for(int j=i-1; (j>=0) && (min2max ? (array[j] > e) : (array[j] < e)); j--)
           {
                  array[k] = array[j]; // k = j+1 後移一位直至a[i]的前面的數字比a[i】小(為真的前提下 )
                  k = j;
           }

           if( k != i)
           {
                array[k] = e;
           }
       }
    }

選擇排序和插入排序的時間複雜度都為O(n2)。

三、氣泡排序與希爾排序

氣泡排序:從後向前,兩兩比較,逆序交換。

    template < typename T>
    static void Bubble(T array[], int len, bool min2max = true)  // O(n2) 最壞的情況下   穩定的排序法
    {
      bool exchange = true;

      for(int i=0; (i<len) && exchange ; i++)
      {
          exchange = false;

          for(int j=len-1; j>i; j--)
          {
             if( min2max ? (array[j] < array[j-1]) : (array[j] > array[j-1]))
             {
                Swap(array[j], array[j-1]);
                exchange = true;  //進行下一次迴圈的條件,否則迴圈結束
             }
          }
      }
    }

希爾排序:將待排序列分為若干組,在每一組內進行插入排序,以使整個序列基本有序,然後對整個序列進行插入排序。

步長!步長!步長!——劃分序列的依據。

    template < typename T>
    static void Shell(T array[], int len, bool min2max = true) //不穩定的排序法 O(n3/2))
    {
        int d = len;

        do
        {
            d = d/3 + 1;  //實踐證明

            for(int i=d; i<len; i+=d)
            {
                int k = i;
                T e = array[i];

                for(int j=i-d; (j>=0) && (min2max ? (array[j] > e) : (array[j] < e)); j-=d)
                {
                       array[j+d] = array[j];
                       k = j;
                }

                if( k != i)
                {
                     array[k] = e;
                }
            }

        }while( d > 1);
    }

劃時代的意義:將排序演算法的複雜度從n的平方降低。

也可以通過冒泡的方法,將每組內的資料進行排列,具體實現如下:

    template < typename T>
    static void Shell2Bubble(T array[], int len, bool min2max = true)
    {
        int d = len;

        do
        {
            d = d/3 + 1;  //實踐證明

            bool exchange = true;

            for(int i=0; (i<len) && exchange ; i+=d)
            {
                exchange = false;

                for(int j=len-d; j>i; j-=d)
                {
                   if( min2max ? (array[j] < array[j-1]) : (array[j] > array[j-1]))
                   {
                      Swap(array[j], array[j-1]);
                      exchange = true;
                   }
                }
            }

        }while( d > 1);
    }

四、歸併排序與快速排序

歸併排序的基本思想:

兩個或兩個以上有序序列合併成一個新的有序序列,這種歸併方法稱為2路歸併。

所以,對於無序序列,就必須先分別進行排序。如何排序呢?————降維、遞迴!

具體實現如下:

    template <typename T>
    static void Merge( T array[], int len, bool min2max = true)
    {
       T *helper = new T[len];

       if( helper != NULL)
       {
            Merge(array, helper, 0, len-1, min2max);
       }

       delete[] helper;
    }
    template <typename T>  //二路歸併排序
    static void Merge( T src[], T helper[], int begin, int end, bool min2max )
    {
        if( begin < end)
        {
            int mid = (begin + end) / 2;
            
/*****************************遞迴******************************/
            Merge(src, helper, begin, mid, min2max);
            Merge(src, helper, mid+1, end, min2max);
            Merge(src, helper, begin, mid, end, min2max);
        }
    }
    template <typename T>  //二路歸併排序
    static void Merge( T src[], T helper[], int begin, int mid, int end, bool min2max )
    {
       int i = begin;
       int j = mid + 1;
       int k = begin;

       while( (i <= mid) && (j <= end) )
       {
           if( min2max ? (src[i] < src[j]) : (src[i] > src[j]) )
           {
               helper[k++] = src[i++];
           }
           else
           {
               helper[k++] = src[j++];          
           }
        }

           while( i <= mid)
           {
               helper[k++] = src[i++];
           }

           while( j <= end)
           {
               helper[k++] = src[j++];
           }

           for(i=begin; i<=end; i++)
           {
               src[i] = helper[i];

               //cout<< " src[i]" << src[i]<<endl;
           }

    }

可以用下面的示意圖來表示上述歸併過程,通過遞迴,不斷增加引數來進行排序。

快速排序:任取序列中的某個資料元素作為基準將整個序列劃分為左右兩個子序列

左側子序列中的所有元素都小於或等於基準元素;

右側子序列中的所有元素都大於基準元素;

基準元素排在這兩個子序列中間。

分別對這兩個子序列重複繼續劃分(遞迴),直到所有的資料元素都排在相應位置上為止。

最重要的是要找到基準元素!


實現程式碼如下:

    template <typename T>
    static int Partition(T array[], int begin, int end, bool min2max)
    {
        T pv = array[begin];

        while( begin < end )
        {
            while( (begin < end) && (min2max ? (array[end] > pv) : (array[end] < pv)))
            {
                end--; //大於基準則往前進一位
            }
            /**************跳出迴圈後進行交換******************/
            Swap(array[begin], array[end]);

            while((begin < end) && (min2max ? (array[begin] <= pv) : (array[begin] >= pv)))
            {
                begin++; //小於則往後進一位
            }

            Swap(array[begin], array[end]);
        }

        array[begin] = pv;  //基準就位

        return begin;  //返回基準的最終下標
    }

通過上面的函式,就可以確定在快速排序中進行比較的基準元素的位置,

在Quick函式中,實現如下:

    template<typename T>
    static void Quick(T array[], int begin, int end, bool min2max )
    {
        if( begin < end)
        {
            int pivot = Partition(array, begin, end, min2max);

            Quick(array, begin, pivot-1, min2max);
            Quick(array, pivot+1, end, min2max);
        }
    }

封裝成介面如下:

    template<typename T>
    static void Quick(T array[], int len, bool min2max = true)
    {
        Quick(array, 0, len-1, min2max);
    }

小結:

歸併排序需要額外的空間才能完成,空間複雜度為O(n);

歸併排序的時間複雜度為O(n*logn),是一種穩定的排序法;

快速排序通過遞迴的方式對排序問題進行劃分;

快速排序的時間複雜度為O(n*logn),是一種不穩定的排序法。

五、排序的工程應用例項

陣列類(array())與排序類(Sort())的關係:


array():T*     ——返回C++、C陣列的首地址

    T* array() const
    {
        return m_array;  //返回原生陣列的首地址
    }


在排序類(Sort())中針對自定義的陣列類(array())物件進行函式過載,分別實現陣列類物件的不同排序方法。

    template <typename T>
    static void Select(Array<T>& array, bool min2max = true)
    {
        Select(array.array(), array.length(), min2max);
    }

    template <typename T>
    static void Insert(Array<T>& array, bool min2max = true)
    {
        Insert(array.array(), array.length(), min2max);
    }

    template <typename T>
    static void Bubble(Array<T>& array, bool min2max = true)
    {
        Bubble(array.array(), array.length(), min2max);
    }

    template <typename T>
    static void Shell(Array<T>& array, bool min2max = true)
    {
        Shell(array.array(), array.length(), min2max);
    }

    template <typename T>
    static void Merge(Array<T>& array, bool min2max = true)
    {
        Merge(array.array(), array.length(), min2max);
    }

    template <typename T>
    static void Quick(Array<T>& array, bool min2max = true)
    {
        Quick(array.array(), array.length(), min2max);
    }

排序類能夠對陣列類物件進行排序。

相關文章