数据结构 | 算法精讲:‘直接插入排序、冒泡排序、简单选择排序

注明:

为了方便讲解,我们事先定义一个结构体:

// 定义数组的最大大小
#define MAX_SIZE 100

// 定义结构体 Sqlist
typedef struct {
    int r[MAX_SIZE];  // 用于存储元素的数组
    int length;       // 数组的长度
} Sqlist;

一. 直接插入排序

我们先引入一个情景。我们在打扑克牌,我们手中有3、4、5、7四张牌,可是我们想要一张“6”来打出顺子。此时我们摸到了一张“6”,那我们就会把手中的“5”和“7”中间空出一个位置把“6”插进去,这便是插入类排序,而这其中最简单的便是“直接插入排序

1. 核心思想

将待排序的元素R_{1}插入到已经排好序的记录表{R_1,R_2,...,R_{i-1}}中,得到一个新的、记录数增加1的有序表,直到所有记录都插入完为止。

2. 过程示例

设有关键字序列{7,4,-2,19,13,6},为了方便理解,我们把序列用两个中括号隔开,前面的中括号包含的是已经排好序的序列,后面的中括号包含无序序列。排序过程如下所示:

初始:[7] [4 -2 19 13 6]

进行第一趟排序时,7是有序序列,4是无序序列的首元素,也是第一次排序的待排元素,我们用它和有序序列的末元素开始依次比较。

因为4比7小,所以7向后移动一个单位,前面没有元素了,4插入到7的前面,比较结束。

第一次排序后序列:[4 7] [-2 19 13 6]

第二趟排序,[4 7]是有序序列,-2是待排元素,将其依次和有序元素比较,-2比7小,所以7向后移动,继续比较;-2比4小,所以4往后移动;前面没有元素了,所以-2插入到4的前面,比较结束。

第二次排序后序列:[-2 4 7] [19 13 6]

......

以此类推,最后得到完整的有序序列。

3. 算法实现

void straightInsertSort(Sqlist &L) {
    int j, k;
    // 外层循环,从第二个元素开始遍历到最后一个元素
    // 外层循环从j = 2开始,是因为这是插入排序的一种常见约定。
    // L.r[0]元素被用作哨兵或当前待插入元素的临时存储。
    // 实际要排序的元素从索引1开始
    for (j = 2; j < L.length; j++) {
        // 将当前待插入元素存储到哨兵位置
        L.r[0] = L.r[j];
        // 内层循环,从当前元素的前一个位置开始比较并移动大于哨兵的元素
        for (k = j - 1; L.r[0] < L.r[k]; k--) {
            L.r[k + 1] = L.r[k];
        }
        // 将哨兵值插入到正确的位置
        L.r[k + 1] = L.r[0];
    }
}

4. 性能分析

时间复杂度:最坏情况下为O(n^2),此时待排序列为逆序

                      最好情况下为O(n),此时待排序列为顺序

空间复杂度:O(1)

5. 记忆方法

从二开始比,从前往后比。先存待插值,大数往后移。小数不用动,数值插后面。

二. 冒泡排序

1. 核心思想

依次比较相邻的两个记录的关键字,若两个记录是反序的(即前一个记录的关键字大于后前一个记录的关键字),则进行交换,直到没有反序的记录为止。

2. 过程示例

原始数组: [5, 2, 9, 1, 5]

现在,让我们用冒泡排序来完成这个任务。冒泡排序的基本思想是从数组的起始位置开始,两两比较相邻元素,如果它们的顺序不对,则交换它们。这样一轮下来,最大的元素就会被交换到数组的最后一个位置。

第一轮:

  • 比较 52,它们的顺序不对,交换之后数组变为 [2, 5, 9, 1, 5]
  • 比较 59,它们的顺序正确,不做交换
  • 比较 91,它们的顺序不对,交换之后数组变为 [2, 5, 1, 9, 5]
  • 比较 95,它们的顺序不对,交换之后数组变为 [2, 5, 1, 5, 9]

第一轮结束后,最大的数字 9 已经被冒泡到数组的最后。

第二轮:

  • 比较 25,它们的顺序正确,不做交换
  • 比较 51,它们的顺序不对,交换之后数组变为 [2, 1, 5, 5, 9]
  • 比较 55,它们的顺序正确,不做交换

第二轮结束后,次大的数字 5 已经被冒泡到数组的倒数第二个位置。

......

以此类推,最后得到完整的有序序列。

3.算法实现

void bubbleSort(Sqlist &L) {
    int j, k;
    // 外层循环,控制排序的趟数
    for (j = 1; j < L.length; j++) {
        // 内层循环,进行一趟排序
        for (k = 1; k < L.length - j; k++) {
            // 比较相邻两个元素,如果顺序不对则交换它们
            if (L.r[k] > L.r[k + 1]) {
                // 使用 L.r[0] 作为临时变量存储较大的元素
                L.r[0] = L.r[k];
                L.r[k] = L.r[k + 1];
                L.r[k + 1] = L.r[0];
            }
        }
    }
}

4. 性能分析

时间复杂度:最坏情况下为O(n^2)

                      最好情况下为O(n)

空间复杂度:O(1)

5. 记忆方法

从一开始往后看,后面大则前后换,后面小则看下个。

三. 简单选择排序

选择排序的定义:从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。

1. 核心思想

通过 n-i 次关键字间的比较,从 n-i+1 个记录中选取关键字最小的记录,然后和第 i 个记录进行交换,i=1, 2, … n-1 。

2. 过程示例

原始数组:7,4,−2,19,13,6

排序过程如下:

  1. 初始状态:7,4,−2,19,13,6

  2. 第一趟排序: 在未排序的部分中,找到最小的元素 -2,并与第一个元素交换位置。 −2,4,7,19,13,6

  3. 第二趟排序: 在未排序的部分中,找到最小的元素 4,并与第二个元素交换位置。 −2,4,7,19,13,6

  4. 第三趟排序: 在未排序的部分中,找到最小的元素 6,并与第三个元素交换位置。 −2,4,6,19,13,7

  5. 第四趟排序: 在未排序的部分中,找到最小的元素 7,并与第四个元素交换位置。 −2,4,6,7,13,19

  6. 第五趟排序: 在未排序的部分中,找到最小的元素 13,并与第五个元素交换位置。 −2,4,6,7,13,19

  7. 最终结果: 整个数组都有序了。 −2,4,6,7,13,19

在简单选择排序中,每一趟排序都会在未排序部分中找到最小的元素,并与未排序部分的第一个元素交换位置。这样,经过多趟排序,数组就逐渐变得有序。这是一种直观而简单的排序算法,但在实际应用中,对于大型数据集,效率相对较低。

3. 算法实现

void simpleSelectionSort(Sqlist &L) {
    int j, k, min, t;
    // 外层循环,控制排序的趟数
    for (j = 1; j < L.length; j++) {
        min = L.r[j]; // 假设当前未排序部分的第一个元素为最小值
        t = j; // 记录最小值的索引

        // 内层循环,从未排序部分的下一个位置开始,找到最小值的位置
        for (k = j + 1; k < L.length; k++) {
            // 如果找到比当前最小值更小的元素,则更新最小值和其索引
            if (min > L.r[k]) {
                min = L.r[k];
                t = k;
            }
        }
        // 将最小值与未排序部分的第一个元素交换位置
        L.r[0] = L.r[t];
        L.r[t] = L.r[j];
        L.r[j] = L.r[0];
    }
}

4. 性能分析

时间复杂度:最坏情况下为O(n^2)

                      最好情况下为O(n^2)

空间复杂度:O(1)

5. 记忆方法

查找最小值,换到最前面

你可能感兴趣的:(专业课复习,数据结构,排序算法,算法)