排序之插入排序

概念:

插入排序是一种简单的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用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 使用常数大小的额外空间。
  • 稳定排序:在插入操作过程中,我们会将元素插入到相等元素的右侧,不会改变它们的顺序。
相关推荐
小码农<^_^>几秒前
优选算法精品课--滑动窗口算法(一)
算法
羊小猪~~2 分钟前
神经网络基础--什么是正向传播??什么是方向传播??
人工智能·pytorch·python·深度学习·神经网络·算法·机器学习
软工菜鸡28 分钟前
预训练语言模型BERT——PaddleNLP中的预训练模型
大数据·人工智能·深度学习·算法·语言模型·自然语言处理·bert
南宫生31 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
AI视觉网奇1 小时前
sklearn 安装使用笔记
人工智能·算法·sklearn
JingHongB1 小时前
代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础
算法·深度优先·图论
weixin_432702261 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
小冉在学习1 小时前
day52 图论章节刷题Part04(110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长 )
算法·深度优先·图论
Repeat7151 小时前
图论基础--孤岛系列
算法·深度优先·广度优先·图论基础
小冉在学习1 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论