[数据结构 -- 手撕排序第二篇] 一篇带你详细了解希尔排序

目录

1、常见排序算法

1.1 插入排序基本思想

2、希尔排序

2.1 希尔排序( 缩小增量排序 )

2.1.1 预排序阶段

2.1.2 插入排序阶段

2.2 单趟希尔排序

2.2.1 思路分析

2.2.2 代码实现

3、希尔排序代码实现

4、希尔排序时间复杂度

5、希尔排序与插入排序效率对比

6、希尔排序特性总结


1、常见排序算法

1.1 插入排序基本思想

直接插入排序是一种简单的插入排序法,其基本思想是 :
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为
止,得到一个新的有序序列
实际中我们玩扑克牌时,就用了插入排序的思想

[数据结构 -- 手撕排序第二篇] 一篇带你详细了解希尔排序_第1张图片

 

2、希尔排序

2.1 希尔排序( 缩小增量排序 )

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成多个组,所有距离相差为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取gap=gap/3+1重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。

我们画图来分析一下:我们这里是排升序

[数据结构 -- 手撕排序第二篇] 一篇带你详细了解希尔排序_第2张图片

由图我们其实可以看出来,当最后一次gap=1时,本质是插入排序,而希尔排序就是先预排序,最后再插入排序一次就实现了排序

我们将上面的图来分析一下:

2.1.1 预排序阶段

1、第一趟,当gap=5时,意思是每间隔5个数为一组,9与4,1与8,2与6,5与3,7与5分为一组,当后面的数字小于前面的数字我们就交换,这样我们就将这几组中小的数字排到了前面,大的数字就排到了后面;

2、第二趟,我们就让gap = gap/3+1(这里gap/3+1是为了最后能得到gap=1,当gap<3时,如果只是除以3,得到的就是0,这样就实现不了完全排序),gap=2,每间隔2个数为一组,4与2,5,8,5一组,1与3,9,6,7一组,比较大小,然后交换;

2.1.2 插入排序阶段

3、当调整gap=1时,本质就是插入排序了,这时经过了之前的预排序阶段,我们的数字已经接近有序了,但是不完全有序,此时上插入排序,效率提升会很大。

2.2 单趟希尔排序

2.2.1 思路分析

单趟的希尔排序其实就是将相隔为gap的数字分为一组,先进行排序。

画图分析:

 

[数据结构 -- 手撕排序第二篇] 一篇带你详细了解希尔排序_第3张图片

将单趟的每个间隔为gap的数字分为一组先进行排序。

2.2.2 代码实现

for (int i = 0; i < n - gap; i++)//多组一起排序(没有提升效率,只是少写一组循环)
{
    int end = i;
    int tmp = a[i + gap];
    while (end >= 0)
    {
        if (a[end] > tmp)
        {
            a[end + gap] = a[end];
            end -= gap;
        }
        else
        {
            break;
        }
    }
    a[end + gap] = tmp;
}

3、希尔排序代码实现

给单趟排序外面加一层循环,让gap不断缩小,直到为 1,就实现了希尔排序。

void ShellSort(int* a, int n)
{
	//1. 当 gap > 1 时先进性预排序
	//2. 当 gap == 1 时直接插入排序
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;// +1可以保证最后一次一定是1
		for (int i = 0; i < n - gap; i++)//多组一块进行
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

4、希尔排序时间复杂度

希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定 。

以下是两本书对希尔排序时间复杂度的描述:

[数据结构 -- 手撕排序第二篇] 一篇带你详细了解希尔排序_第4张图片

我们这里的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:O(n^1.25)到O(1.6*n^1.25)来算。

5、希尔排序与插入排序效率对比

我们分别开辟两个数组,都是100000大小,使用希尔排序与插入排序,对比所消耗的时间。

//时间对比
void TestOp()
{
	srand((unsigned int)time(NULL));
	const int n = 100000;
	int* a1 = (int*)malloc(sizeof(int) * n);
	int* a2 = (int*)malloc(sizeof(int) * n);

	for (int i = 0; i < n; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
	}

	int begin1 = clock();
	InsertSort(a1, n);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, n);
	int end2 = clock();


	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);

	free(a1);
	free(a2);
}

int main()
{
	TestOp();
	return 0;
}

[数据结构 -- 手撕排序第二篇] 一篇带你详细了解希尔排序_第5张图片

我们可以看出希尔排序要比插入排序的效率高出很多,当数据量越大的时候,希尔排序的更占优势。

但是,当数组本身就是有序的,希尔排序就比不过插入排序,因为希尔排序存在预排序,执行了就要花费时间。

6、希尔排序特性总结

1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定 。

4。稳定性:不稳定。

你可能感兴趣的:(排序算法,排序算法,算法,数据结构,c语言)