• 算法:希尔排序


    希尔排序是基于插入排序的一个优化。可以使平均时间复杂度减少为O(n * log2(n))。

    例题

    洛谷1177 排序

    题目描述
    将读入的 N 个数从小到大排序后输出。

    输入格式
    第 1 行为一个正整数 N。
    第 2 行包含 N 个空格隔开的正整数 a[i],为你需要进行排序的数,数据保证了a[i]不超过10^9。

    输出格式
    将给定的 N个数从小到大输出,数之间用空格隔开。

    输入输出样例
    输入

    5
    4 2 4 5 1
    

    输出

    1 2 4 4 5
    

    说明提示
    对于20% 的数据,有 N <= 10^3。
    对于100% 的数据,有 N <=10^5 。

    希尔排序

    这里我们定义一个新的变量gap表示增量,意思是每次将a[1] ~ a[n] 分为gap组:

    第1组:a[1],a[gap + 1],a[2 * gap + 1] ……
    第2组:a[2],a[gap + 2],a[2 * gap + 2] ……
    第3组:a[3],a[gap + 3],a[2 * gap + 3] ……
    ……………………………………………………
    第gap组:a[gap],a[gap + gap],a[2 * gap + gap] ……

    之后分别对这gap组进行插入排序,就可以让序列中变得比之前更加有序。

    gap刚开始往往取(n / 2),之后每次让gap /= 2,直到gap == 0了就停止。这里可能相对来说比较难理解,你可以自己动手去分析一下样例。

    最后算一下这个算法的时间复杂度:我们需要先枚举gap,再去枚举i和j,总共有三重循环。第一重循环每次是gap /= 2,跑了大约log2(n)次,而第二重循环跑了大约n次。但第三重循环实际上很快,常数次就跑完了,因为每次分完组,都会使这个序列变得更加有序,第三重循环去插入的时候跑了几次就跳出去了。所以综合起来平均的时间复杂度也就O(n * log2(n))级别,相对于插入排序就快了很多了。

    这里有一个坑点务必注意:在写j这个循环时,判断是否跳出循环的语句中一定要把j >= 1 放在a[j] > a[j + gap]前面。

    for (int j = i - gap; j >= 1 && a[j] > a[j + gap]; j -= gap) // 正确
    for (int j = i - gap; a[j] > a[j + gap] && j >= 1; j -= gap) // 错误
    

    因为如果说我的j -= gap后万一变成了负数,那么a[j]就没有意义了,而&&运算是先去判断前面的语句,如果前面的语句正确才去判断后面的语句,否则不会去判断后面的语句。

    这样如果是第一行的for循环,当j变成负数后,先判断出j >= 1是假的,根据&&符号的运算法则,不回去判断a[j] > a[j + gap],是正确的。

    而如果是第二行的for循环,当j变成负数后,会先去判断a[j] > a[j + gap],可j是负数,a[j]根本没有意义,就成功RE了。

    代码

    # include <cstdio>
    # include <cmath>
    # include <cstring>
    # include <algorithm>
    
    using namespace std;
    
    const int N_MAX = 100000;
    
    int n;
    int a[N_MAX + 10];
    
    void shellSort()
    {
    	for (int gap = n / 2; gap >= 1; gap /= 2)
    		for (int i = gap + 1; i <= n; i++)
    			for (int j = i - gap; j >= 1 && a[j] > a[j + gap]; j -= gap)
    				swap(a[j], a[j + gap]);
    }
    
    int main()
    {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++)
    		scanf("%d", &a[i]);
    	shellSort();
    	for (int i = 1; i <= n; i++)
    		printf("%d ", a[i]);
    	printf("
    ");
    	return 0;
    }
    
  • 相关阅读:
    今天不说技术,说说中国的十二生肖告诉了我们什么?这就是我们的祖先!
    JS函数的原型及对象,对象方法,对象属性的学习
    C#3.0特性之列表对象的赋值更容易
    读本地图像文件,在上面写一些文件,再传到WWW服务器上
    【Visual C++】vs2008/2005正确打开vs2010所创建项目的几种方法
    高级Swing容器(一)
    助你成长为优秀的程序员 杰出的软件工程师、设计师、分析师和架构师
    Root Pane Containers(一)
    【Visual C++】关于无法打开包括文件:“StdAfx.h”或者意外结尾的错误解决方案
    20年工作经验的架构师写给程序员的一封信
  • 原文地址:https://www.cnblogs.com/000zwx000/p/12330584.html
Copyright © 2020-2023  润新知