掌握经典排序算法(类型二)由数值找排名

1. 前言

本篇内容主要讲第二种排序类型,即由数值找排名排序法。

2. 相关排序算法

相关排序算法目前只有一种,即插入排序算法。

2.1 插入排序

2.1.1 算法模型

插入排序的算法模型和上一篇中的最值法相似,同样分无序区和有序区。
掌握经典排序算法(类型二)由数值找排名_第1张图片

2.1.2 计算过程

插入排序计算过程如下:

  1. 将待排序数据视为无序区,有序区默认为空;
  2. 从无序区取出任意一个数,和有序区内容逐个比较,按顺序插入到有序区数组中。重复执行,直到无序区为空,算法结束。
2.1.3 编码实现

项目地址:
https://gitee.com/pivotfuture/SortAlgorithm/tree/master/src/insertSort

核心代码如下:

/**
 * @brief insertSort_putResultInOriginArray
 *  插入排序,结果存储在原数组中。排序后的数据会覆盖原数组中数据。
 * @param unordered_area 无序区数据指针
 * @param data_count 需要排序的数据个数
 */
void insertSort_putResultInOriginArray(int unordered_area[], size_t data_count)
{
    if (data_count < 0)
        return;

    // 总体逻辑:循环取无序区数据,插入有序区。
    // 无序区在左边(低地址),有序区在右边(高地址)
    // 第一个待排序数据不需要进行插入排序,直接进入有序区。
    for (int i = 1; i < data_count; i++)
    {
        // 无序区数据索引,因为有序区从右向左生长,所以无序区需要从右向左取数。
        int unordered_area_data_index = data_count - 1 - i;

        // 取出无序区右端第一个未排序数据
        int first_unordered_data = unordered_area[unordered_area_data_index];

        // 有序区起始地址(初始时有序区为空)
        int *ordered_area_left = &unordered_area[unordered_area_data_index + 1];
        int *first_bigger_ordered_data = NULL;

        // 当前有序区数据个数
        int ordered_area_data_count = i;

        // 下面遍历有序区,通过比较找到合适位置插入
        // 我们要实现从左到右按从小到大排序,
        // 只要将当前未排序数据,插入到第一个比当前未排序数据大的数的左边即可。
        // 如果没有比当前未排序数据大的有序数,则将当前未排序数插入到有序区最右侧。
        bool find_bigger_one = false;
        for (int j = 0; j < ordered_area_data_count; j++)
        {
            if (ordered_area_left[j] > first_unordered_data)
            {
                first_bigger_ordered_data = &ordered_area_left[j];
                find_bigger_one = true;
                break;
            }
        }

        if (find_bigger_one)
        {
            // 把第一个更大数左边的所有已排序数左移一位
            for (int *ptr = ordered_area_left; ptr < first_bigger_ordered_data; ptr++)
            {
                *(ptr - 1) = *ptr;
            }

            // 插入当前未排序数到有序区
            *(first_bigger_ordered_data - 1) = first_unordered_data;
        }
        else
        {
            // 将有序区全部左移一位
            for (int k = 0; k < ordered_area_data_count; k++)
            {
                ordered_area_left[k - 1] = ordered_area_left[k];
            }

            // 插入当前未排序数到有序区右端第一个位置
            ordered_area_left[ordered_area_data_count - 1] = first_unordered_data;
        }
    }

    // 此时无序区为空,打印有序区
    qDebug("插入排序后的数据为:");
    for (int j = 0; j < data_count; j++)
    {
        printf("%d ", unordered_area[j]);
    }
    printf("\n");
}

为了说明算法细节,上面的代码稍微有些冗杂,不够精简。

网络上有一段精简的实现代码,这里贴出,并加上了注释:

/**
* @brief 实现插入排序
*  总体逻辑:有序区在左边(低地址),无序区在右边(高地址)
*/
void insertSort(int array[], int count)
{
    // 外面大循环用于遍历无序区数据,第一个无序区数据可以直接进入有序区,因为此时有序区为空
    // 所以这里索引从 1 开始,而不是从 0 开始。
    for (int i = 1; i < count; i++)
    {
        int prevIndex = i - 1;
        
        // 取出当前待排序值,有序区右端增加一个空位,无序区左端让出一个位置。
        // 在排序完成时,会将当前值填入空位。
        int currentValue = array[i]; 
        
        // 在有序区中从右向左比较,比当前值大的数,右移一个位置。
        // 相反,空位会向左移动。
        while (prevIndex >= 0 && array[prevIndex] > currentValue)
        {
            array[prevIndex + 1] = array[prevIndex];
            prevIndex--;
        }
        
        // 至此,比当前值大的数均在空位右边,比当前值小的会在空位左边。
        // 把当前值放入当前有序区空位中,当前数插入排序完成,继续处理下一个
        // 待排序数
        array[prevIndex + 1] = currentValue;
    }
}

这段代码比较精简,可能需要动手验算帮助理解。虽然写法有所不同,但其背后的本质原理是不变的,仍然是从无序区取数据插入到有序区中。

2.1.4 算法特点

插入排序需要对数据频繁插入,使用数组效率较低,所以实现时,为了提高效率可以使用链表等数据结构。算法在最终结束时,才能知道待排序数据的最大值或最小值。

2.1.5 时间复杂度

取第1个数,插入最多需要遍历0次有序区;取第2个数,插入最多需要遍历1次有序区;依此类推:
总遍历次数为: 0 + 1 + 2 + . . . + n − 1 = ( n − 1 ) n 2 0+1+2+...+n-1=\frac{(n-1)n}{2} 0+1+2+...+n1=2(n1)n
所以时间复杂度的数量级为 n 2 n^2 n2,即时间复杂度为 O ( n 2 ) O(n^2) O(n2)

3. 结语

本篇文章只包含一个算法,相对来说比较简单,读者在了解算法思想的基础上,尽量自己动手编写调试一遍算法代码。算法并不是一成不变的,只要理解了算法的原理,就可以根据需要对算法进行合理优化和改进。

下一篇我们将研究整体局部排序法涉及的相关算法。


本文原创发布于Qt未来工程师,后续内容已在公众号中发布,敬请关注。

你可能感兴趣的:(通用算法,排序算法,算法,数据结构)