经典排序算法----希尔排序算法(非稳定)

希尔排序(非稳定)


希尔排序

希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。(希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。)

该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法(冒泡排序算法直接插入排序算法)有较大提高。


希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
  • 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位
平均时间复杂度O(n1.3),最好的情况O(n),最坏的情况O(n2),不稳定。


以n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例

第一次: gap = 10/2=5

希尔排序
4938659726132749554
1A    1B    
 2A    2B   
  3A    3B  
   4A    4B 
    5A    5B








1A,1B,2A,2B等为分组标记,数字相同的表示在同一组,大写字母表示是该组的第几个元素, 每次对同一组的数据进行直接插入排序。即分成了五组(49, 13) (38, 27) (65, 49) (97, 55) (26, 4)这样每组排序后就变成了(13, 49) (27, 38) (49, 65) (55, 97) (4, 26),下同。

第一次排序完之后数组:13,27,49,55,4,49,38,68,97,26


第二次: gap = 5/2=2

希尔排序
1327495544938659726
1A 1B 1C 1D 1E 
 2A 2B 2C 2D 2E
          







分成了两组(13,49,4,38,97)和(27,55,49,65,26)每次对同一组数据进行直接插入排序,每组就变成了(4,13,38,49,97)和(26,27,49,55,65)。

第二次排序之后的数组:4,26,13,27,38,49,49,55,97,65

第三次:gap = 2/2=1

希尔排序
4261327384949559765
1A1B1C1D1E1F1G1H1I1J









这次只有一组,进行 直接插入排序

第三次得到数组:4,13,26,27,38,49,49,55,65,97

第四次 gap 1/2  = 0 结束。

下面给出严格按照定义来写的希尔排序算法:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void Show(int *arr, int n)
{
	for (int i = 0; i < n; i++)
		printf("%-5d", arr[i]);
	printf("\n");
}

void ShellSort(int *arr, int n)
{
	int i, j, gap;

	for (gap = n / 2; gap > 0; gap /= 2)		//步长
	{
		for (i = 0; i < gap; i++)				//直接插入排序
		{
			for (j = i + gap; j < n; j+=gap)
				if (arr[j] < arr[j - gap])
				{
					int temp = arr[j];
					int k = j - gap;
					while (k >= 0 && arr[k] > temp)
					{
						arr[k + gap] = arr[k];
						k -= gap;
					}
					arr[k + gap] = temp;
				}
		}
	}
}

//直接插入排序不就是希尔排序的特殊情况吗?
int main()
{
	time_t time1;
	srand((unsigned int)time(&time1));
	int arr[100] = { 0 };
	for (int i = 0; i < 100; i++)
		arr[i] = rand() % 100;
	printf("排序之前数组中的元素为:\n");
	Show(arr, 100);
	ShellSort(arr, 100);
	printf("排序之后数组中的元素为:\n");
	Show(arr, 100);

	system("pause");
	return 0;
}

运行结果:

排序之前数组中的元素为:
80   67   70   6    97   14   84   94   25   96   37   59   62   58   52   4
14   30   15   53   92   59   59   33   55   77   7    13   33   95   82   55
18   23   31   75   26   54   21   38   12   69   3    85   17   39   49   8
25   45   76   23   96   54   79   87   23   94   54   6    96   32   71   89
55   22   47   94   87   22   34   49   77   43   18   54   38   38   13   43
35   78   93   96   8    68   91   76   33   59   95   54   85   51   99   64
68   92   22   14
排序之后数组中的元素为:
3    4    6    6    7    8    8    12   13   13   14   14   14   15   17   18
18   21   22   22   22   23   23   23   25   25   26   30   31   32   33   33
33   34   35   37   38   38   38   39   43   43   45   47   49   49   51   52
53   54   54   54   54   54   55   55   55   58   59   59   59   59   62   64
67   68   68   69   70   71   75   76   76   77   77   78   79   80   82   84
85   85   87   87   89   91   92   92   93   94   94   94   95   95   96   96
96   96   97   99
请按任意键继续. . .

很明显,上面的shellsort1代码虽然对直观的理解希尔排序有帮助,但代码量太大了,不够简洁清晰。因此进行下改进和优化,以第二次排序为例,原来是每次从1A到1E,从2A到2E,可以改成从1B开始,先和1A比较,然后取2B与2A比较,再取1C与前面自己组内的数据比较…….。这种每次从数组第gap个元素开始,每个元素与自己组内的数据进行直接插入排序显然也是正确的。

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void Show(int *arr, int n)
{
	for (int i = 0; i < n; i++)
		printf("%-5d", arr[i]);
	printf("\n");
}

void ShellSort(int a[], int n)
{
	int j, gap;

	for (gap = n / 2; gap > 0; gap /= 2)
		for (j = gap; j < n; j++)//从数组第gap个元素开始
			if (a[j] < a[j - gap])//每个元素与自己组内的数据进行直接插入排序
			{
		int temp = a[j];
		int k = j - gap;
		while (k >= 0 && a[k] > temp)
		{
			a[k + gap] = a[k];
			k -= gap;
		}
		a[k + gap] = temp;
			}
}

//直接插入排序不就是希尔排序的特殊情况吗?
int main()
{
	time_t time1;
	srand((unsigned int)time(&time1));
	int arr[100] = { 0 };
	for (int i = 0; i < 100; i++)
		arr[i] = rand() % 100;
	printf("排序之前数组中的元素为:\n");
	Show(arr, 100);
	ShellSort(arr, 100);
	printf("排序之后数组中的元素为:\n");
	Show(arr, 100);

	system("pause");
	return 0;
}

运算结果:

排序之前数组中的元素为:
11   59   90   41   37   43   32   81   91   27   44   64   17   64   42   70
29   93   51   63   56   52   17   72   29   39   96   7    29   84   84   54
81   9    79   35   33   4    94   47   15   66   9    8    64   62   82   15
81   40   78   82   92   67   74   62   51   84   79   13   5    40   33   86
32   83   13   68   15   42   65   12   29   90   56   45   46   38   54   34
41   3    61   39   5    26   89   52   19   22   21   63   96   90   30   90
91   21   87   4
排序之后数组中的元素为:
3    4    4    5    5    7    8    9    9    11   12   13   13   15   15   15
17   17   19   21   21   22   26   27   29   29   29   29   30   32   32   33
33   34   35   37   38   39   39   40   40   41   41   42   42   43   44   45
46   47   51   51   52   52   54   54   56   56   59   61   62   62   63   63
64   64   64   65   66   67   68   70   72   74   78   79   79   81   81   81
82   82   83   84   84   84   86   87   89   90   90   90   90   91   91   92
93   94   96   96
请按任意键继续. . .

再将直接插入排序部分用 白话经典算法系列之二 直接插入排序的三种实现 中直接插入排序的第三种方法来改写下:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void Show(int *arr, int n)
{
	for (int i = 0; i < n; i++)
		printf("%-5d", arr[i]);
	printf("\n");
}

void Swap(int *a, int *b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void ShellSort(int a[], int n)
{
	int i, j, gap;

	for (gap = n / 2; gap > 0; gap /= 2)
		for (i = gap; i < n; i++)
			for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)
				Swap(&a[j], &a[j + gap]);
}

//直接插入排序不就是希尔排序的特殊情况吗?
int main()
{
	time_t time1;
	srand((unsigned int)time(&time1));
	int arr[100] = { 0 };
	for (int i = 0; i < 100; i++)
		arr[i] = rand() % 100;
	printf("排序之前数组中的元素为:\n");
	Show(arr, 100);
	ShellSort(arr, 100);
	printf("排序之后数组中的元素为:\n");
	Show(arr, 100);

	system("pause");
	return 0;
}
运行结果:

排序之前数组中的元素为:
37   48   37   10   83   55   17   42   50   28   31   87   32   47   68   54
18   34   85   47   44   15   72   10   12   63   33   84   64   79   16   98
54   17   6    81   29   23   52   7    33   17   59   41   82   24   86   29
89   70   32   77   36   5    94   69   19   40   8    98   10   75   39   79
76   38   34   57   13   48   39   21   17   60   33   33   81   45   19   59
9    58   52   0    50   67   96   53   76   39   66   74   76   90   24   68
8    98   54   33
排序之后数组中的元素为:
0    5    6    7    8    8    9    10   10   10   12   13   15   16   17   17
17   17   18   19   19   21   23   24   24   28   29   29   31   32   32   33
33   33   33   33   34   34   36   37   37   38   39   39   39   40   41   42
44   45   47   47   48   48   50   50   52   52   53   54   54   54   55   57
58   59   59   60   63   64   66   67   68   68   69   70   72   74   75   76
76   76   77   79   79   81   81   82   83   84   85   86   87   89   90   94
96   98   98   98
请按任意键继续. . .

附注:上面希尔排序的步长选择都是从n/2开始,每次再减半,直到最后为1。其实也可以有另外的更高效的步长选择,如果读者有兴趣了解,请参阅维基百科上对希尔排序步长的说明:

http://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F

引自<http://blog.csdn.net/morewindows/article/details/6668714>



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值