内部排序之一:插入排序和希尔排序的N中实现

2024-09-02 06:08

本文主要是介绍内部排序之一:插入排序和希尔排序的N中实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

   本来想将所有的内部排序总结为一篇博文,但是随着研究的深入,还是放弃了这个念头,斟前酌后,还是觉得分开来写比较好,具体原因,看完本篇博文也就自然明了了。

   本篇文章主要探讨插入排序和希尔排序,之所将二者放在一起,很明显,是因为希尔排序是建立在插入排序的基础之上的。

   注:以下各排序算法的N种实现方法大部分都是我根据算法思想,自己写出来的,或者是参考其本身的经典实现,我自己都已测试通过,但不敢保证一定都没问题,如果有疑问,欢迎指出。

插入排序

   插入排序的思想很简单,它的基本操作就是将一个数据插入到已经排好序的序列中,从而得到一个新的有序序列。根据查找插入位置的实现思路不同,它又可以分为:直接插入排序、折半插入排序、2-路插入排序。。。这里,我们主要探讨下直接插入排序和折半插入排序。

   直接插入排序

   直接插入排序是最基本的插入排序方法,也是一种最简单的排序方法。其基本实现思想如下:

   1、首先把第一个元素单独看做一个有序序列,依次将后面的元素插入到该有序序列中;

   2、插入的时候,将该元素逐个与前面有序序列中的元素进行比较,找到合适的插入位置,形成新的有序序列;

   3、当有序序列扩大为整个原始序列的大小时,排序结束。

     第一种实现方法

   按照该思想,我第一次写出来的实现代码如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
第一种代码形式
插入排序后的顺序为从小到大
*/
void Insert_Sort1( int *arr, int len) 
     int i; 
     //从第1个元素开始循环执行插入排序 
     for (i=1;i<len;i++)    
     {   //将第i个元素分别与前面的元素比较,插入适当的位置 
         if (arr[i]<arr[i-1]) 
         {   //一直向左进行比较,直到插入到适当的位置 
             int key = arr[i]; 
             int count = 0;  //用来记录key在与前面元素时向左移动的位置 
             while (i>0 && key<arr[i-1]) 
            
                 arr[i] = arr[i-1]; 
                 arr[i-1] = key; 
                 i--; 
                 count++; 
            
             //将待插入的数定位到下一个元素, 
             //因为后面还要执行i++,所以这里不再加1 
             i += count;   
         }  
    
}

第二种实现方法


    很明显,上面的代码有些冗杂,如果面试的时候让你手写插入排序的代码,很难一下子写出来。于是,我考虑将while循环去掉,直接在后面再来一个for循环,每次比较,遇到比自己大的就交换,直到遇到比自己小的,才退出for循环。这样代码改成了如下形式:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
第二种代码形式
插入排序后的顺序为从小到大
*/
void Insert_Sort2( int *arr, int len) 
     int i,j; 
     for (i=1;i<len;i++) 
         for (j=i-1;j>=0 && arr[j]>arr[j+1];j--) 
        
             //交换元素数值 
             //由于arr[j]不等于arr[j+1], 
             //因此可以安全地用该交换方法 
             arr[j]^=arr[j+1]; 
             arr[j+1]^=arr[j]; 
             arr[j]^=arr[j+1]; 
        
}

第三种实现方法


   上面的代码要用到数据的交换,即每次要插入的元素要逐个地与前面比它大的元素互换位置,而数据交换需要三步赋值操作,我们完全可以避免进行如此多的操作(排序算法中一般都会尽量避免数据的交换操作),为了提高执行效率(虽然该执行效率的提高可能并没有那么显著),我们再回过头来看第一种实现方法,我们可以通过key变量先将待插入的数据保存起来,在比较时只将元素右移一位即可,最后再将key放到要插入的位置,这样可以减少两个赋值操作的执行时间。这样我们可以把代码改成如下实现形式:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
第三种代码形式
插入排序后的顺序为从小到大
*/
void Insert_Sort3( int *arr, int len) 
     int i,j; 
     for (i=1;i<len;i++) 
         if (arr[i] < arr[i-1]) 
         {   //向前逐个比较,直到需要插入的地方 
             int key = arr[i]; 
             for (j=i-1;j>=0 && arr[j]>key;j--) 
                 arr[j+1] = arr[j]; 
             arr[j+1] = key;    //插入key 
        
}

这也是最常见的实现形式,如果在面试中要手写插入排序的话,直接把这种实现代码写出来就可以了。

   另外,很明显可以看出来,对于长度为n的待排序咧,直接插入排序的平均时间复杂度为O(n*n),而且直接插入排序的比较次数与原始序列的中各元素的位置密切相关,待排序的序列越接近于有序,需要比较的次数就越小,时间复杂度也就越小。



   折半插入排序

    直接插入排序算法简单,且容易实现,当待排序的长度n很小时,是一种很好的排序方法,尤其当原始序列接近有序时,效率更好。如果待排序的长度n很大,则不适宜采用直接排序。这时我们可以考虑对其做些改进,我们可以从减少比较和移动的次数入手,因此可以采用折半插入排序,其思想类似于折半查找,这里不再详细说明,直接给出实现代码:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
插入排序后的顺序为从小到大
*/
void BInsert_Sort( int *arr, int len) 
     int i; 
     //从第1个元素开始循环执行插入排序 
     for (i=1;i<len;i++)    
     {    
         int low =0; 
         int high = i-1; 
         int key = arr[i]; 
         //循环至要插入的两个点之间 
         while (low<=high) 
        
             int mid = (low+high)/2;  
             if (key<arr[mid]) 
                 high = mid-1; 
             else
                 low = mid+1; 
        
         //循环结束后low=high+1,并且low位置即为key要插入的位置 
       
         //从low到i的元素依次后移一位 
         int j; 
         for (j=i;j>low;j--) 
             arr[j] = arr[j-1]; 
         //将key插入到low位置处 
         arr[low] = key; 
    
}

从代码中可以看出,折半插入排序所需附加的存储空间与直接插入排序相等,时间上来看,折半插入排序减少了比较的次数,但是元素的移动次数并没有减少。因此,折半插入排序的平均时间复杂度仍为O(n*n)。



希尔排序    希尔排序(shell排序),又称缩小增量排序。上面我们提到,直接插入排序在原始序列越接近有序的情况下,排序效率越高,希尔排序正是利用了直接插入排序的这个优势。希尔排序的基本思想如下:



   它将序列按照某个增量间隔分为几个子序列,分别对子序列进行插入排序,而后再取另一增量间隔,对划分的子序列进行插入排序,依次往后。。。待序列已经大致有序,最后再对整个序列进行插入排序(即增量间隔为1),从而得到有序序列。

   本文的重点放在排序算法的各种代码实现上,因此不再对具体的实现思想做过多的阐述,读者可以查阅相关资料或书籍来熟悉希尔排序的具体思想。由于希尔排序要用到插入排序,因此,我们依次根据上面基本插入排序的三种不同实现方法来书写希尔排序的代码。


   第一种实现方法


   仔细分析希尔排序的实现思想,会发现,如果要循环对各个子序列依次进行插入排序,我们需要在直接插入排序代码的外面再加一层for循环,用来循环所有的子序列。我们根据插入排序的第一种实现方法写出的代码如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
第一种形式的代码
对长为len的数组进行一趟增量为ader的插入排序
本算法在插入排序算法的第一种实现形式上进行修改得到
*/
void Shell_Insert1( int *arr, int len, int ader) 
     int i,k; 
     //循环对ader个子序列进行插入排序操作 
     for (k=0;k<ader;k++) 
         for (i=ader+k;i<len;i+=ader)      //对一个子序列进行插入排序操作 
         {   //将第i个元素分别与前面的每隔ader个位置的元素比较,插入适当的位置 
             if (arr[i]<arr[i-ader]) 
             {   //一直向左进行比较,直到插入到适当的位置 
                 int key = arr[i]; 
                 int count = 0;  //用来记录key在与前面元素比较时向左移动了几个ader长度 
                 while (i>k && key<arr[i-ader]) 
                
                     arr[i] = arr[i-ader]; 
                     arr[i-ader] = key; 
                     i -= ader; 
                     count++; 
                
                 //将待插入的数定位到下一个元素,执行下一次插入排序 
                 //因为后面还要执行i+=ader,所以这里回到原位置即可 
                 i += count*ader;   
             }  
        
}

第二种实现方法

很明显,与上面插入排序的第一种实现方法一样,更加冗杂,现在我们用插入排序的第二种实现方法来实现希尔排序,同样采用添加外层for循环的方式,来循环对每个子序列进行插入排序。代码如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
第二种形式的代码
对长为len的数组进行一趟增量为ader的插入排序
本算法在插入排序算法的第三种实现形式上进行修改得到
*/
void Shell_Insert2( int *arr, int len, int ader) 
     int i,j,k; 
     //循环对ader个子序列各自进行插入排序 
     for (k=0;k<ader;k++) 
         for (i=ader+k;i<len;i+=ader) 
             for (j=i-ader;j>=k && arr[j]>arr[j+ader];j-=ader) 
            
                 //交换元素数值 
                 arr[j]^=arr[j+ader]; 
                 arr[j+ader]^=arr[j]; 
                 arr[j]^=arr[j+ader]; 
            
}

第二种实现方法的改进


   上面的代码中需要三个for循环,因为我们是循环对每个子序列进行插入排序的,实际上我们还可以这样做:对每个子序列交叉进行排序。比如,第1个子序列中的第2个元素A5(A5表示它在总序列A中的位置序号是5,下同)刚进行完插入排序操作,便接着对第2个子序列中的第2个元素A6进行插入排序操作。这样我们就可以少写一个for循环,但实际比较的次数还是相同的,只是代码更加简洁。如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
在第二种代码的形式上继续精简代码
交叉进行各个子序列的插入排序
*/
void Shell_Insert2_1( int *arr, int len, int ader) 
     int i,j; 
     //交叉对ader个子序列进行插入排序 
         for (i=ader;i<len;i++) 
             for (j=i-ader;j>=0 && arr[j]>arr[j+ader];j-=ader) 
            
                 //交换元素数值 
                 //由于arr[j]不等于arr[j+1], 
                 //因此可以安全地用该交换方法 
                 arr[j]^=arr[j+ader]; 
                 arr[j+ader]^=arr[j]; 
                 arr[j]^=arr[j+ader]; 
            
}

第三种实现方法

  同样,根据插入排序的第三种实现方法,循环逐个对每个子序列进行插入排序操作,我们可以得到希尔排序的实现方法,如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
第三种形式的代码
对长为len的数组进行一趟增量为ader的插入排序
本算法在插入排序算法的第二种实现形式上进行修改得到
*/
void Shell_Insert3( int *arr, int len, int ader) 
     int i,j,k; 
     //循环对ader个子序列各自进行插入排序 
     for (k=0;k<ader;k++) 
         for (i=ader+k;i<len;i+=ader) 
             if (arr[i] < arr[i-ader]) 
            
                 int key = arr[i]; 
                 for (j=i-ader;j>=k && arr[j]>key;j-=ader) 
                     arr[j+ader] = arr[j]; 
                 arr[j+ader] = key; 
            
}

第三种实现方法的改进


   我们可以对该方法做出同样的改进,对各个子序列进行交叉排序,代码如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
在第三种代码的形式上继续精简代码
交叉进行各个子序列的插入排序
*/
void Shell_Insert3_1( int *arr, int len, int ader) 
     int i,j; 
     //对ader子序列交叉进行插入排序 
     for (i=ader;i<len;i++) 
         if (arr[i] < arr[i-ader]) 
        
             int key = arr[i]; 
             for (j=i-ader;j>=0 && arr[j]>key;j-=ader) 
                 arr[j+ader] = arr[j]; 
             arr[j+ader] = key; 
        
}

同样,如果在面试中要手写希尔排序的代码,推荐这种方法实现的代码。

   希尔排序的时间复杂度根据选择的增量序列不同会有所不同,但一般都会比n*n小(序列长度为n)。

   在选择增量序列时,应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须为1.

   看如下两个增量序列:

n/2、n/4、n/8...1

1、3、7...2^k-1

   第一个序列称为希尔增量序列,使用希尔增量时,希尔排序在最坏情况下的时间复杂度为O(n*n)。

   第二个序列称为Hibbard增量序列,使用Hibbard增量时,希尔排序在最坏情况下的时间复杂度为O(n^3/2)。

   经验研究表明,在实践中使用这些增量序列要比使用上面两个增量序列的效果好的多,其中最好的序列是

{1、5、9、41、109...}

   该序列中的项或是9*4^i-9*2^i+1,或是4^i-3*2^i+1。

   希尔排序的性能是完全可以接受的,即时是对数以万计的n来说也是如此。编程的简单特点使得它成为对适度的大量输入数据进行排序时经常选用的算法。

完整代码下载    各种实现方式的完整的代码打包下载地址:http://download.csdn.net/detail/mmc_maodun/6969381


来源: csdn   作者:兰亭风雨   
源自:http://tech.ddvip.com/2014-03/1394804220209161.html

这篇关于内部排序之一:插入排序和希尔排序的N中实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Python Flask实现定时任务的不同方法详解

《PythonFlask实现定时任务的不同方法详解》在Flask中实现定时任务,最常用的方法是使用APScheduler库,本文将提供一个完整的解决方案,有需要的小伙伴可以跟随小编一起学习一下... 目录完js整实现方案代码解释1. 依赖安装2. 核心组件3. 任务类型4. 任务管理5. 持久化存储生产环境

详解Java中三种状态机实现方式来优雅消灭 if-else 嵌套

《详解Java中三种状态机实现方式来优雅消灭if-else嵌套》这篇文章主要为大家详细介绍了Java中三种状态机实现方式从而优雅消灭if-else嵌套,文中的示例代码讲解详细,感兴趣的小伙伴可以跟... 目录1. 前言2. 复现传统if-else实现的业务场景问题3. 用状态机模式改造3.1 定义状态接口3

基于Python实现温度单位转换器(新手版)

《基于Python实现温度单位转换器(新手版)》这篇文章主要为大家详细介绍了如何基于Python实现温度单位转换器,主要是将摄氏温度(C)和华氏温度(F)相互转换,下面小编就来和大家简单介绍一下吧... 目录为什么选择温度转换器作为第一个项目项目概述所需基础知识实现步骤详解1. 温度转换公式2. 用户输入处

MySQL实现多源复制的示例代码

《MySQL实现多源复制的示例代码》MySQL的多源复制允许一个从服务器从多个主服务器复制数据,这在需要将多个数据源汇聚到一个数据库实例时非常有用,下面就来详细的介绍一下,感兴趣的可以了解一下... 目录一、多源复制原理二、多源复制配置步骤2.1 主服务器配置Master1配置Master2配置2.2 从服

Java实现TXT文件导入功能的详细步骤

《Java实现TXT文件导入功能的详细步骤》在实际开发中,很多应用场景需要将用户上传的TXT文件进行解析,并将文件中的数据导入到数据库或其他存储系统中,本文将演示如何用Java实现一个基本的TXT文件... 目录前言1. 项目需求分析2. 示例文件格式3. 实现步骤3.1. 准备数据库(假设使用 mysql

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

SpringBoot集成P6Spy的实现示例

《SpringBoot集成P6Spy的实现示例》本文主要介绍了SpringBoot集成P6Spy的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录本节目标P6Spy简介抛出问题集成P6Spy1. SpringBoot三板斧之加入依赖2. 修改

Python实现数据可视化图表生成(适合新手入门)

《Python实现数据可视化图表生成(适合新手入门)》在数据科学和数据分析的新时代,高效、直观的数据可视化工具显得尤为重要,下面:本文主要介绍Python实现数据可视化图表生成的相关资料,文中通过... 目录前言为什么需要数据可视化准备工作基本图表绘制折线图柱状图散点图使用Seaborn创建高级图表箱线图热

Redis分布式锁中Redission底层实现方式

《Redis分布式锁中Redission底层实现方式》Redission基于Redis原子操作和Lua脚本实现分布式锁,通过SETNX命令、看门狗续期、可重入机制及异常处理,确保锁的可靠性和一致性,是... 目录Redis分布式锁中Redission底层实现一、Redission分布式锁的基本使用二、Red

基于Python实现数字限制在指定范围内的五种方式

《基于Python实现数字限制在指定范围内的五种方式》在编程中,数字范围限制是常见需求,无论是游戏开发中的角色属性值、金融计算中的利率调整,还是传感器数据处理中的异常值过滤,都需要将数字控制在合理范围... 目录引言一、基础条件判断法二、数学运算巧解法三、装饰器模式法四、自定义类封装法五、NumPy数组处理