【算法】【sort 2.1.3】希爾排序

shell sort

  • 是一種優化的 插入排序
  • 基於插入排序,每個不斷縮小的區間得到後,都要做插入排序
  • 效率不穩定,但是一般比 插入排序好
  • 插入排序主要是做相鄰的交換
  • 這東東有三層for
  • 執行過程:


[Running] g++ test_sort_xier_2.3.cpp -std=c++14 -o test_sort_xier_2.3.exe && ./test_sort_xier_2.3.exe
 100 99 8 7 200 3 1 43
3
7
 max h 7
 43 99 8 7 200 3 1 100
--b---
 43 99 8 7 200 3 1 100
 change h 2
---e--
 8 99 43 7 200 3 1 100
 8 7 43 99 200 3 1 100
 8 7 43 99 200 3 1 100
 8 3 43 7 200 99 1 100
 1 3 8 7 43 99 200 100
 1 3 8 7 43 99 200 100
--b---
 1 3 8 7 43 99 200 100
 change h 0
---e--
 1 3 8 7 43 99 200 100

[Done] exited with code=0 in 0.609 seconds


java的講解與c的不太一樣

h 有序數組

  • 任意間隔爲h的數組都是有序的
  • 排序時,使用的h 是遞減的,這是一個for循環。
  • h 最小是1
  • h的最大值
//如果指定間隔是3,size是N
while(h  < N/3 )
{
  h  = h * 3 +1 ;
}
  • 舉例 N = 8 ,h 算出來最大是h =1 時的 4

第一層for

  • 與上面一致:
  • h 從最大開始, h必須大於等於1; h 每次遞減 規則是 h/3

第二層for

  • h 有序數組 ,每個數組遍歷一遍
  • h 是第一層for 每次 的值
  • 初始化 i : int i = h ;
  • i 最大是N
  • i 每次+ 1
for (int i = h ; i < N; i++ )

第三層for

  • 對上面的每個數組 做插入排序
  • 數組元素是 j j-h j-2*h 這些
  • j 初始化爲 第一個數組的最後一個就是h
// j 可以爲h,因爲 a[0] 是可以的。
for ( int j = h; j >=h  && less([a[j],a[j-h]]; 
j = j - h )
{
 exchange( j , j -h );
}

參考java的算法

  • 下面的註釋可能有錯誤 :
  • 實現cpp
#include <vector>
#include <string>
#include <iostream>

using Vec = std::vector<int>;

 void show(Vec &v)
{
    //for(auto &entry : v)
    int i = 0;
    int size = v.size();
    for(;i<size;i++)
    {
        std::cout <<" "<< v[i];
    }
    std::cout <<std::endl;
}
bool less(int a, int b )
{
    if(a <b )
    {
        return true;
    }
    return false;
}
bool exchange( int  *a ,int *b)
{
    if(a == nullptr || b == nullptr )
    {
        return false;
    }
    int tmp = *a;
    *a = *b;
    *b = tmp;
    return true;
}
//希爾, 1 增量序列,比如 每次 1/2 1/3這樣的
//2 每一組 分別進行插入排序 ,每次排序後,選用下一個增量序列的數組

void sort(Vec &v)
{
    int N = v.size();
    int h = 1;
    #if 0 
  //  h =1 時,N = 8,N/3爲2時,h 第一次循環就變爲了4,
    while(h < N/3)
   {
       h = 3*h +1;
       std::cout << h <<std::endl;
   }    
   #else
      while(h < N/2)
   {
       h = 2*h +1;
       std::cout << h <<std::endl;
   }    
   #endif
    std::cout << " max h "<<h <<std::endl;
  //增量h遞減 插入排序
   //排序是從最大增量 開始到 1 ,每次都要對每個子數組做排序
    {
        //啥時候結束?,h=1 時,做最後一次直接插入排序
        //增量h最小是1 ,h 也是每次的跨度值
        while(h>=1)
        {
            //對於每個增量值,第一個分量數組的起始位置是h
           int i= h;        
           //增量值開始,向後(直到N)每個都與全面的h個值構成一個數組
          //每個數組有h個元素,數組最後一個元素是從h開始++ , 下標就是i
          for(; i < N; i++)
          {
              //從i開始構造數組,直到i 等於N結束
              // a[i] 插入到 a[i-h] a[i-2*h] a[i-3*h] 中 
              int j = i ;
              for(;j>=h && less(v[j],v[j-h]); j -=h )
              {
                  exchange(&v[j],&v[j-h]);
              }
                   show(v);
          }
            //增量序列的下一個
        h = h/3;
           std::cout <<"--b---"<<std::endl;
         show(v);
        std::cout << " change h "<<h <<std::endl;
       
        std::cout <<"---e--"<<std::endl;
        }

      
      }
}


int main()
{
    Vec v = {100,99,8,7,200,3,1,43};
    show(v);
    sort(v);
    show(v);
    return 0;
}

大神的筆試題

希爾排序研究
希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。

希爾排序是基於插入排序的以下兩點性質而提出改進方法的:

插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
但插入排序一般來說是低效的, 因爲插入排序每次只能將數據移動一位
希爾排序的基本思想是:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。

算法步驟

1)選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=12)按增量序列個數k,對序列進行k 趟排序;

3)每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。
  • 實現代碼:
/***************************************************************************
 *  @file       main.cpp
 *  @author     MISAYAONE
 *  @date       27  March 2017
 *  @remark     27  March 2017 
 *  @theme      Shell Sort 
 ***************************************************************************/
 
#include <iostream>
#include <vector>
#include <time.h>
#include <Windows.h>
using namespace std;
 
void Shell_sort(int a[],size_t n)
{
	int i,j,k,group;
	for (group = n/2; group > 0; group /= 2)//增量序列爲n/2,n/4....直到1
	{
		for (i = 0; i < group; ++i)
		{
			for (j = i+group; j < n; j += group)
			{
				//對每個分組進行插入排序
				if (a[j - group] > a[j])
				{
					int temp = a[j];
					k = j - group;
					while (k>=0 && a[k]>temp)
					{
						a[k+group] = a[k];
						k -= group;
					}
					a[k] = temp;
				}
			}
		}
	}
}
 
int main(int argc, char**argv)
{
	int a[10] = {1,51,6,2,8,2,564,1,65,6};
 
	Shell_sort(a,10);
	for (int i = 0; i < 10; ++i)
	{
		cout<<a[i]<<" ";
	}
	cin.get();
	return 0;
}
  • 希爾排序的時間複雜度與增量序列的選取有關,例如希爾增量時間複雜度爲O(n2),而Hibbard增量的希爾排序的時間複雜度爲O(N(5/4)),但是現今仍然沒有人能找出希爾排序的精確下界。

步長序列(Gap Sequences)

步長的選擇是希爾排序的重要部分。只要最終步長爲 1 任何步長串行都可以工作。算法最開始以一定的步長進行排序。然後會繼續以一定步長進行排序,最終算法以步長爲 1 進行排序。當步長爲 1 時,算法變爲插入排序,這就保證了數據一定會被排序。

已知的最好步長串行是由 Sedgewick 提出的 (1, 5, 19, 41, 109,…),該步長的項來自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 這兩個算式。這項研究也表明 “比較在希爾排序中是最主要的操作,而不是交換。” 用這樣步長串行的希爾排序比插入排序和堆排序都要快,甚至在小數組中比快速排序還快,但是在涉及大量數據時希爾排序還是比快速排序慢。

複雜度分析:

最差時間複雜度 O(nlog2 n)
平均時間複雜度 依賴於步長間隔 O(nlog2 n)
最優時間複雜度 O(nlogn)
最差空間複雜度 O(n),輔助空間 O(1)

特點分析:不穩定算法unstable sort,in_place

例題1:寫出希爾排序算法程序,並說明最壞的情況下需要進行多少次的比較和交換。

程序略,需要O(n^2)次的比較

例題2:設要將序列(Q, H, C, Y, P, A, M, S, R, D, F, X)中的關鍵碼按字母序的升序重新排列,則:

冒泡排序一趟掃描的結果是 H, C, Q, P, A, M, S, R, D, F, X ,Y ;
初始步長爲4的希爾(shell)排序一趟的結果是 P, A, C, S, Q, D, F, X , R, H,M, Y ;
二路歸併排序一趟掃描的結果是 H, Q, C, Y,A, P, M, S, D, R, F, X ;
快速排序一趟掃描的結果是 F, H, C, D, P, A, M, Q, R, S, Y,X ;
堆排序初始建堆的結果是 A, D, C, R, F, Q, M, S, Y,P, H, X 。
————————————————

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章