八大排序算法之插入排序

来源:互联网 发布:网眼监控软件 编辑:程序博客网 时间:2024/06/05 12:03

        八大排序算法之插入排序

  排序分为内部排序和外部排序,内部排序时数据记录在内存中进行排序,适用于数据量比较少的情况;而外部排序因数据量太大,内存一次不能容纳所有的数据,此时需要借助文件来进行排序。

        本文这里介绍的八大排序为内部排序,然后通过内部排序来实现一种外部排序。

        下面先对排序进行分类如下:


        

在具体实现排序之前,先介绍三个衡量算法性能的标准—时间复杂度、空间复杂度和稳定性。

时间复杂度:一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。

空间复杂度:空间复杂度是指算法在计算机内执行时所需存储空间的度量

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

这里先介绍插入排序中的直接插入排序和希尔排序,以下例子以升序为例。

一、直接插入排序(Insert Sort)

1.基本思想

直接插入排序的思想和生活中打扑克理牌的过程。打扑克牌时要将每张牌按顺序整理好,比如说刚开始摸了张7,

只有一张牌不用排序;又摸了张5,从后往前找第一张比5打的牌,将5插入该牌之前;然后摸了张3,同理将3插在已有的牌中第一张比3小的牌之前,同理后面出现的牌根据点数依次找到插入的位置即可。

  直接插入排序的过程和上面描述的整理扑克牌的过程很相似。总结一下分为三步:(1)找位置;(2)移动数组;(3)将待排序的书插入。

2.排序过程

       以数组{7,5,3,9,6}为例进行说明,排序过程如下:



3.代码实现与分析

(1)代码实现

void Insert_Sort(int *pArray, int iLen){int iCount;int iIndex;int iTemp;//n个数最多排(n-1)趟for (iCount = 1; iCount < iLen; ++iCount){iTemp = pArray[iCount];//先将待排序元素存入临时变量中//找位置,从前往后找第一个比待排序元素大的元素,记录其位置for (iIndex = 0; iIndex < iCount; ++iIndex){if (iTemp < pArray[iIndex]){break;}}//找到第一个比待排序元素大的元素以后,整体后移for (int i = iCount; i > iIndex; --i){pArray[i] = pArray[i - 1];}//整体后移以后插入待排序元素pArray[iIndex] = iTemp;}}
 

(2)分析

        一般分析一个算法都是从时间复杂度、空间复杂度和稳定性来进行分析。

a)时间复杂度

       从程序中可以看出,嵌套了两层循环。第一层循环用于控制循环的次数,N个数训话N-1次;第二层循环用于找元素插入的位置和移动元素,这两个循环是并列关系,所以在计算时间复杂度时可以看成一层循环。所以时间复杂度为O(N^2).

b)空间复杂度

       从上面代码中可以看出,不管有多少个元素进行排序,都只使用了一个额外的临时变量,所以空间复杂度为O(1)。

c)稳定性

       本算法是稳定的,读者可以根据程序自行证明一下。

4.程序改进

      上面的程序在最好的情况下(即原始数组有序)的时间复杂度也为O(N^2),这就让我们觉得不太合理,应该有一种方法在数组有序的情况下加速排序过程。仔细思考上面的代码,发现之所以最好的情况下时间复杂度也为O(N^2),是因为以下这个循环始终都会进行:

       

        for (iIndex = 0;iIndex < iCount; ++iIndex)        {            if (iTemp < pArray[iIndex])            {                              break;            }        }

        因为每次都是从前往后找,所以即使原始数据有序循环还是全部执行。

为了改善上面的问题,我们换一种思路。上面的程序是从前往后找,找到插入的位置以后再整体移动;我们可以从后往前找,找第一个比待排序元素小的元素,假设现在序列为{5, 7, 3}

待排序元素为3,此时3先和7比较,发现7比3大,此时7往后移一个位置(这里就是和上面程序不同之处,将比较和移位放在一个循环中进行);然后3和5比较,发现5也比3大,然后5也后移一个位置,此时再将3插入到5之前即可。

 

代码实现如下:

void Insert_Sort_(int *pArray, int iLen){int iCount;int iIndex;int iTemp;//n个数最多排(n-1)趟for (iCount = 1; iCount < iLen; ++iCount){iTemp = pArray[iCount];//先将待排序元素存入临时变量中//找位置,从后往前找第一个比待排序元素小的元素。改进之处:边找边移位for (iIndex = iCount - 1; iIndex >= 0; --iIndex) // 1 2 3 4 5{if (iTemp >= pArray[iIndex]){break;}else{pArray[iIndex + 1] = pArray[iIndex];}}//插入待排序元素pArray[iIndex + 1] = iTemp;}}


分析:

       此时假设元素数组元素有序{1,2,3,4,5},里面的循环每次都只比较一次就break了,所以此时的时间复杂度在最好的情况下降到了O(1)。

小结:直接插入排序越有序越快



二、希尔排序

1.基本思想

        希尔排序和直接插入排序一样都是插入排序的一种,而且希尔排序是在直接插入排序的基础上发展而来,因为每一趟希尔排序的过程用的都是直接插入排序。借助直接插入排序越有序越快这一特点,我们先对原始数据进行间隔性分组,先使组内有序,然后再缩小间隔,再使组内有序;直到间隔为1的时候,再进行排序即可。

        总结一下步骤:(1)先定义分组间隔;(2)根据间隔进行分组,使用直接插入排序使组内有序;(3)最后一趟排序过程的间隔必须为1,此时再使用直接插入排序即可。

2.排序过程

        假设原始待排序列为{12, 4,6, 9, 0, 34, 56, 7, 8, 10, 23, 45, 21, 11, 2},间隔序列为{5, 3, 1}。刚开始以5为间隔进行分组,则分组情况如下:




同理,可得到间隔为3和间隔为1时的排序序列。

(1).代码分析与实现

void ShellSort(int *pArray, int iLen){int iDlt_a[] = {3, 1 };for (int i = 0; i < (sizeof(iDlt_a) / sizeof(*iDlt_a)); ++i){Shell(pArray, iLen, iDlt_a[i]);}}//一趟希尔排序其实就是一趟直接插入排序void Shell(int *pArray, int iLen, int dlt){int iCount;int iIndex;int iTemp;for (iCount = dlt; iCount < iLen; iCount++)  //需要iLen-dlt趟{iTemp = pArray[iCount];//临时变量暂存待排序的值for (iIndex = iCount - dlt; iIndex >= 0; iIndex -= dlt){if (pArray[iIndex] < iTemp) //满足条件说明找到第一个比待排序元素小的元素,iIndex指向其位置{break;}else{pArray[iIndex + dlt] = pArray[iIndex];}}pArray[iIndex + dlt] = iTemp;//iIndex指向的是第一个比待排序元素小的元素的位置,我们应该把待排序元素插在iIndex之前,即iIndex+dlt}}

(2)分析

       希尔排序的分析是一个复杂的问题,因为它的时间是所取“增量”序列的函数,这涉及到数学上一些尚未解决的难题。下面的分析摘自《数据结构(严蔚敏)》,仅供参看。

a)时间复杂度

      时间复杂度为O(n^1.3) ~ O(n^1.5).

b)空间复杂度

      从上面的程序中可以看出,用到的额外空间主要是一个增量序列和一个临时变量,这两个在程序运行前都是确定好的,是不会随着待排序规模的增改而改变的,所以希尔排序的时间复杂度为O(1)。

c)稳定性

     不稳定。跳跃式的交换数据都不稳定。










原创粉丝点击