排序之插入排序

概念:

插入排序是一种简单的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

算法步骤:

以下是插入排序算法的一般步骤:

  1. 将第一待排序的记录看作是有序序列,从第二个记录开始进行遍历。
  2. 假设当前记录的关键字是key,用它与有序序列中的记录从后往前依次进行比较。
  3. 如果key值小于当前记录的关键字,则将当前记录向右移动一位。
  4. 重复第3步直到key值大于或等于有序序列中的某个记录的关键字,或者已经到了有序序列的开头。
  5. 将key插入到找到的位置。
  6. 重复步骤2到5,直到所有记录都被扫描并插入到有序序列中。

++插入排序的时间复杂度是O(n^2),在数据规模较小或者部分数据已经有序的情况下效率较高。但是,对于大规模数据,插入排序的性能会下降。++
单次插入操作


通俗一点的:

插入排序是一种非常直观的排序方法,就像我们整理扑克牌一样。假设你手中有一副没有排序的扑克牌,你想要将它们从小到大排序。你可以这样操作:

  1. 选择基准:你先拿出第一张牌,这张牌就是你的"基准",它暂时是有序的。

  2. 逐个比较:然后你拿出第二张牌,将它与第一张牌比较。如果第二张牌的数字比第一张牌大,你就将它放在第一张牌的后面,这样你就有两张牌的有序序列了。

  3. 插入位置:如果第二张牌的数字比第一张牌小,你就继续拿出第三张牌,将它与第一张牌比较。如果第三张牌的数字也比第二张牌小,你就将它放在第一张牌的前面,然后继续比较第二张和第三张牌。如果第二张牌比第三张牌大,你就将第二张牌移动到第三张牌的后面。

  4. 重复操作:你继续拿出剩下的牌,重复上述步骤,直到所有的牌都被比较过并插入到正确的位置。

  5. 完成排序:当你手中的牌都按照这个方式比较并插入后,你就得到了一副从小到大排序的扑克牌。

将这个过程转换为代码,就是插入排序算法。

代码实现:

cpp 复制代码
#include <iostream>

using namespace std;

// 插入排序函数
void insertionSort(int arr[], int n) {
    int i, key, j;
    // 从第二个元素开始遍历数组
    for (i = 1; i < n; i++) {
        key = arr[i]; // 当前要插入的元素
        j = i - 1;

        // 将当前元素与已排序部分的元素从后向前比较
        while (j >= 0 && arr[j] > key) {
            // 如果已排序部分的元素大于key,则将该元素向后移动
            arr[j + 1] = arr[j];
            j = j - 1;
        }
        // 将key插入到正确的位置
        arr[j + 1] = key;
    }
}

效果展示:

cpp 复制代码
// 打印数组的函数
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << "\n"; // 换行
}

// 主函数
int main() {
    int arr[] = {9, 5, 1, 4, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    cout << "Original array: ";
    printArray(arr, n);

    // 调用插入排序函数
    insertionSort(arr, n);

    cout << "Sorted array: ";
    printArray(arr, n);

    return 0;
}
cpp 复制代码
Original array: 9 5 1 4 3
Sorted array: 1 3 4 5 9

D:\2024C语言\data-structure\insertion_sort\x64\Debug\insertion_sort.exe (进程 9304)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .

insertionSort函数会遍历数组中的每个元素,并将每个元素插入到前面已排序部分的正确位置。printArray函数用于打印数组,让我们可以看到排序前后的对比。main函数中定义了一个未排序的数组,并调用insertionSort函数对其进行排序。


优化:

以下是插入排序的几种优化方法:

  1. 二分插入排序:在插入元素前,使用二分查找法找到插入点,这样可以减少比较元素的次数,将比较操作的时间复杂度降低到O(log n)。但是,由于插入操作本身的时间复杂度仍然是O(n),所以整体的时间复杂度仍然是O(n^2)。

  2. 希尔排序:希尔排序是插入排序的一种改进版本,它通过引入增量序列来对数组进行分组,每组内的元素使用插入排序进行排序。随着增量逐渐减小,直到增量为1,此时整个数组使用插入排序进行排序。这种方法可以减少比较和移动的次数,提高效率。

  3. 优化数据移动:在找到插入点后,可以一次性将所有大于待插入元素的值向后移动一位,而不是逐个交换。这样可以减少数据的移动次数。

  4. 使用链表:在链表上实现插入排序可以避免数组元素移动的开销,因为链表的节点可以单独移动。在链表上,插入排序的时间复杂度可以接近O(n log n)。

  5. 使用循环而不是递归:在某些情况下,使用循环结构代替递归结构可以减少函数调用的开销。

下面是一个C++中实现的插入排序,包括了使用二分查找来优化查找插入点的过程:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm> // 引入algorithm库以使用std::swap

using namespace std;

// 二分查找找到插入点
int binarySearch(const vector<int>& arr, int key, int start, int end) {
    while (start < end) {
        int mid = start + (end - start) / 2;
        if (arr[mid] >= key) {
            end = mid;
        } else {
            start = mid + 1;
        }
    }
    return start;
}

// 插入排序函数
void insertionSort(vector<int>& arr) {
    for (int i = 1; i < arr.size(); ++i) {
        int key = arr[i];
        int j = binarySearch(arr, key, 0, i);

        // 将大于key的元素向后移动
        while (j < i) {
            arr[j + 1] = arr[j];
            ++j;
        }
        arr[j] = key; // 插入key到正确的位置
    }
}

// 打印数组的函数
void printArray(const vector<int>& arr) {
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;
}

// 主函数
int main() {
    vector<int> arr = {9, 5, 1, 4, 3};
    cout << "Original array: ";
    printArray(arr);

    insertionSort(arr);
    cout << "Sorted array: ";
    printArray(arr);

    return 0;
}

insertionSort函数使用二分查找来找到每个元素的插入点,然后一次性将所有大于该元素的值向后移动,以减少数据移动的次数。这种方法在查找插入点时提高了效率,但整体时间复杂度仍然是O(n^2),因为数据移动的时间复杂度仍然是O(n)


特性:

  • 时间复杂度为 O(n2)、自适应排序:在最差情况下,每次插入操作分别需要循环 n−1、n−2、...、2、1 次,求和得到 (n−1)n/2 ,因此时间复杂度为 O(n2) 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 O(n) 。
  • 空间复杂度为 O(1)、原地排序:指针 i 和 j 使用常数大小的额外空间。
  • 稳定排序:在插入操作过程中,我们会将元素插入到相等元素的右侧,不会改变它们的顺序。
相关推荐
莫叫石榴姐25 分钟前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
茶猫_1 小时前
力扣面试题 - 25 二进制数转字符串
c语言·算法·leetcode·职场和发展
Hera_Yc.H2 小时前
数据结构之一:复杂度
数据结构
肥猪猪爸3 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
linux_carlos3 小时前
环形缓冲区
数据结构
readmancynn4 小时前
二分基本实现
数据结构·算法
萝卜兽编程4 小时前
优先级队列
c++·算法
Bucai_不才4 小时前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
盼海4 小时前
排序算法(四)--快速排序
数据结构·算法·排序算法
一直学习永不止步4 小时前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表