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

排序

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

交换类排序

冒泡排序

冒泡排序的基本思想是:从后往前(或从前往后)两两比较相邻元素的值。若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++;}}// 把某一个数组中剩余的元素放到数组Awhile (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.mzph.cn/pingmian/41559.shtml

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

相关文章

commonjs、module 模块同时启动

怎样同时在一个项目中同时启动node服务和我们前端项目&#xff08;commonjs、module 模块同时启动&#xff09; 今天在使用node实现完增删改查的接口之后&#xff0c;将自己node代码嵌入到我们react项目中 启动完前端项目之后&#xff0c;当我使用node service.js的时候&#x…

Unity 简单载具路线 Waypoint 导航

前言 在游戏开发和导航系统中&#xff0c;"waypoint" 是指路径中的一个特定位置或点。它通常用于定义一个物体或角色在场景中移动的目标位置或路径的一部分。通过一系列的 waypoints&#xff0c;可以指定复杂的移动路径和行为。以下是一些 waypoint 的具体用途&…

用Python轻松转换PDF为CSV

数据的可访问性和可操作性是数据管理的核心要素。PDF格式因其跨平台兼容性和版面固定性&#xff0c;在文档分享和打印方面表现出色&#xff0c;尤其适用于报表、调查结果等数据的存储。然而&#xff0c;PDF的非结构化特性限制了其在数据分析领域的应用。相比之下&#xff0c;CS…

【国产开源可视化引擎Meta2d.js】图元

图元 又称画笔Pen。图形表达的基本元素&#xff0c;组成图像的基本单元。 构成 每一个图元由ID、名字、类型、属性&#xff08;数据&#xff09;组成。 ID 名为“id”的特殊属性&#xff0c;图元实例&#xff08;画布上的图元对象&#xff09;的唯一标识。拖拽到画布或创建…

【线性代数的本质】矩阵与线性变换

线性变化要满足两点性质&#xff1a; 直线&#xff08;连续的点&#xff09;在变换后还是直线。原点不变。 假设有坐标轴&#xff08;基底&#xff09; i ^ \widehat{i} i 和 j ^ \widehat{j} j ​&#xff1a; i ^ [ 1 0 ] , j ^ [ 0 1 ] \widehat{i}\begin{bmatrix} 1 \…

《昇思25天学习打卡营第6天|网络构建》

文章目录 前言&#xff1a;今日所学&#xff1a;1. 定义模型类2. 模型层3. 模型参数 前言&#xff1a; 在第六节中我们学习了网络构建&#xff0c;了解了神经网络模型是由神经网络层和Tensor操作构成&#xff0c;我们使用的mindspore.nn中提供了常见的升级网络层的实现&#x…

在线图片转文字的软件,分享3种强大的软件!

在信息爆炸的时代&#xff0c;图片作为信息的重要载体之一&#xff0c;其内容往往蕴含着巨大的价值。然而&#xff0c;面对海量的图片信息&#xff0c;如何高效、准确地将其转化为文字&#xff0c;成为了许多人的迫切需求。今天&#xff0c;就为大家盘点几款功能强大的在线图片…

【python基础】—如何理解安装程序时要配置Widows和DOS操作系统中的path环境变量?

文章目录 前言一、环境变量是什么&#xff1f;二、为什么需要设置环境变量&#xff1f;三、配置anaconda的环境变量 前言 在安装一些程序的时候&#xff0c; 我们总是需要将安装路径配置到正在使用电脑的环境变量里。为什么要进行这一步呢&#xff1f;本文主要解释Widows和DOS…

特殊用途二极管+二极管故障检测+三极管(BJT)的工作原理+定时器的使用(小灯定时闪烁实现)

2024-7-5&#xff0c;星期五&#xff0c;17:27&#xff0c;天气&#xff1a;晴&#xff0c;心情&#xff1a;晴。今天没有什么特殊的事情发生&#xff0c;继续学习啦&#xff0c;加油加油&#xff01;&#xff01;&#xff01; 今日完成模电自选教材第二章内容的学习&#xff…

1-4 NLP发展历史与我的工作感悟

1-4 NLP发展历史与我的工作感悟 主目录点这里 第一个重要节点&#xff1a;word2vec词嵌入 能够将无限的词句表示为有限的词向量空间&#xff0c;而且运算比较快&#xff0c;使得文本与文本间的运算有了可能。 第二个重要节点&#xff1a;Transformer和bert 为预训练语言模型发…

【ABB】原点设定

【ABB】原点设定 操作流程演示 操作流程 操作轴回原点编辑电机校准偏移更新转速计数器 1.首先得了解机器手的轴&#xff0c;这里以6轴作参考。 注意先回456轴&#xff0c;后回123轴。 2.然后需要了解机器人关节运动模式&#xff0c;即选择如下两个模式。 3.注意机器人各轴移动…

QT的编译过程(底层逻辑)

qmake -project 用于从源代码生成项目文件&#xff0c;qmake 用于从项目文件生成 Makefile&#xff0c;而 make 用于根据 Makefile 构建项目。 详细解释&#xff1a; qmake -project 这个命令用于从源代码目录生成一个初始的 Qt 项目文件&#xff08;.pro 文件&#xff09;。它…

吃顿饭的时间,用AI开发一个应用官网

最早接触开发时做的第一个项目就是企业官网&#xff0c;到后来自己开始走上独立开发者的道路时&#xff0c;哪怕是开发面向消费者的移动端产品&#xff0c;在产品上架时也需要提供应用官网。 感觉&#xff0c;编程这件事情和官网开发&#xff0c;紧密相连。 过往为了追求开发效…

个人微信 微信营销系统

个人微信 微信营销系统 CRM系统

Windows 玩转大模型第一天:大模型本地部署,调用大模型API可直接工程化应用(全部代码和详细部署流程)

Ollama 是一个开源框架&#xff0c;专为在本地机器上便捷部署和运行大型语言模型&#xff08;LLM&#xff09;而设计。 以下是其主要特点和功能概述&#xff1a; 1. 简化部署&#xff1a;Ollama 目标在于简化在 Docker 容器中部署大型语言模型的过程&#xff0c;使得非专业用…

ELK日志系统和Filebeat采集器的学习总结

ELK是ElasticSerach、Logstash、Kina Logstash负责采集数据&#xff0c;Logstash有三个插件&#xff0c;input、filter、output&#xff0c;filter插件作用是对采集的数据进行处理&#xff0c;过滤的&#xff0c;因此filter插件可以选&#xff0c;可以不用配置。 ElasticSear…

vulnhub靶场之DC-1

1 信息收集 1.1 主机发现 arp-scan -l 主机ip地址为&#xff1a;192.168.1.4 1.2 端口服务扫描 nmap -sS -sV -A -T5 -p- 192.168.1.4 开发22&#xff0c;80&#xff0c;111端口 1.3 目录扫描 dirsearch -u 192.168.1.4 2 渗透测试 2.1 先访问一下80端口 发现是一个…

K8S 部署 EFK

安装说明 系统版本为 Centos7.9 内核版本为 6.3.5-1.el7 K8S版本为 v1.26.14 ES官网 开始安装 本次安装使用官方ECK方式部署 EFK&#xff0c;部署的是当前的最新版本。 在 Kubernetes 集群中部署 ECK 安装自定义资源 如果能打开这个网址的话直接用这个命令安装,打不开的话…

半导体制造企业 文件共享存储应用

用户背景&#xff1a;半导体设备&#xff08;上海&#xff09;股份有限公司是一家以中国为基地、面向全球的微观加工高端设备公司&#xff0c;为集成电路和泛半导体行业提供具竞争力的高端设备和高质量的服务。 挑战&#xff1a;芯片的行业在国内迅猛发展&#xff0c;用户在上海…

科研绘图系列:R语言STAMP图(STAMP Plot)

介绍 STAMP图(STAMP plot)并非一个广泛认知的、具有特定名称的图表类型,而是可能指在STAMP(Statistical Analysis of Metagenomic Profiles:“STAMP: statistical analysis of taxonomic and functional profiles”)软件使用过程中生成的各种统计和可视化图表的总称。ST…