【算法】插入类排序

简介

插入类排序的基本思想是在一个已排好序的记录子集基础上,每一步将下一个待排序的几步有序插入已排好的记录子集,直到将所有待排记录全部插入为止

1:直接插入排序

例子

对于序列{14, 62, 35, 77, 55, 14, 35, 95}而言,其顺序如下

{48} 62 35 77 55 14 *35* 98
{48 62} 35 77 55 14 *35* 98
{35 48 62} 77 55 14 *35* 98
{35 48 62 77} 55 14 *35* 98
{35 48 55 62 77} 14 *35* 98
{14 35 48 55 62 77} *35* 98
{14 35 *35* 48 55 62 77} 98
{14 35 *35* 48 55 62 77 98}

为了提高效率,附设一个监视哨data[0],使得data[0]始终存放待插入得记录。

该监视哨作用有二:

  1. 备份待插入记录,以便前面关键字较大得记录后移

  2. 防止越界

算法实现

void InsertSortion::directInsertionSort(TargetType *data, int length)
{
    int j;
    for (size_t i = 2; i <= length; i++)
    {
        data[0] = data[i]; // 将待插入数据记录到监视哨data[0]
        j = i - 1;
        while (data[0] < data[j]) // 寻找插入位置,同时移动元素位置
        {
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = data[0]; // 将待插入数据插入已排序的序列
    }
}

在插入排序中:

  • 最坏情况为逆序,每一趟比较移动次数均为\(i-1\),此时总比较次数为\(\frac{(n+2)(n-1)}{2}\),即\(\sum_{i=2}^{n}i\),记录次数也达到最大,为

    \(\frac{(n+4)(n-1)}{2}\),即\(\sum_{i=2}^{n}(i+1)\)

  • 最好情况为顺序,此时需比较\(n-1\)次,记录移动\(2(n-1)\)次。

由于排序记录大概率随机,出现各种排列概率相同,故取平均值。时间复杂度\(T(n)=O(n^2)\)

2:折半插入排序

折半插入排序主要是将查找过程利用二分查找进行优化,使其查找复杂度达到了\(O(n\log_{2}{n})\),但由于其移动元素的时间消耗不变,时间复杂度仍然是\(O(n^2)\)

算法实现

void InsertSortion::halfInsertionSort(TargetType *data, int length)
{
    int temp, low, high, mid;
    for (size_t i = 2; i <= length; i++)
    {
        temp = data[i];
        low = 1;
        high = i - 1;
        while (low <= high) // 确定插入位置
        {
            mid = (low + high) / 2;
            if (temp < mid)
            {
                high = mid - 1;
            }
            else
            {
                low = mid + 1;
            }
        }
        for (size_t j = i - 1; j >= low; j++)   // 开始移动
        {
            data[j + 1] = data[j];
        }
        data[low] = temp;
    }
}

3:希尔排序

希尔排序又称缩小增量排序,是一张基于插入思想的排序方法。

算法思想

先将待排序记录序列分割为若干个较稀疏的子序列,分别进行插入排序。经过上述粗略调整后,再对全部记录进行一次直接插入排序。

  1. 选定记录间的距离\(d_i(i=1)\),在整个待排序记录序列中,将所有间隔为\(d_1\)的记录分为一组,进行组内直接插入排序
  2. \(i=i+1\),记录间的距离为\(d_i(d_i<d_{i-1})\),在整个待排序记录序列中,将所有间隔为\(d_i\)的记录分为一组,进行组内直接插入排序。

重复步骤2多次,直到\(d_i=1\),此时只有一个子序列,对该序列进行直接插入排序即可。

例子

初始关键字序列:{46, 55, 13, 42, 94, 17, 05, 70}

\(d_i=4\),则分为4个间隔为4的子序列:

{ 46 55 13 42 94 17 05 70 }

对其每一个子序列进行插入排序,得:

{ 46 17 05 42 94 55 13 70 }

再取\(d_i=2\),分为两个间隔为2的子序列:

{ 46 17 05 42 94 55 13 70}

对其两个子序列进行插入排序:

{ 05 17 13 42 46 55 94 70}

\(d_i=1\),对整体进行插入排序,得到结果:

{ 05 13 17 42 46 55 70 94 }

算法实现

void InsertSortion::shellInsert(TargetType *data, int length, int delta)
{
    /* 对记录数字data做一趟希尔排序*/
    for (int i = 1 + delta; i <= length; i++)
    {
        if (data[i] < data[i - delta])
        {
            data[0] = data[i]; // 备份data[i],不做监视哨
            int j = i - delta;
            for (j = i - delta; (j > 0) && (data[0] < data[j]); j -= delta)
            {
                data[j + delta] = data[j];
            }
            data[j + delta] = data[0];
        }
    }
}
/**
 * @description: 希尔排序
 * @param {TargetType*} 待记录数组
 * @param {int} n  数组长度
 * @param {int} *dalta   增量数组
 * @param {int} m  增量数组长度
 * @return {*}
 */
void InsertSortion::shellSort(TargetType *data, int n, int *dalta, int m)
{
    for (int i = 0; i < m - 1; i++)
    {
        shellInsert(data, n, dalta[i]);
    }
}

一般认为希尔排序的复杂度为\(O(n^{1.5})\)。且希尔排序不是稳定的排序算法。

posted @ 2022-06-05 22:52  帝皇の惊  阅读(71)  评论(0)    收藏  举报