当前位置: 首页 > news >正文

排序算法系列之(五)——为目标打好基础的希尔排序

前言

刚刚分析过的插入排序通常被叫做简单插入排序或者直接插入排序,而这篇文章刚好以插入排序为基础来说说希尔排序,还是先从名字开始,结果发现完全没有头绪,说实话第一次听说这个排序时还以为是个特别神奇的高端算法,结果了解一番之后发现其实是一个被改造的插入排序,“希尔”居然是发明者的名字,所以从名字来判断算法思想在这里行不通,甚至说快速排序起码说明了这种方法排序快,而希尔排序等于什么都没说。

希尔排序的基础是插入排序,整个排序也是在新元素不断插入到有序序列适当位置的过程中完成的,唯一的不同的就是通过不同的步长将整个序列划分为不同的小序列不断插入,直到步长为1时就退化成了最基本的直接插入排序,但是此时整个序列已经“基本”有序了,需要交换的元素对比一开始直接插入的方法明显减少,从而可以加快排序的速度,因为最后步长为1的一次插入排序与简单插入排序完全相同,所以前面的几趟排序完全可以看做是最后的排序目标“打基础”,让最后一次的排序序列尽可能有序,下面描述一下希尔排序的过程,前提是你已经了解简单插入排序的过程,可以参考文章抓扑克牌风格的插入排序熟悉一下。

希尔排序

希尔排序的有一个关键的元素是步长,关于步长的选择有很多种方法,比如只选奇数,选择互为质数等等,其目的就是为了减少重复比较的次数,我们现在只为了解希尔排序的过程,所以先选择一种简单的步长选定方法,以元素个数的一半为基础,每次减少一半直到步长降为1,比如10个元素的步长选择分别为5,2,1,本质思想就是分别以步长5,2,1对整个待排序列进行简单的插入排序,最后就完成了整个序列的排序。

我们用物品重量排序作为例子吧,原来插入排序的例子是将新得到的扑克牌不断插入到有序序列中得到最终排序,这次可以直接先给出物品质量序列的初始排列,假设为99, 34, 54, 65, 11, 1, 5, 12, 89, 42,一共10件物品摆在面前,目标为将物品重量从小到大排序,首先选取步长5开始排序过程:

  1. 最开始的排序序列如下:
    99, 34, 54, 65, 11, 1, 5, 12, 89, 42

  2. 以步长为5将整个序列分为5组,分组情况如下:
    99, _, _, _, _, 1, _, _, _, _
    _, 34, _, _, _, _, 5, _, _, _
    _, _, 54, _, _, _, _, 12, _, _
    _, _, _, 65, _, _, _, _, 89, _
    _, _, _, _, 11, _, _, _, _, 42

  3. 将这五组子序列分别使用简单插入排序,得到以下序列:
    1, _, _, _, _, 99, _, _, _, _
    _, 5, _, _, _, _, 34, _, _, _
    _, _, 12, _, _, _, _, 54, _, _
    _, _, _, 65, _, _, _, _, 89, _
    _, _, _, _, 11, _, _, _, _, 42

  4. 这五个子序列组成完整的中间临时序列为:
    1, 5, 12, 65, 11, 99, 34, 54, 89, 42

  5. 然后以步长为2将整个序列划分,得到以下分组情况:
    1, _, 12, _, 11, _, 34, _, 89, _
    _, 5, _, 65, _, 99, _, 54, _, 42

  6. 将这两组子序列使用简单插入排序,得到以下序列:
    1, _, 11, _, 12, _, 34, _, 89, _
    _, 5, _, 42, _, 54, _, 65, _, 99

  7. 将子序列整体来看得到中间临时序列:
    1, 5, 11, 42, 12, 54, 34, 65, 89, 99

  8. 最后再将整个待排序列进行一次简单插入排序,便可得到最终排好的序列,实际上最后一次插入排序只有中间几个元素需要移动了:
    1, 5, 11, 12, 34, 42, 54, 65, 89, 99

代码实现

/*
功能:  交换两个变量
参数:  element1--被交换的第一个元素的地址
        element2--被交换的第二个元素的地址
返回值:无
注意:  只用来表示思路,不考虑指针为空等特殊情况
*/
void swap_data(int* element1, int* element2)
{
    int middle_value = *element1;
    *element1 = *element2;
    *element2 = middle_value;
}

/*
功能:  希尔排序,实现数组元素从小到大排列
参数:  array--表示待排序的数组,此处会退化成指针
        count--数组元素的个数
返回值:无
注意: 只用来表示思路,不考虑指针为空等特殊情况
*/
void shell_sort(int array[], int count)
{
    int step = count / 2;
    while (step > 0)
    {
        for (int pos = step; pos < count; ++pos)
        {
            for (int insert_index = pos; insert_index >= step; insert_index -= step)
            {
                if (array[insert_index] < array[insert_index - step])
                    swap_data(&array[insert_index], &array[insert_index - step]);
            }
        }
        step /= 2;
    }
}

  • 对比插入排序源代码,找找不同
void insert_sort(int array[], int count)
{
    for (int pos = 1; pos < count; ++pos)
    {
        for (int insert_index = pos; insert_index > 0; --insert_index)
        {
            if (array[insert_index] < array[insert_index - 1])
                swap_data(&array[insert_index], &array[insert_index - 1]);
        }
    }
}

代码分析

以上代码就是希尔排序的实现方式了,对比直接插入的源代码发现,如果将希尔排序的初始步长设置成1,那么整个希尔排序的代码就和简单插入排序完全一样了,这也符合我们之前分析的过程,其实希尔排序就是分多次,每次用不同的步长执行简单插入排序。

还有一点就是代码执行的过程与上面示例中的分组插入看起来有些不同,只是因为这个写起来更方便一些,分组只是为了人脑能更快的理解算法的思想,但是代码编写时还要考虑复杂性,将数据拆分成几组然后分别进行插入排序完全可以做到,但是实际上完全没有必要。

比如分成两组排序的那一步,直观上先排索引为0,2,4,6,8上的元素,依次做插入操作,然后排索引为1,3,5,7,9上的元素,在依次做插入操作,但是在实现的代码中就做了变通,反正都要做插入操作,并且步长都是2,所以可以直接对索引是0,1,2,3,4,5,6,7,8,9上的元素做插入排序,只要注意步长是2,就不会影响到其他组(实际上并不存在)的元素了,整个过程顺着代码,一步步执行就明白了。

运行测试

希尔排序–源码

如果你想运行测试一下,可以复制或者下载源代码,在本机运行测试一下,当然也可以选择在线C++编译器,把源代码复制到网页中运行查看结果,建议不明白的可以在本地环境单步调试一下,这样有助于理解算法思路。

相关文章:

  • linux环境下查找包含指定内容的文件及其所在行数
  • Mysql查询可通过给条件字段添加索引提高查询速度
  • Mysql开启、查看慢查询日志
  • IP地址常见分类:A类、B类、C类、D类、E类
  • Mysql表连接:内连接、外连接、交叉连接、自然连接真的都不一样吗
  • C/C++版本更迭历程
  • gcc编译生成可执行文件的过程中发生了什么
  • Mysql中explain命令简析
  • Python利用requests模块实现代理访问网络
  • linux环境下查看C/C++程序的堆栈信息
  • Mysql调优之Using filesort一般情况
  • gdb启动多进程程序并切换调试进程
  • Mysql中使用count加条件统计
  • 排序算法系列之(六)——逐步砍掉树杈的堆排序
  • Mysql5.7版本中数据表字段可用的类型
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • co模块的前端实现
  • python_bomb----数据类型总结
  • 基于游标的分页接口实现
  • 京东美团研发面经
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 离散点最小(凸)包围边界查找
  • 聊聊redis的数据结构的应用
  • 前端临床手札——文件上传
  • 软件开发学习的5大技巧,你知道吗?
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 用element的upload组件实现多图片上传和压缩
  • # centos7下FFmpeg环境部署记录
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • $$$$GB2312-80区位编码表$$$$
  • (arch)linux 转换文件编码格式
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (过滤器)Filter和(监听器)listener
  • (九)c52学习之旅-定时器
  • (五)IO流之ByteArrayInput/OutputStream
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • .Net mvc总结
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .Net 知识杂记
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)