从插入排序到希尔排序

从插入排序到希尔排序

插入排序

原理

插入排序是一种简单直观的排序算法,其基本思想是通过将每个元素逐个插入到已排序的部分中,逐步构建一个有序序列。

操作步骤

  1. 初始化:将第 1 个元素视为已经有序的部分(初始时长度为 1)。

  2. 遍历数组:从第 2 个元素开始,依次将其视为待插入的“新”元素。

  3. 查找位置:在已排序部分中找到该元素的正确位置,并将其插入到适当的位置。

  • 从已排序部分的末尾向前查找,找到第 1 个小(大)于等于 arr[i] 的元素位置。

  • 将 arr[i] 插入到该位置之后。

重复步骤:直到所有元素都处理完毕。

排序过程

c683ed33be08ca61062a81a9105bc4f5.png
  • 默认[ 3 ]视为有序部分。

  • 第 1 轮遍历排序 8,将其插入 3 前面,并将 [ 8 3 ] 视为有序部分。

  • 第 2 轮遍历排序 5,将其插入 8 后面,并将 [ 8 5 3 ] 视为有序部分。

  • 第 3 轮遍历排序 1,将其插入 3 后面,并将 [ 8 5 3 1 ] 视为有序部分。

  • 第 4 轮遍历排序 2,将其插入 3 后面,并将 [ 8 5 3 2 1] 视为有序部分。

  • 第 5 轮遍历排序 6,将其插入 8 后面,并将 [ 8 6 5 3 2 1 ] 视为有序部分。

  • 第 6 轮遍历排序 9,将其插入 8 前面,并将 [ 9 8 6 5 3 2 1 ] 视为有序部分。

  • 第 7 轮遍历排序 7,将其插入 8 后面,并将 [ 9 8 7 6 5 3 2 1 ] 视为有序部分。

代码实现

/**
 * @ 简单插入排序
 * @ arr - 待排序的数组     num - 数组元素个数
 * @ 要求从大到小排序数据
 * */ 
void simple_insertion_sort(int *arr, int num)
{
    int i, j, tmp;

    for (i=1; i<num; i++) {
        j = i - 1;      /* 有序部分最后一个索引 */
        tmp = arr[i];   /* 当前待插入的元素 */

        /* 1. 若插入数据比有序部分最后一个元素还小,无需处理 */
        if (arr[j] >= tmp) {
            continue;
        }

        /* 2. 查找带插入数据位置,并后移数据腾出位置 */
        while (j >= 0) {          
            if (arr[j] >= tmp)
                break;

            arr[j+1] = arr[j];
            j--;
        }

        /* 3. 插入到正确位置 */
        arr[j+1] = tmp;
    }
}

技术点

  • 提前终止内层循环:在内层循环中,如果当前已排序部分的第一个元素已经小于等于待插入元素,则无需继续比较,直接结束内层循环。这可以减少不必要的比较操作。

进阶代码

插入排序,可以理解为从序列中第 1 个元素开始,间隔固定 1 个元素,进行的序列排序。

假设程序猿想要在序列中从任意元素 (start_pos) 位置开始,间隔固定元素个数 (gap) 提取成新的序列进行插入排序,此时代码又该如何进行通用设计呢?

/**
 * @ 插入排序
 * @ arr - 待排序的数组                 num - 数组元素个数
 * @ start_pos - 待处理元素起始位置     gap - 间隔
 * @ 要求从大到小排序数据
 * */ 
void insertion_sort(int *arr, int num, int start_pos, int gap)
{
    int i;
    int index;          /* 用于有序部分数据遍历索引 */
    int insert_data;    /* 用于记录待插入的数据 */

    for (i=start_pos+gap; i<num; i+=gap) {
        index = i - gap;        /* 有序部分最后一个索引,从最后一个开始索引 */
        insert_data = arr[i];   /* 当前待插入的元素 */

        /* 1. 若插入数据比有序部分最后一个元素还小,无需处理 */
        if (arr[index] >= insert_data) {
            continue;
        }

        /* 2. 查找带插入数据位置,并后移数据腾出位置 */
        while (index >= 0) {          
            if (arr[index] >= insert_data)
                break;

            arr[index + gap] = arr[index];
            index -= gap;
        }

        /* 3. 插入到正确位置 */
        arr[index + gap] = insert_data;
    }
}
  • 注意:我们只需将start_pos = 0 且 gap = 1 就相当于简单插入排序。

运行结果


ece47449e3c1b9c27eb84ca995004761.png

总结

插入排序作为基础排序算法之一,虽然在最坏情况下性能不佳,但在某些特定场景下仍然具有其独特的优势。通过优化如“提前终止内层循环”等方法,可以在一定程度上提升效率,使其更适合于小规模数据或部分有序的数据。然而,对于大规模数据来说,仍然建议使用更高效的排序算法。

希尔排序

原理

希尔排序是一种改进的插入排序,它允许在远距离的元素之间进行比较和交换,从而减少排序的时间复杂度。

  1. 确定步长:选择一个初始步长 gap,通常取数组长度的一半。

  2. 分组排序:将数组分成若干个子序列,每个子序列中的元素间隔为当前的步长 gap

  3. 逐步缩小步长:对每一个子序列进行插入排序或其他排序算法(如交换排序),然后减小步长 gap,重复分组和排序的过程,直到步长为1,此时整个数组视为一个子序列,进行一次插入排序。

操作步骤

  1. 初始化步长:设置初始步长 gap = n / 2,其中 n 是数组的长度。

  2. 循环调整步长:当前步长减半:gap = gap / 2 直到步长为 0

  3. 分组排序:对于每个子序列(元素间隔为当前步长),进行插入排序或其他稳定排序算法。

排序过程

1e4747376233c2fb6ff384cf5ebbd4b1.png

  • 确定序列元素数量 n=8

  • 第 1 次排序

    • 将序列分为  n/2 = 4 组,每组固定间隔  gap = n/2 = 4 元素。

    • 分组情况 [ 3 2 ]   [ 8 6 ]   [ 5 9 ]   [ 1 7 ]。

    • 对于每组进行插入排序得到 [ 3 2 ]   [ 8 6 ]   [ 9 5]   [ 7 1]。

    • 合并之后的序列为 [ 3 8 9 7 2 6 5 1]。

  • 第 2 次排序

    • 将序列分为  gap/2 = 2组,每组固定间隔  gap = gap/2 = 2元素。

    • 分组情况 [ 3 9 2 5 ]   [ 8 7 6 1 ]。

    • 对于每组进行插入排序得到 [ 9 5 3 2 ]   [ 8 7 6 1 ]。

    • 合并之后的序列为 [ 9 8 5 7 3 6 2 1]。

  • 第 3 次排序

    • 将序列分为  gap/2 = 1组,每组固定间隔  gap = gap/2 = 1元素。

    • 等同于简单插入排序。

    • 排序之后的序列为 [ 9 8 7 6 5 3 2 1]。

代码实现

/**
 * @ 希尔排序,插入排序的优化
 * @ arr - 待排序的数组     num - 数组元素个数
 * @ 要求从大到小排序数据
 * */ 
void shell_sort(int *arr, int num)
{
    int i;
    int gap = num / 2;    /* 控制间距, 默认从 num/2 开始 */

    while (gap > 0) {           /* 控制间距 */
        for (i=0; i<gap; i++) { /* 控制组数 */
            /* 调用插入排序 */
            insertion_sort(arr, num, i, gap);
        }
        gap /= 2;
    }
}

分析

  • 希尔排序主要还是引入了分组的概念,通过分组将序列拆分成小序列,并且根据间隔实现远距离数据的快速排序,从而减少数据比较与移位的处理,降低时间复杂度。

  • 相对于插入排序,希尔排序主要多了两层循环,一层用于控制间隔 gap,一层用于当前间隔下各个组的插入排序处理。

运行结果

a80b7335e3a4962292b90c3a36ac4dbd.png

缺点

  • 希尔排序在大多数实现中并不是稳定的排序算法。这意味着,当两个相等的元素出现时,它们的相对顺序可能会被打乱。

  • 在某些特定的数据分布下,希尔排序的时间复杂度可能退化到O(n²),这与普通的插入排序相当,无法发挥其高效的理论优势。

  • 希尔排序的性能严重依赖于步长序列的选择。如果步长选择不当,可能导致算法效率低下或排序失败。

优化方法

  • 改进步长序列的选择:使用更科学的步长序列,如Sedgewick提出的步长序列:1, 3, 7, 15, 31,…。这种步长序列可以在一定程度上减少排序过程中的逆序数,提高算法效率。

  • 确保稳定性:在比较和交换元素时,除了比较大小外,还应考虑元素的原始位置。例如,在比较两个相等的元素时,优先保留原位置较前的元素。这可以通过在键值对中包含索引信息来实现。

  • 优化排序过程中的细节:在内循环中,尽量减少不必要的数据移动和比较操作。例如,当发现当前元素已经位于正确的位置时,可以提前终止内层循环。

  • 结合其他优化技术:将希尔排序与其他排序算法的优点结合起来,如在小规模的数据子集上使用更高效的排序方法(如归并排序或快速排序)进行优化。

完整代码

/**
 * @Filename : insertion_sort.c
 * @Revision : $Revision: 1.00 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 从插入排序一步一步到希尔排序
**/

#include <stdio.h>
#include <string.h>

#define MAX_NUM     8  
staticint cnt = 0; /* 统计排序次数 */

/**
 * @ 打印数组
 * @ arr - 待打印的数组     num - 数组元素个数
 * */
void print_arr(int *arr, int num) 
{
    int i;

    for (i=0; i<num; i++)
        printf("%02d ", arr[i]);
    printf("\n");
}

/**
 * @ 插入排序
 * @ arr - 待排序的数组                 num - 数组元素个数
 * @ start_pos - 待处理元素起始位置     gap - 间隔
 * @ 要求从大到小排序数据
 * */
void insertion_sort(int *arr, int num, int start_pos, int gap)
{
    int i;
    int index;          /* 用于有序部分数据遍历索引 */
    int insert_data;    /* 用于记录待插入的数据 */

    for (i=start_pos+gap; i<num; i+=gap) {
        index = i - gap;        /* 有序部分最后一个索引,从最后一个开始索引 */
        insert_data = arr[i];   /* 当前待插入的元素 */

        cnt++;

        /* 1. 若插入数据比有序部分最后一个元素还小,无需处理 */
        if (arr[index] >= insert_data) {
            printf("无需移位 第%02d次排序: ", cnt);
            print_arr(arr, num);
            continue;
        }

        /* 2. 查找带插入数据位置,并后移数据腾出位置 */
        while (index >= 0) {          
            if (arr[index] >= insert_data)
                break;

            arr[index + gap] = arr[index];
            index -= gap;
        }

        /* 3. 插入到正确位置 */
        arr[index + gap] = insert_data;

        printf("需要移位 第%02d次排序: ", cnt);
        print_arr(arr, num);
    }
}

/**
 * @ 简单插入排序
 * @ arr - 待排序的数组     num - 数组元素个数
 * @ 要求从大到小排序数据
 * */
void simple_insertion_sort(int *arr, int num)
{
    printf("排序前数组内容: ");
    print_arr(arr, num);

    cnt = 0;    /* 清空统计次数 */
    insertion_sort(arr, num, 0, 1);

    printf("排序后数组内容: ");
    print_arr(arr, num);
}

/**
 * @ 希尔排序,插入排序的优化
 * @ arr - 待排序的数组     num - 数组元素个数
 * @ 要求从大到小排序数据
 * */
void shell_sort(int *arr, int num)
{
    int i;
    int gap = num / 2;    /* 控制间距, 默认从 num/2 开始 */

    printf("排序前数组内容: ");
    print_arr(arr, num);

    cnt = 0;    /* 清空统计次数 */

    while (gap > 0) {           /* 控制间距 */
        for (i=0; i<gap; i++) { /* 控制组数 */
            /* 调用插入排序 */
            insertion_sort(arr, num, i, gap);
        }
        gap /= 2;
    }

    printf("排序后数组内容: ");
    print_arr(arr, num);
}

int main(void)
{
    int buf1[] = {3, 8, 5, 1, 2, 6, 9, 7};
    int buf2[] = {3, 8, 5, 1, 2, 6, 9, 7};

    printf ("----------简单插入排序---------\n");
    simple_insertion_sort(buf1, MAX_NUM);

    printf ("---------希尔排序---------\n");
    shell_sort(buf2, MAX_NUM);
    
    return0;
}

运行结果

c57af05138c3eb103dd0e2b23c4efe99.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/970335.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AcWing——3624. 三值字符串

双指针解法 #include<iostream> #include<unordered_map> using namespace std; int main() {int n; cin >> n;while(n--){unordered_map<char, int> tree;string s; cin >> s;int ans 0x7fffffff; for(int i 0, j 0; j < (int)s.size();…

【Vue3源码解析】响应式原理

源码环境搭建 【Vue3源码解析】应用实例创建及页面渲染-CSDN博客 写文章时的Vue 版本&#xff1a; "version": "3.5.13",针对单个包进行开发环境打包、测试。 pnpm run dev reactivityreactive 创建响应式对象 packages/reactivity/src/reactive.ts …

刷题记录(回顾)HOT100 二叉树-10: ​199. 二叉树的右视图

题目&#xff1a;199. 二叉树的右视图 难度&#xff1a;中等 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左…

书籍推荐:《书法课》林曦

记得樊登老师说过&#xff0c;如果你想了解一个事物&#xff0c;就去读5本相关的书&#xff0c;你会比大部分人都更了解它。这是我读的第4本和“书法”有关的书&#xff0c;作为一个零基础的成年人&#xff0c;林曦这本《书法课》非常值得一读。&#xff08;无论你是否写字&…

一个根据输入内容过滤下拉选的组件

1.element的select自定义过滤不是很灵&#xff0c;使用了input和dropdown 组件 <template><div class"autocomplete-wrapper"><!-- 使用 el-input 组件 --><el-inputv-model"inputValue"input"handleInput"placeholder&q…

5G与物联网的协同发展:打造智能城市的未来

引言 随着科技的不断进步&#xff0c;智能城市的概念已经不再是科幻小说中的幻想&#xff0c;它正在逐步走进我们的生活。而这背后的两大驱动力无疑是 5G和 物联网&#xff08;IoT&#xff09;。5G网络以其高速率、低延迟、大容量的优势&#xff0c;与物联网的强大连接能力相结…

SpringBoot 与 SpringCloud的版本对应详细版

| Greenwich版本 | 兼容Spring Boot 2.1.x | | Hoxtonl版本 | 兼容Spring Boot 2.2.x | 在实际开发过程中&#xff0c;我们需要更详细的版本对应&#xff1a; | Spring Boot | Spring Cloud | | — | — | | 1.5.2.RELEASE | Dalston.RC1 | | 1.5.9.RELEASE | Edgware.RE…

最新国内 ChatGPT Plus/Pro 获取教程

最后更新版本&#xff1a;20250202 教程介绍&#xff1a; 本文将详细介绍如何快速获取一张虚拟信用卡&#xff0c;并通过该卡来获取ChatGPT Plus和ChatGPT Pro。 # 教程全程约15分钟开通ChatGPT Plus会员帐号前准备工作 一个尚未升级的ChatGPT帐号&#xff01;一张虚拟信用卡…

现在有什么赛道可以干到退休?

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;点击跳转到网站 &#xff0c;对人工智能感兴趣的小伙伴可以点进去看看。 最近&#xff0c;一则“90后无论男女都得65岁以后退休”的消息在多个网…

electron打包基本教程

从0开始搭建 概要步骤基础软件运行项目打包项目 注意事项 概要 将html打包成桌面的主流有electron和nwjs&#xff0c;nwjs更加简单&#xff0c;但是使用效果不如electron&#xff0c;electron打包比较麻烦&#xff0c;但是效果比较好&#xff0c;反正各有优势和缺点 步骤 基…

ESXi安装【真机和虚拟机】(超详细)

项目简介&#xff1a; ESXi&#xff08;Elastic Sky X Integrated&#xff09;是VMware公司开发的一种裸机虚拟化管理程序&#xff0c;允许用户在单一物理服务器上运行多个虚拟机&#xff08;VM&#xff09;。它直接安装在服务器硬件上&#xff0c;而不是操作系统之上&#xff…

【kafka系列】消费者重平衡

目录 流程 1. 消费者组重平衡&#xff08;Rebalance&#xff09;的流程逻辑分析 阶段一&#xff1a;触发重平衡 阶段二&#xff1a;消费者组协调 阶段三&#xff1a;重平衡完成 关键设计思想 2. Mermaid 流程代码 关键点总结 重平衡的影响 1. 重平衡期间的消费行为 2…

基于SpringBoot+Vue的高校线上心理咨询室的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

DeepSeek学术秘籍:如何让DeepSeek辅助论证?

随着人工智能技术的飞速发展&#xff0c;AIGC技术在学术领域的应用逐渐引起了广泛关注。其中最近大火的DeepSeek作为一款基于大语言模型的应用&#xff0c;其出现标志着学术论文写作中研究方法的一次重大变革。 辅助论证 在学术论文写作中&#xff0c;借助DeepSeek优化辅助论证…

常见的排序算法:插入排序、选择排序、冒泡排序、快速排序

1、插入排序 步骤&#xff1a; 1.从第一个元素开始&#xff0c;该元素可以认为已经被排序 2.取下一个元素tem&#xff0c;从已排序的元素序列从后往前扫描 3.如果该元素大于tem&#xff0c;则将该元素移到下一位 4.重复步骤3&#xff0c;直到找到已排序元素中小于等于tem的元素…

DockerFile优化镜像体积

title: DockerFile优化镜像体积 date: 2025-02-15 15:22:40 tags: DockerFile优化镜像体积DockerFile优化镜像体积 DockerFile优化镜像体积前文回顾:一、细数优化镜像体积的思路与方式二、优化Dockfile文件编辑 Dockerfile2文件三、构建镜像四、运行镜像五、查看运行效果原文 …

从零开始-将小爱接入大模型

文章目录 前言一、学习教程二、docker安装二、项目下载和配置三、文件修改文件.env.exampledeepseek模型 注册gitee并获取密钥 文件.migpt.example.js连接小爱同学 三、运行项目创建目录启动docker容器 总结 前言 基于当前人工智能的发展&#xff0c;大模型使用越来越方便&…

动态规划dp_4

一.背包 如果求组合数就是外层for循环遍历物品&#xff0c;内层for遍历背包。 如果求排列数就是外层for遍历背包&#xff0c;内层for循环遍历物品。 二.题 1. 思路&#xff1a;dp五部曲&#xff0c;思路在注释 /* dp[i]表示&#xff1a;到达第 i 个台阶有dp[i]种方法 状态转…

SpringBoot整合easy-es

一、easy-es简介 EasyES是一款基于Elasticsearch官方提供的RestHighLevelClient开发的ORM框架&#xff0c;旨在简化开发流程并提高效率。 EasyES在保持RestHighLevelClient原有功能的基础上进行增强&#xff0c;而不做任何改变。它采用与Mybatis-Plus相似的语法&#xff0c;使得…

B样条曲线插值边界条件

以下内容来自deepseek&#xff0c;准确性未知&#xff0c;若有疑问&#xff0c;欢迎交流讨论。 1. 切矢条件&#xff08;Tangent Vector Condition&#xff09; 定义&#xff1a;在曲线的起点或终点处指定一阶导数&#xff08;切线方向&#xff09;&#xff0c;强制曲线在端点…