排序——交换类排序、插入类排序、选择类排序、归并类排序

news2024/11/13 23:51:40

排序

排序算法分为交换类排序、插入类排序、选择类排序、归并类排序。

交换类排序

冒泡排序

冒泡排序的基本思想是:从后往前(或从前往后)两两比较相邻元素的值。若A[ j - 1 ] > A[ j ],则交换它们,直到序列比较完。我们称它为第一趟冒泡,结果是将最小的元素交换到待排序列的第一个位置。关键字最小的元素如气泡一般逐渐往上“漂浮”直至“水面”。下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素放到了序列的最终位置…这样最多做 n − 1 n - 1 n1趟冒泡就能把所有元素排好序。

可以通过点击此处进入旧金山大学提供的网站来演示冒泡排序动画效果。

代码实战:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int ElemType;

typedef struct {
    // 存储元素的起始地址
    ElemType *elem;
    // 元素个数
    int table_len;
} SSTable;


/*
 * 顺序表初始化
 */
void st_init(SSTable &ST, int len) {
    ST.table_len = len;
    ST.elem = (ElemType *) malloc(sizeof(ElemType) * ST.table_len);

    // 用于生成随机数的骰子
    srand(time(NULL));

    for (int i = 0; i < ST.table_len; i++) {
        // 生产的数是0-99之间
        ST.elem[i] = rand() % 100;
    }
}


/*
 * 顺序表打印
 */
void st_print(SSTable ST) {
    for (int i = 0; i < ST.table_len; i++) {
        printf("%3d", ST.elem[i]);
    }
    printf("\n");
}


/*
 * 交换两个元素
 */
void swap(ElemType &a, ElemType &b) {
    ElemType temp;

    temp = a;
    a = b;
    b = temp;
}


/*
 * 冒泡排序
 *
 * 排序往往都是用两层循环:1.内层循环控制比较/交换 2.外层循环控制
 */
void bubble_sort(ElemType *A, int len) {
    // 哨兵: 是否发生交换
    bool flag;

    // 外层循环控制
    for (int i = 0; i < len - 1; i++) {
        flag = false;

        // 内层控制比较/交换
        // j = len - 1 是从最后一个元素从后往前比较/交换
        for (int j = len - 1; j > i; j--) {
            if (A[j - 1] > A[j]) {
                swap(A[j - 1], A[j]);
                flag = true;
            }
        }

        // 本趟排序未发生交换说明整个顺序表已经有序
        if (false == flag) {
            return;
        }
    }
}


/*
 * 冒泡排序
 */
int main() {
    SSTable ST;
    // 一、顺序表初始化
    st_init(ST, 10);

    // 固定数组用于调试
    ElemType A[10] = {64, 94, 95, 79, 69, 84, 18, 22, 12, 78};
    // memcpy可用于拷贝整型数组、浮点型数组 strcpy只能拷贝字符型数组
    memcpy(ST.elem, A, sizeof(A));
    // 打印顺序表
    st_print(ST);

    // 二、冒泡排序
    bubble_sort(ST.elem, ST.table_len);
    st_print(ST);

    return 0;
}

时间复杂度:时间复杂度其实就是程序实际的运行次数,可以看到内层是 j > i j > i j>i,外层 i i i的值是从 0 0 0 n − 1 n - 1 n1,所以程序的总运行次数是 1 + 2 + 3 + . . . + ( n − 1 ) 1 + 2 + 3 + ... + (n - 1) 1+2+3+...+(n1),这是等差数列求和,得到是结果是 n ( n − 1 ) / 2 n(n - 1)/2 n(n1)/2,忽略低阶项和高阶项的首项系数,因此时间复杂度是 O ( n 2 ) O(n^2) O(n2)

空间复杂度:因为未使用额外的空间(额外空间必须与输入元素的个数N相关),所以空间复杂度是 O ( 1 ) O(1) O(1)

快速排序

快速排序的核心是分治思想:假设我们的目标依然是按从小到大的顺序排列,我们找到数组中的一个分割值,把比分割值小的数都放在数组的左边,把比分割值大的数都放在数组的右边,这样分割值的位置就被确定。

数组一分为二,我们只需排前一半数组和后一半数组,复杂度直接减半。采用这种思想,不断地进行递归,最终分割得只剩一个元素时,整个序列自然就是有序的。

可以通过点击此处进入旧金山大学提供的网站来演示快速排序动画效果。

代码实战:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int ElemType;

typedef struct {
    // 存储元素的起始地址
    ElemType *elem;
    // 元素个数
    int table_len;
} SSTable;


/*
 * 顺序表初始化
 */
void st_init(SSTable &ST, int len) {
    ST.table_len = len;
    ST.elem = (ElemType *) malloc(sizeof(ElemType) * ST.table_len);

    // 用于生成随机数的骰子
    srand(time(NULL));

    for (int i = 0; i < ST.table_len; i++) {
        // 生产的数是0-99之间
        ST.elem[i] = rand() % 100;
    }
}


/*
 * 顺序表打印
 */
void st_print(SSTable ST) {
    for (int i = 0; i < ST.table_len; i++) {
        printf("%3d", ST.elem[i]);
    }
    printf("\n");
}


/*
 * 分割函数 挖坑法
 */
int partition(ElemType *A, int low, int high) {
    // 拿最左边的元素作为分割值 并存储下来
    ElemType pivot = A[low];

    while (low < high) {
        // 找到一个比分割值小的元素
        while (low < high && A[high] >= pivot) {
            high--;
        }
        A[low] = A[high];

        // 找到一个比分割值大的元素
        while (low < high && A[low] <= pivot) {
            low++;
        }
        A[high] = A[low];
    }

    // 分割值放到中间位置
    // 左边的都比分割值小 右边都比分割值大
    A[low] = pivot;

    return low;
}


/*
 * 快速排序
 */
void quick_sort(ElemType *A, int low, int high) {
    if (low < high) {
        // 存储分割值的位置
        int pivoit_pos = partition(A, low, high);
        quick_sort(A, low, pivoit_pos - 1);
        quick_sort(A, pivoit_pos + 1, high);
    }
}


int main() {
    SSTable ST;
    // 一、顺序表初始化
    st_init(ST, 10);

    // 固定数组用于调试
    ElemType A[10] = {64, 94, 95, 79, 69, 84, 18, 22, 12, 78};
    // memcpy可用于拷贝整型数组、浮点型数组 strcpy只能拷贝字符型数组
    memcpy(ST.elem, A, sizeof(A));
    // 打印顺序表
    st_print(ST);

    // 二、快速排序
    quick_sort(ST.elem, 0, ST.table_len - 1);
    st_print(ST);

    return 0;
}

时间复杂度:假如每次快速排序数组都被平均地一分为二,那么可以得出quick_sort递归的次数是 l o g 2 n log_2 n log2n,第一次partition遍历次数为 n n n,分成两个数组后,每个数组遍历 n / 2 n/2 n/2次,加起来还是 n n n,因此时间复杂度是 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n),因计算机是二进制的,所以在复试面试回答复杂度或与人交流时,提到复杂度时一般直接讲 O ( n l o g n ) O(nlog n) O(nlogn),而不带下标 2 2 2

快速排序最差的时间复杂度为什么是呢?

因为数组本身从小到大有序时,如果每次我们仍然用最左边的数作为分割值,那么每次数组都不会二分,导致递归 n n n次,所以快速排序最坏时间复杂度为 n n n的平方。当然,为了避免这种情况,有时会首先随机选择一个下标,先将对应下标的值与最左边的元素交换,再进行partition操作,从而极大地降低出现最坏时间复杂度的概率,但是仍然不能完全避免。

因此快排最好和平均时间复杂度是 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n),最差是 O ( n 2 ) O(n^2) O(n2).

空间复杂度: O ( l o g 2 n ) O(log_2 n) O(log2n),因递归的次数是 l o g 2 n log_2 n log2n,而每次递归的形参都是需要占用空间的。

插入类排序

插入类排序有三种:直接插入排序、折半插入排序、希尔排序。

直接插入排序

如果一个序列只有一个数,那么该序列自然是有序的。插人排序首先将第一个数视为有序序列,然后把后面9个数视为要依次插入的序列。首先,我们通过外层循环控制要插人的数,用 insertVal保存要插入的值87,我们比较 arr[0]是否大于 arr[1],即3是否大于87,由于不大于,因此不发生移动,这时有序序列是3,87.接着,将数值2插入有序序列,首先将2赋给 insertVal,这时判断87 是否大于2,因为87大于2,所以将87向后移动,将2覆盖然后判断3是否大于2,因为3大于2,所以3移动到87所在的位置,内层循环结束,这时将2赋给arr[0]的位置,得到下表中第2次插人后的效果。

有序序列要插入的数的序列
最初387 2 93 78 56 61 38 12 40
第1次插入3 872 93 78 56 61 38 12 40
第2次插入2 3 8793 78 56 61 38 12 40

继续循环会将数依次插人有序序列,最终使得整个数组有序。插入排序主要用在部分数有序的场景,例如手机通讯录时时刻刻都是有序的,新增一个电话号码时,以插入掛序的方法将其插入原有的有序序列,这样就降低了复杂度。

可以通过点击此处进入旧金山大学提供的网站来演示插入排序动画效果。

代码实战:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int ElemType;

typedef struct {
    // 存储元素的起始地址
    ElemType *elem;
    // 元素个数
    int table_len;
} SSTable;


/*
 * 顺序表初始化
 */
void st_init(SSTable &ST, int len) {
    ST.table_len = len;
    ST.elem = (ElemType *) malloc(sizeof(ElemType) * ST.table_len);

    // 用于生成随机数的骰子
    srand(time(NULL));

    for (int i = 0; i < ST.table_len; i++) {
        // 生产的数是0-99之间
        ST.elem[i] = rand() % 100;
    }
}


/*
 * 顺序表打印
 */
void st_print(SSTable ST) {
    for (int i = 0; i < ST.table_len; i++) {
        printf("%3d", ST.elem[i]);
    }
    printf("\n");
}


/*
 * 插入排序
 */
void insert_sort(ElemType *A, int len) {
    int i, j, insert_val;

    // 外层控制要插入的数 从第A[1]个开始
    for (i = 1; i < len; i++) {
        // 待插入的值
        insert_val = A[i];

        // 内层控制 j要大于等于0 同时A[j]大于insert_val时 A[j]位置元素往后覆盖
        for (j = i - 1; j >= 0 && A[j] > insert_val; j--) {
            A[j + 1] = A[j];
        }
        A[j + 1] = insert_val;
    }
}


int main() {
    SSTable ST;
    // 一、顺序表初始化
    st_init(ST, 10);

    // 固定数组用于调试
    ElemType A[10] = {64, 94, 95, 79, 69, 84, 18, 22, 12, 78};
    // memcpy可用于拷贝整型数组、浮点型数组 strcpy只能拷贝字符型数组
    memcpy(ST.elem, A, sizeof(A));
    // 打印顺序表
    st_print(ST);

    // 二、插入排序
    insert_sort(ST.elem, ST.table_len);
    st_print(ST);

    return 0;
}

时间复杂度:随着有序序列的不断增加,插入排序比较的次数也会增加,插入排序的执行次数也是从 1 1 1加到 n − 1 n-1 n1,总运行次数为 n ( n − 1 ) / 2 n(n - 1)/2 n(n1)/2,时间复杂度依然为 O ( n 2 ) O(n^2) O(n2).

如果数组本身有序,那么就是最好的时间复杂度 O ( n ) O(n) O(n)。当数组有序,我们的内层循环每次都是无法进入的,因此,最好的时间复杂度就是 O ( n ) O(n) O(n)

空间复杂度:因为未使用额外的空间(额外空间必须与输入元素的个数 N相关),所以空间复杂为 O ( 1 ) O(1) O(1)

选择类排序

选择类排序有两种:简单选择排序、堆排序

简单选择排序

简单选择排序原理:假设排序表为L[1⋯n],第i趟排序即从L[i⋯n]中选择关键字最小的元素与L(i)交换,每趟排序可以确定一个元素的最终位置,这样经过n-1 趟排序就可使得整个排序表有序。

首先假定第零个元素是最小的,把下标0赋值给 min(min 记录最小的元素的下标),内层比较时,从1号元素一直比较到9号元素,谁更小,就把它的下标赋给 min,一轮比较结束后,将min 对应位置的元素与元素 i 交换,如下表所示。第一轮确认 2最小,将2与数组开头的元素3交换。第二轮我们最初认为87 最小,经过轮比较,发现3最小,这时将87与3交换。持续进行,最终使数组有序。

最初3 87 2 93 78 56 61 38 12 40
第1轮比较后标记3 87 2 93 78 56 61 38 12 40
第1次交换2 87 3 93 78 56 61 38 12 40
第2轮比较后标记2 87 3 93 78 56 61 38 12 40
第2次交换2 3 87 93 78 56 61 38 12 40
第3轮比较后标记2 3 87 93 78 56 61 38 12 40
第3次交换2 3 12 93 78 56 61 38 87 40
⋯⋯

可以通过点击此处进入旧金山大学提供的网站来演示选择排序动画效果。

代码实战:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int ElemType;

typedef struct {
    // 存储元素的起始地址
    ElemType *elem;
    // 元素个数
    int table_len;
} SSTable;


/*
 * 顺序表初始化
 */
void st_init(SSTable &ST, int len) {
    ST.table_len = len;
    ST.elem = (ElemType *) malloc(sizeof(ElemType) * ST.table_len);

    // 用于生成随机数的骰子
    srand(time(NULL));

    for (int i = 0; i < ST.table_len; i++) {
        // 生产的数是0-99之间
        ST.elem[i] = rand() % 100;
    }
}


/*
 * 顺序表打印
 */
void st_print(SSTable ST) {
    for (int i = 0; i < ST.table_len; i++) {
        printf("%3d", ST.elem[i]);
    }
    printf("\n");
}


/*
 * 交换两个元素
 */
void swap(ElemType &a, ElemType &b) {
    ElemType temp;

    temp = a;
    a = b;
    b = temp;
}


/*
 * 选择排序
 */
void select_sort(ElemType *A, int len) {
    // min用于记录最小的元素的下标
    int i, j, min;

    // 如序列长度为10 下标最多遍历到8即可
    // 因为i=8时A[8]已经和A[9]比较过一次
    for (i = 0; i < len - 1; i++) {
        min = i;

        // 找到从i开始到最后的序列的最小值的下标
        for (j = i + 1; j < len; j++) {

            if (A[j] < A[min]) {
                // min记录最小值的下标
                min = j;
            }
        }

        // 最小元素为i时不用交换
        if (min != i) {
            swap(A[i], A[min]);
        }
    }
}


int main() {
    SSTable ST;
    // 一、顺序表初始化
    st_init(ST, 10);

    // 固定数组用于调试
    ElemType A[10] = {64, 94, 95, 79, 69, 84, 18, 22, 12, 78};
    // memcpy可用于拷贝整型数组、浮点型数组 strcpy只能拷贝字符型数组
    memcpy(ST.elem, A, sizeof(A));
    // 打印顺序表
    st_print(ST);

    // 二、选择排序
    select_sort(ST.elem, ST.table_len);
    st_print(ST);

    return 0;
}

时间复杂度:选择排序虽然减少了交换次数,但是循环比较的次数依然和冒泡排序的数量是一样的,都是从 1 1 1加到 n − 1 n-1 n1,总运行次数为 n ( n − 1 ) / 2 n(n-1)/2 n(n1)/2。我们忽略循环内语句的数量,因为我们在计算时间复杂度时,主要考虑与 n n n有关的循环,如果循环内交换得多,例如有5条语句,那么最终得到的无非是 5 n 2 5n^2 5n2;循环内交换得少,例如有2条语句,那么得到的就是 2 n 2 2n^2 2n2,但是时间复杂度计算是忽略首项系数的,因此最终还是 O ( n 2 ) O(n^2) O(n2)。因此,选择排序的时间复杂度依然为 O ( n 2 ) O(n^2) O(n2)

空间复杂度:因为未使用额外的空间(额外空间必须与输入元素的个数 N相关),所以空间复杂为 O ( 1 ) O(1) O(1)

堆排序

堆(Heap)是计算机科学中的一种特殊的树状数据结构。

若满足以下特性,则可称为堆:“给定堆中任意结点P和C,若P是C的父结点,则P的值小于等于(或大于等于)C的值。”若父结点的值恒小于等于子结点的值,则该堆称为最小堆(min heap);反之,若父结点的值恒大于等于子结点的值,则该堆称为最大堆(max heap)。堆中最顶端的那个结点称根结点 (root node),根结点本身没有父结点(parent node)•平时在工作中,我们将最小堆称小根堆或小顶堆,把最大堆称大根堆或大顶堆。

假设我们有3,87,2,93,78, 56,61, 38, 12,40共10个元素,我们将这10个元素建成一棵完全二叉树,这里采用层次建树法,虽然只用一个数组存储元素,但是我们能将二叉树中任意一个位置的元素对应到数组下标上,我们将二叉树中每个元素对应到数组下标的这种数据结构称为堆,比如最后一个父元素的下标是N/2-1,也就是a[4],对应的值为78。

为什么是N/2-1?因这是层次建立一棵完全二叉树的特性。可以这样记忆:如果父结点的下标是dad,那么父结点对应的左子结点的下标值是2*dad+1。接着,依次将每棵子树都调整为父结点最大,最终将整棵树变为一个大根堆。

可以通过点击此处进入旧金山大学提供的网站来演示堆排序动画效果。

1数组元素情况3 87 2 93 78 56 61 38 12 40
数组元素对应的二叉树
2数组元素情况3 87 2 93 78 56 61 38 12 40
数组元素对应的二叉树,arr[4] 与 arr[9] 进行比较
3数组元素情况3 87 2 93 78 56 61 38 12 40
数组元素对应的二叉树,arr[3] 与 arr[7] 进行比较
4数组元素情况3 87 61 93 78 56 2 38 12 40
数组元素对应的二叉树,arr[2] 与 arr[6] 进行比较,发生交换
5数组元素情况3 93 61 87 78 56 2 38 12 40
数组元素对应的二叉树,arr[1] 与 arr[3] 进行比较,发生交换,发生交换后arr[3]重新作为父结点,与孩子结点进行比较
6数组元素情况93 87 61 38 78 56 2 3 12 40
数组元素对应的二叉树,arr[0] 与 arr[1] 进行比较,发生交换,发生交换后arr[1]重新作为父结点,与孩子结点进行比较

代码实战:

首先我们通过随机数生成10个元素,通过随机数生成,我们可以多次测试排序算法是否正确,然后打印随机生成后的元素顺序,然后通过堆排序对元素进行排序,然后再次打印排序后的元素顺序。

堆排序的步骤是首先把堆调整为大根堆,然后我们交换根部元素也就是A[0],和最后一个元素,这样最大的元素就放到了数组最后,接着我们将剩余9个元素继续调整大根堆,然后交换A[0]和9个元素的最后一个,循环往复,直到有序。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int ElemType;

typedef struct {
    // 存储元素的起始地址
    ElemType *elem;
    // 元素个数
    int table_len;
} SSTable;


/*
 * 顺序表初始化
 */
void st_init(SSTable &ST, int len) {
    ST.table_len = len;
    ST.elem = (ElemType *) malloc(sizeof(ElemType) * ST.table_len);

    // 用于生成随机数的骰子
    srand(time(NULL));

    for (int i = 0; i < ST.table_len; i++) {
        // 生产的数是0-99之间
        ST.elem[i] = rand() % 100;
    }
}


/*
 * 顺序表打印
 */
void st_print(SSTable ST) {
    for (int i = 0; i < ST.table_len; i++) {
        printf("%3d", ST.elem[i]);
    }
    printf("\n");
}


/*
 * 交换两个元素
 */
void swap(ElemType &a, ElemType &b) {
    ElemType temp;

    temp = a;
    a = b;
    b = temp;
}


/*
 * 调整为大根堆
 */
void adjust_down(ElemType *A, int k, int len) {
    // 父亲结点的下标
    int dad = k;
    // 左孩子的下标
    int son = 2 * dad + 1;

    while (son < len) {
        // 左孩子和右孩子比较
        if (son + 1 < len && A[son] < A[son + 1]) {
            // 如果右孩子大则拿右孩子跟父亲比较
            son++;
        }

        // 如果孩子比父亲大
        if (A[son] > A[dad]) {
            // 孩子和父亲进行值交换
            swap(A[son], A[dad]);

            // 孩子成为新的父亲 需要去判断下面的子树符合大根堆
            dad = son;
            son = 2 * dad + 1;
        } else {
            break;
        }
    }
}


/*
 * 堆排序
 */
void heap_sort(ElemType *A, int len) {
    int i;

    // 从最后一个父亲元素开始调整为大根堆
    for (i = len / 2 - 1; i >= 0; i--) {
        adjust_down(A, i, len);
    }
    swap(A[0], A[len - 1]);

    // i代表的时剩余无序数的数组长度
    for (i = len - 1; i > 1; i--) {

        // 调整剩余元素为大根堆
        adjust_down(A, 0, i);

        // A[0]和无序数数组的最后一个元素
        swap(A[0], A[i - 1]);
    }
}



int main() {
    SSTable ST;
    // 一、顺序表初始化
    st_init(ST, 10);

    // 固定数组用于调试
    ElemType A[10] = {3, 87, 2, 93, 78, 56, 61, 38, 12, 40};
    // memcpy可用于拷贝整型数组、浮点型数组 strcpy只能拷贝字符型数组
    memcpy(ST.elem, A, sizeof(A));
    // 打印顺序表
    st_print(ST);

    // 二、堆排序
    heap_sort(ST.elem, ST.table_len);
    st_print(ST);

    return 0;
}

时间复杂度:adjust_down函数的循环次数时 l o g 2 n log_2 n log2n,heap_sort函数的第一个for循环了 n / 2 n / 2 n/2次,第二个for循环了 n n n次,总计次数是 3 n l o g 2 n / 2 3nlog_2 n/2 3nlog2n/2,因此时间复杂度时 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n)。堆排序最好、最坏、平均时间复杂度都是 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n)

空间复杂度:因为没有使用额外的跟 n n n相关的空间,所以空间复杂度时 O ( 1 ) O(1) O(1)

归并类排序

在这里插入图片描述

如上图所示,我们把每两个元素归为一组,进行小组内排序,然后再次把两个有序小组合并为一个有序小组,不断进行,最终合并一个有序数组。

可以通过点击此处进入旧金山大学提供的网站来演示归并排序动画效果。

代码实战:归并排序的代码是采用递归思想实现的,掌握递归实现即可。首先,最小下标值和最大下标值相加并除以 2,得到中间下标值 mid,用 MergeSor t对 low 到 mid 排序,然后用 MergeSort 对 mid+1 到high 排序。当数组的前半部分和后半部分都排好序后,使用 Merge 函数。Merge 函数的作用是合并两个有序数组。为了提高合并有序数组的效率,在Merge 函数内定义了 B[N]. 首先,我们通过循环把数组 A 中从 low 到 high 的元素全部复制到 B 中,这时游标 i (遍历的变量称为游标)从low 开始,游标 j 从 mid+1开始,谁小就将谁先放入数组A,对其游标加1,并在每轮循环时对数组 A的计数游标 k 加1。

#include <stdio.h>
#include <stdlib.h>


#define N 7
typedef int ELemType;


/*
 * 合并两个有序数组
 */
void merge(ELemType A[], int low, int mid, int high) {
    // 家static的目的是无论递归调用多少次 都只有一个B[N]
    static ELemType B[N];

    int i, j, k;

    // 把A[i]所有的元素都给B[i]
    for (i = low; i <= high; i++) {
        B[i] = A[i];
    }

    // 合并两个有序数组
    for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
        if (B[i] < B[j]) {
            A[k] = B[i];
            i++;
        } else {
            A[k] = B[j];
            j++;
        }
    }

    // 把某一个数组中剩余的元素放到数组A
    while (i <= mid) {
        // 前一半有剩余元素放入
        A[k] = B[i];
        i++;
        k++;
    }
    while (j <= high) {
        // 后一半有剩余元素放入
        A[k] = B[j];
        j++;
        k++;
    }
}


/*
 * 归并排序
 */
void merge_sort(ELemType A[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;

        merge_sort(A, low, mid);
        merge_sort(A, mid + 1, high);
        merge(A, low, mid, high);
    }
}


/*
 * 打印
 */
void print(ELemType A[]) {
    for (int i = 0; i < N; i++) {
        printf("%3d", A[i]);
    }
}


int main() {
    int A[N] = {49, 38, 65, 97, 76, 13, 27};
    merge_sort(A, 0, 6);
    print(A);
    return 0;
}

时间复杂度:MergeSort 函数的递归次数是 l o g 2 n log_2 n log2n,Merge 函数的循环了 n n n次,因此时间复杂度是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

归并排序最好、最坏、平均时间复杂度都是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

空间复杂度: O ( n ) O(n) O(n),因为使用了数组B,它的大小与A一样占用n个元素的空间。

所有排序算法时间与空间复杂度汇总

各种排序算法的时间复杂度、空间复杂度、稳定性和复杂性

排序方式时间复杂度空间复杂度稳定性复杂性
平均情况最坏情况最好情况
插人排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定简单
希尔排序 O ( n ( 1.3 ) ) O(n^(1.3)) O(n(1.3)) O ( 1 ) O(1) O(1)不稳定较复杂
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定简单
快速排序 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( l o g 2 n ) O(log_2 n) O(log2n)不稳定较复杂
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定简单
堆排序 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定较复杂
归并排序 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n ) O(n) O(n)稳定较复杂
基数排序 O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) O ( r ) O(r) O(r)稳定较复杂

稳定性是指排序前后,相等的元素位置是否会被交换。

复杂性是指代码编写的难度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1901542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试与开发流程

软件测试简介 软件测试是对软件进行检测和评估&#xff0c;以确定其是否满足所需结果的过程和方法。它是在规定的条件下对程序进行操作&#xff0c;发现程序错误&#xff0c;从而衡量软件质量&#xff0c;并对其是否满足设计要求进行评估的过程。 与计算机系统操作有关的计算机…

昇思25天学习打卡营第2天|MindSpore快速入门

打卡 目录 打卡 快速入门案例&#xff1a;minist图像数据识别任务 案例任务说明 流程 1 加载并处理数据集 2 模型网络构建与定义 3 模型约束定义 4 模型训练 5 模型保存 6 模型推理 相关参考文档入门理解 MindSpore数据处理引擎 模型网络参数初始化 模型优化器 …

优化路由,优化请求url

1、使用父子关系调整下使其更加整洁 2、比如说我修改了下url,那所有的页面都要更改 优化&#xff1a;把这个url抽出来&#xff0c;新建一个Api文件夹用于存放所有接口的url&#xff0c;在业务里只需要关注业务就可以 使用时 导包 发请求 如果想要更改路径&#xff0c;在这里…

ReAct Agent 分享回顾

在人工智能的迅速发展中&#xff0c;ReAct Agent作为一项前沿技术&#xff0c;受到越来越多的关注。本文结合ReAct Agent 提出者的访谈内容&#xff0c;探讨ReAct Agent的研究背景、技术挑战、未来展望&#xff0c;以及它与大模型的紧密联系&#xff0c;分析其科研成果与商业化…

迅捷PDF编辑器合并PDF

迅捷PDF编辑器是一款专业的PDF编辑软件&#xff0c;不仅支持任意添加文本&#xff0c;而且可以任意编辑PDF原有内容&#xff0c;软件上方的工具栏中还有丰富的PDF标注、编辑功能&#xff0c;包括高亮、删除线、下划线这些基础的&#xff0c;还有规则或不规则框选、箭头、便利贴…

使用Docker、Docker-compose部署单机版达梦数据库(DM8)

安装前准备 Linux Centos7安装&#xff1a;https://blog.csdn.net/andyLyysh/article/details/127248551?spm1001.2014.3001.5502 Docker、Docker-compose安装&#xff1a;https://blog.csdn.net/andyLyysh/article/details/126738190?spm1001.2014.3001.5502 下载DM8镜像 …

动态颤抖的眼睛效果404页面源码

动态颤抖的眼睛效果404页面源码&#xff0c; 源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 动态颤抖的眼睛效果404页面源码

【密码学】密码学五要素

密码学五要素是密码系统的基本组成部分&#xff0c;这五个要素共同构成了密码系统的框架。在实际应用中&#xff0c;密码系统的安全性依赖于密钥的安全管理以及算法的强度。 如果任何一方被泄露或破解&#xff0c;那么整个密码系统都将面临风险。因此&#xff0c;在设计和使用密…

关于多人开发下git pull报错代码冲突问题的解决方案

关于多人开发下git pull报错代码冲突问题的解决方案 问题描述 最近多人开发项目习惯性先 git pull 来更新代码的时候&#xff0c;遇到了下面的问题&#xff1a;error: Your local changes to the following files would be overwritten by merge: Please, commit your change…

医疗器械FDA | FDA如何对医疗器械网络安全认证进行审查?

FDA医械网络安全文件出具​https://link.zhihu.com/?targethttps%3A//www.wanyun.cn/Support%3Fshare%3D24315_ea8a0e47-b38d-4cd6-8ed1-9e7711a8ad5e FDA对医疗器械的网络安全认证进行审查时&#xff0c;主要关注以下几个方面&#xff0c;以确保医疗器械在网络环境中的安全性…

vulhub靶场之DEVGURU:1

1 信息收集 1.1 主机发现 arp-scan -l 发现主机IP地址为“192.168.1.11 1.2 端口发现 nmap -sS -sV -A -T5 -p- 192.168.1.11 发现端口为&#xff1a;22&#xff0c;80&#xff0c;8585 1.3 目录扫描 dirsearch -u 192.168.1.11 发现存在git泄露 2 文件和端口访问 2…

力扣5----最长回文子串

给你一个字符串 s&#xff0c;找到 s 中最长的回文子串 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba" 同样是符合题意的答案。示例 2&#xff1a; 输入&#xff1a;s "cbbd" 输出…

嵌入式通信协议全解析:SPI、I²C、UART详解(附带面试题)

目录 一、什么是通信 二、 通信的分类 同步通信&#xff08;Synchronous Communication&#xff09; 异步通信&#xff08;Asynchronous Communication&#xff09; 不同协议标准区分图&#xff1a; UART UART的特点&#xff1a; UART的通信过程&#xff1a; UART的配置…

Linux多进程和多线程(四)进程间通讯-定时器信号和子进程退出信号

多进程(四) 定时器信号alarm()函数示例alarm()函数的限制定时器信号的实现原理setitimer()函数setitimer()和alarm()函数的区别 setitimer() old_value参数的示例 对比alarm()区别总结&#xff1a; 子进程退出信号 示例: 多进程(四) 定时器信号 SIGALRM 信号是用来通知进程…

ctfshow web 36d 练手赛

不知所措.jpg 没啥用然后测试了网站可以使用php伪达到目的 ?filephp://filter/convert.base64-encode/resourcetest/../index.<?php error_reporting(0); $file$_GET[file]; $file$file.php; echo $file."<br />"; if(preg_match(/test/is,$file)){inclu…

统一视频接入平台LntonCVS视频监控平台具体功能介绍

LntonCVS视频监控平台是一款基于H5技术开发的安防视频监控解决方案&#xff0c;专为全球范围内不同品牌、协议及设备类型的监控产品设计。该平台提供了统一接入管理&#xff0c;支持标准的H5播放接口&#xff0c;使其他应用平台能够快速集成视频功能。无论开发环境、操作系统或…

24-7-6-读书笔记(八)-《蒙田随笔集》[法]蒙田 [译]潘丽珍

文章目录 《蒙田随笔集》阅读笔记记录总结 《蒙田随笔集》 《蒙田随笔集》蒙田&#xff08;1533-1592&#xff09;&#xff0c;是个大神人&#xff0c;这本书就是250页的样子&#xff0c;但是却看了好长好长时间&#xff0c;体会还是挺深的&#xff0c;但看的也是不大仔细&…

《第一行代码》小结

文章目录 一. Android总览1. 系统架构2. 开发环境3. 在红米手机上运行4. 项目资源详解4.1 整体结构4.2 res文件4.3 build.gradle文件 二. Activity0. 常用方法小结1. 创建一个Activity 一. Android总览 1. 系统架构 应用层&#xff1a;所有安装在手机上的应用程序 应用框架层&…

vb.netcad二开自学笔记3:启动与销毁

Imports Autodesk.AutoCAD.ApplicationServicesImports Autodesk.AutoCAD.EditorInputImports Autodesk.AutoCAD.RuntimePublic Class WellcomCADImplements IExtensionApplicationPublic Sub Initialize() Implements IExtensionApplication.InitializeMsgBox("net程序已…

字节跳动与南开联合开源 StoryDiffusion:一键生成漫画和视频故事的神器!完全免费!

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 漫画&#xff0c;是多少人童年的回忆啊&#xff01; 记得小学…