【算法】【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的不太一样

  • 这个挺好
  • 参考
  • 参考
  • 时间关系,我还是按照java的来吧

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 
#include 
#include 

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 
#include 
#include 
#include 
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 。
————————————————

你可能感兴趣的:(算法/数据结构)