【排序算法略解】(十种排序的稳定性,时间复杂度以及实现思想)(含代码)(完工于2023.8.3)

文章目录

  • 1、冒泡排序/选择排序/插入排序
    • 冒泡排序(Bubble Sort)
    • 选择排序(Selection Sort)
    • 插入排序(Insertion Sort)
  • 2、希尔排序(Shell's Sort)
  • 3、快速排序(Quick Sort)
  • 4、堆排序(Heap Sort)
  • 5、归并排序(Merge Sort)
  • 6、桶排序/计数排序/基数排序
    • 桶排序(Bucket sort)
    • 计数排序(Counting Sort)
    • 基数排序(Radix Sort)

注:以下排序默认为升序排序。

稳定性:指的是排序的过程中是否会改变多个相同的值的相对次序,如果会改变则是不稳定的。

1、冒泡排序/选择排序/插入排序

冒泡排序,选择排序,插入排序是最简单的排序方法。

冒泡排序(Bubble Sort)

排序方法:扫描的过程中,比较相邻两个数的大小关系,如果存在逆序就交换这两个数,这样每趟可以保证最后一个数,也就是最大的数,一步步交换上去,如气泡上浮一样,回归到正确的位置。

时间复杂度: O ( n 2 ) O(n^2) O(n2)

稳定性:稳定。对于两个相同的值,不会发生交换。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//arr是被排序数组,start是起点坐标,end是终点坐标(闭区间)void sort(int* arr, int start, int end) {int n = end - start + 1;//只需要排序n - 1次for (int i = 1; i < n; i++) {//倒数i - 1个数已经归位不必交换for (int j = start; j <= end - i; j++) {//出现逆序if (arr[j + 1] < arr[j]) {int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}

选择排序(Selection Sort)

排序方法:扫描的过程中,每次选择剩余未排序的数组中选择最小值与未排序部分第一个值进行交换,从而使得未排序部分的第一个位置得到正确的值。

时间复杂度: O ( n 2 ) O(n^2) O(n2)

稳定性:不稳定。例如,3 3 2 → 一次交换 \stackrel{一次交换}{\rightarrow} 一次交换 2 3 3 可以发现两个3的位置改变了。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//arr是被排序数组,start是起点坐标,end是终点坐标(闭区间)void sort(int* arr, int start, int end) {int n = end - start + 1;//只需要排序n - 1次for (int i = 1; i < n; i++) {int min_element_positon = start + i - 1;for (int j = start + i - 1; j <= end; j++) {if (arr[j] < arr[min_element_positon]) {min_element_positon = j;}}int tmp = arr[min_element_positon];arr[min_element_positon] = arr[start + i - 1];arr[start + i - 1] = tmp;}
}

插入排序(Insertion Sort)

排序方法:每次取未排序部分的数组,将其插入到前半已经排序后的数组内,类似打牌时整理牌序的方法,插入到前面整理好的数组内的合适的位置。

时间复杂度: O ( n 2 ) O(n^2) O(n2)

稳定性:稳定。相同的值符合大于等于关系,并不会插入到相同值的前面。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//arr是被排序数组,start是起点坐标,end是终点坐标(闭区间)void sort(int* arr, int start, int end) {int n = end - start + 1;//只需要排序n - 1次for (int i = 1; i < n; i++) {int key = arr[start + i];int j = start + i;while (j >= start) {if (j == start) break; // 如果是最小值,会一直到开头if (j == start + i && key >= arr[j - 1]) break; //如果是最大值if (key >= arr[j - 1] && key <= arr[j + 1]) break;//在中间合适的值arr[j] = arr[j - 1];j--;}arr[j] = key;}
}

2、希尔排序(Shell’s Sort)

排序方法:每次选择特定的增量,对这个增量下的子序列进行直接插入排序。

最好时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

最坏时间复杂度: O ( n n ) O(n\sqrt{n}) O(nn )【选取d={1, 2, 4, 8, …2k}大到小】,$O(n\log{n}\log{n})$【选取2m*2^n次大到小的集合】

关于时间复杂度的证明:主要考虑对于两个不同增量的序列的插入排序,两者的结果都是可以互相继承的;以及相关其他复杂度的 分析

稳定性:不稳定,因为不同序列相同的值的位置可能改变。

代码实现:

int arr[] = {0, 2, 9, 55, 54, 7, 8, 1, 40, 600};void sort(int* arr, int start, int end) {int n = end - start + 1;for (int d = n / 2; d >= 1; d /= 2) {//倍减for (int i = d; i < n; i++) {//一共进行n - d次插入int tmp = arr[start + i];//取无序部分第一个数进行插排int j = i - d;for (; j >= 0 && tmp < arr[j + start]; j -= d) {arr[j + d + start] = arr[j + start];}//如果前面有比最后一个数小的,就腾出一位//实际上会多减一次,要补回darr[j + d + start] = tmp;}}
}

3、快速排序(Quick Sort)

排序方法:“挖坑填数”,取出需要排序区间的第一个数作为基数,利用双指针方法,整理当前数组,使得这个数组成为坑左边均为小于等于基数的数,坑右边都是大于等于基数的数,最后把基数填入这个位置,此处基数回到正确的位置。然后分成两个区间,递归分治,重复上述操作。

一般时间复杂度: O ( n log ⁡ ( n ) ) O(n\log(n)) O(nlog(n))

最坏时间复杂度(例如数组已经有序的时候): O ( n 2 ) O(n^2) O(n2)

稳定性:不稳定。例如 6 3 3 经过一次的填坑得到 3 3 _ 3的次序发生了改变。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//arr是被排序数组,start是起点坐标,end是终点坐标(闭区间)void sort(int* arr, int start, int end) {if (start >= end) { //如果只有1个元素,或者没有元素就不需要排序return;}//目的是把数组整理成一半小于等于基数,一半大于等于基数,中间是坑位的样子int l = start, r = end, key = arr[l];//取第一个元素作为基数while (l < r) {while (l < r && arr[r] >= key) {r--;//从后往前找到第一个小于key的值}if (l < r) {//如果相遇则说坑位左右两侧都已经整理完毕arr[l++] = arr[r];}while (l < r && arr[l] <= key) {l++;//从前往后找到第一个大于key的值}if (l < r) {arr[r--] = arr[l];}}arr[l] = key;sort(arr, start, l - 1);sort(arr, l + 1, end);
}

4、堆排序(Heap Sort)

排序方法:利用标号关系构造一个无序堆,通过堆调整,形成一个有序的大顶堆。每次取堆顶的数和未排序数的最大标号,即堆底最后一个叶子交换位置,此时,最后一个叶子是当前未排序的数中最大的数,已经回到正确的位置,离开堆。之后,调整堆,维护大顶堆的性质,等待下一次交换。

关于堆调整:如果父节点最大不交换,如果父节点不是最大,则要交换左右儿子中较大的一个,交换后,可以使得原来那个较小儿子所在的子树的父节点变成较大儿子,这样保证较小儿子的子树符合二叉堆的性质,并且根节点也符合条件,但是,可能会影响较另外子数成堆,所以,为了维护堆的性质,要一直交换下去,直到符合性质,最坏可能要交换到叶子,但是这样的交换不超过 log ⁡ n \log{n} logn次,因为每次交换都可以使得近似大小的另一子树符合堆的性质,需要维护的堆的性质的节点数倍减,直至单个节点,单个节点符合堆的性质。

时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

时间复杂度分析:开始的堆调整 n n n次,之后每个数取出后的堆调整的交换次数取决于二叉堆的深度,由于二叉堆是一个完全二叉树,所以深度不超过 log ⁡ n \log{n} logn所以一趟的交换次数不超过 log ⁡ n \log{n} logn次。

稳定性:不稳定。如果是全相等的堆,仍然会进行交换。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//arr是被排序数组,start是起点坐标,end是终点坐标(闭区间)
//堆调整函数,维护当前子树堆的性质
void heap_modify(int* arr, int root_id, int last_id, int start) {int tmp = arr[root_id + start];for (int i = 2 * root_id + 1; i <= last_id; i = i * 2 + 1) {//枚举左儿子if (i < last_id && arr[i + start] < arr[i + 1 + start]) {//如果有右儿子,并且右儿子更大,取右儿子进行比较//否则取左儿子i++;}//较大的儿子和根节点进行比较,都是和原来根节点交换的数进行比较if (arr[i + start] > tmp) {arr[root_id + start] = arr[i + start];root_id = i;//较大的儿子的id是根节点的下一个id}else {//如果较大的儿子不大于根节点,不必调整下去break;}}arr[root_id + start] = tmp;//填坑
}
void sort(int* arr, int start, int end) {//构造有序堆for (int i = end; i >= start; i--) {//映射成0~n的标号,倒着整理heap_modify(arr, i - start, end - start, start);}int n = end - start + 1;for (int i = 1; i < n; i++) {//n - 1趟int tmp = arr[start];arr[start] = arr[end - i + 1];//与当前无序数组最大下标交换arr[end - i + 1] = tmp;heap_modify(arr, 0, end - i - start, start);//维护堆的性质}
}

5、归并排序(Merge Sort)

排序方法:利用分治的思想,将数组不断划分,到单个数,然后递归合并起来,利用双指针的手法,将两个有序数组合并成一个有序的数组。

时间复杂度: O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

稳定性:稳定,合并的数组的时候,如果两个值相同的话会按照原先的顺序依次放入到临时数组当中。

代码实现:

int arr[] = {0, 2, 9, 55, 54, 7, 8, 1, 40, 600};
int tmp[1000];
void merge_arr(int* arr, int* tmp, int start, int end, int mid) {int i = start, j = mid + 1, p = 0;//要保证归并排序的稳定性,<=的时候优先取左侧while (i <= mid && j <= end) {if (arr[i] <= arr[j]) {tmp[p++] = arr[i++];} else {tmp[p++] = arr[j++];}}//当一个数组填完了,剩余的部分要填完,也就是另一个数组内不存在小于等于这个数的数据了while (i <= mid) {tmp[p++] = arr[i++];}while (j <= end) {tmp[p++] = arr[j++];}//回填入数组for (int k = start; k <= end; k++) {arr[k] = tmp[k - start];}
}
void sort(int* arr, int start, int end) {//二分区间if (start < end) {//这个条件需要否则会无限递归int  mid = (start + end) >> 1;sort(arr, start, mid);//分成start~mid mid+1~end两个区间sort(arr, mid + 1, end);merge_arr(arr, tmp, start, end, mid);}
}

6、桶排序/计数排序/基数排序

桶排序(Bucket sort)

排序方法:将输入值的值域划分成若干个区间,分别放入到桶中,再用其他排序方法对桶内的元素进行排序,类似分块,分治。

时间复杂度:取决于桶内排序算法的时间复杂度以及分类后所在的桶的数目,如果使用堆排序来进行桶内排序的话,并且最后均匀地分入到k个桶内,那么时间复杂度是 O ( n + n log ⁡ n k ) O(n+n\log{\frac{n}{k}}) O(n+nlogkn),当桶足够多时,覆盖了值域的时候,便是 O ( n ) O(n) O(n)的计数排序。

稳定性:取决于给桶内排序的算法的稳定性,如果是插入排序就是稳定的,如果是堆排序或者快速排序就是不稳定的。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//arr是被排序数组,start是起点坐标,end是终点坐标(闭区间)void sort(int* arr, int start, int end, int bucket_size) {int min_value = arr[start];int max_value = arr[start];//获得值域for (int i = start; i <= end; i++) {if (arr[i] < min_value) {min_value = arr[i];}if (arr[i] > max_value) {max_value = arr[i];}}int val_area = max_value - min_value + 1;int bucket_nums = (val_area + bucket_size - 1) / bucket_size;//获得向上取整的桶的数目//如果此处size为4,分成 0~3, 4~7, 8~9三个桶,标号分别是0 1 2for (int i = start; i <= end; i++) {int x = arr[i];int id = (x - min_value) / bucket_size;buckets[id][elements_in_bukect[id]++] = x;//填入一个数字}for (int i = 0; i < bucket_nums; i++) {if (elements_in_bukect[i] == 0) continue;int len = elements_in_bukect[i];heap_sort(buckets[i], 0, len - 1);}//回收排序好的数字int p = 0;for (int i = 0; i < bucket_nums; i++) {if (elements_in_bukect[i] == 0) continue;int len = elements_in_bukect[i];for (int j = 0; j < len; j++) {arr[p + start] = buckets[i][j];p++;}}
}

计数排序(Counting Sort)

排序方法:计数排序是桶排序中桶的数量和值域长度相同的时候,就是一次读入数据,放入到对应值的桶内,最后遍历值域输出排序好的数组。

时间复杂度: O ( n + k ) O(n+k) O(n+k)其中k是值域长度,n是元素数目。

稳定性:稳定。按序读入相同值,按序拷贝出来。笔者想了想,直接用一维数组有点难以体现稳定性,如果用可变长数组或者链表来实现的话就能维护相同值的相对次序,每个链表存入该值的id。或者用二维数组。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 4, 6};//这里没有用链表
int cnt[100];
void sort(int* arr, int start, int end) {int min_value = arr[start];int max_value = arr[start];for (int i = start; i <= end; i++) {if (arr[i] < min_value) {min_value = arr[i];}if (arr[i] > max_value) {max_value = arr[i];}}for (int i = start; i <= end; i++) {cnt[arr[i] - min_value]++;}int p = start;for (int i = 0; i <= max_value - min_value; i++) {for (int j = 0; j < cnt[i]; j++) {arr[p++] = i + min_value;}}
}

基数排序(Radix Sort)

排序方法:基数排序相当于对每一位作计数排序。

时间复杂度: O ( k ⋅ n ) O(k·n) O(kn)其中k是最大数的某进制形式下的数码长度(通常是十进制)

原理说明:手写高位到低位分别是主要关键字,次要关键字,,,次次关键字,最次关键字。首先我们根据最低位排序,如果后面高位不相同,会按照高位排序,如果高位相同的话,但是低位不相同,计数排序具有稳定性,不会改变低位已经排好的顺序。

稳定性:稳定。同上。

注意:基数排序的需要非负整数,如果有负数的话需要调整为非负整数。

代码实现:

int arr[] = {0, 2, 9, 3, 5, 7, 8, 1, 40, 600};//这里没有用链表
int buckets[10][10000];//数码桶,一个桶里可以装10000个数
int elements_in_bucket[10];//数码桶内数有多少
void sort(int* arr, int start, int end) {int max_value = arr[start];for (int i = start; i <= end; i++) {if (arr[i] > max_value) {max_value = arr[i];}}int max_length = 0;while (max_value > 0) {max_length++;max_value /= 10;}if (max_length == 0) return;//如果都是0for (int i = 0, base = 1; i < max_length; i++, base *= 10) {//对每一位进行排序for (int j = start; j <= end; j++) {int digit = arr[j] / base % 10;//存入某位数码为digit的桶buckets[digit][elements_in_bucket[digit]] = arr[j];//桶内数目elements_in_bucket[digit]++;}int index = start;for (int j = 0; j < 10; j++) {//遍历数码桶if (elements_in_bucket[j] != 0) {for (int k = 0; k < elements_in_bucket[j]; k++) {arr[index++] = buckets[j][k];}}elements_in_bucket[j] = 0;//}}
}

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

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

相关文章

【sklearn】回归模型常规建模流程

模型训练pipeline 基于数十种统计类型特征&#xff0c;构建LR回归模型。代码逻辑包含&#xff1a;样本切分、特征预处理、模型训练、模型评估、特征重要性的可视化。 步骤一&#xff1a;导入所需库 import pandas as pd import numpy as np import matplotlib.pyplot as plt…

用P2PNet进行大豆计数

文章目录 介绍在大豆数据集上可视化结果环境准备数据集结构数据链接模型训练模型推理代码介绍 这个仓库包含了P2PNet(Rethinking Counting and Localization in Crowds: A Purely Point-Based Framework)在大豆数据集上的pytorch实现。 在大豆数据集上可视化结果 环境准备 …

php meilisearch demo

# 创建一个meilisearch 使用完自动销毁 docker run -itd --rm -p 7700:7700 getmeili/meilisearch:v1.3docker-compose 参数 version: "3" networks:flyserver:driver: bridge services:search:image: getmeili/meilisearch:v1.3restart: alwaysenvironment:- MEILI…

分享一个霓虹灯拨动开关

先看效果&#xff1a; 再看代码&#xff08;查看更多&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title> 霓虹灯拨动开关</title><style>* {border: 0;box-sizin…

算法-链表树

链表 反转单向链表 该题⽬来⾃ LeetCode&#xff0c;题⽬需要将⼀个单向链表反转。思路很简单&#xff0c;使⽤三个变量分别表示当前节点和当前节点的前后节点&#xff0c;虽然这题很简单&#xff0c;但是却是⼀道⾯试常考题 var reverseList function(head) { // 判断下变…

WSL安装

WSL安装 1.Microsoft store 安装 1.1 启动WSL功能 在【程序和功能 -> 启用或关闭 Windows 功能】中勾选【适用于 Linux 的 Windows 子系统】 1.2 Store中下载安装 在 Microsoft Store 中下载并安装需要的 Linux 发行版 2.不使用Store安装WSL 注&#xff1a;1.1也要…

js执行机制

JavaScript 的执行机制是基于单线程的事件循环模型。这意味着 JavaScript 代码会按照顺序一行一行地执行&#xff0c;同时只能执行一个任务。让我们更详细地了解 JavaScript 的执行机制&#xff1a; 调用栈&#xff08;Call Stack&#xff09;&#xff1a; JavaScript 使用调用…

激活函数总结(一):ReLU及其变体

激活函数介绍&#xff08;一&#xff09; 1 引言2 常用激活函数介绍2.1 Sigmoid激活函数2.2 Tanh激活函数2.3 ReLU激活函数2.4 Leaky ReLU激活函数2.5 Parametric ReLU&#xff08;PReLU&#xff09;激活函数2.6 Swish激活函数 3. 总结 介绍的激活函数都在目录中有所展示&#…

Mysql删除重复数据通用SQL

在日常开发过程中&#xff0c;可能会出现一些 bug&#xff0c;导致 Mysql 数据库数据重复&#xff0c;需要删除重复数据&#xff0c;这里记录下删除重复数据的通用 SQL &#xff0c;方便以后需要时查阅 1、写法一 DELETE t1 FROMtbl_name t1 INNER JOIN tbl_name t2 WHEREt1.…

强人工智能转向超人工智能的突破点(猜测)

现如今&#xff0c;人类已经能够借助大量的资源&#xff0c;完成强人工智能。可能向大家接触到的X-EVA之类的APP&#xff0c;里面的虚拟人类有时候会说话五迷三道的&#xff0c;但这只是因为数据不够多&#xff0c;硬件不够支持。在资金足够的情况下&#xff0c;强人工智能已经…

WFPlayer

WFPlayer WFPlayer 可以实现分析音视频生成音频波形图 在线demo地址: demo WFPlayer支持&#xff1a; 在不加载整个媒体文件的情况下创建波形自定义光标、进度、网格、标尺显示和颜色加载媒体url和加载媒体dom元素&#xff08;视频标签和音频标签&#xff09;颜色或宽度等实时…

Linux 块设备操作函数

和字符设备的fil_operations一样&#xff0c;块设备也有操作集&#xff0c;为结构体block_device_operations&#xff0c;此结构体定义在include/linux/blkdev.h中&#xff0c;结构体内容如下&#xff1a; struct block_device_operations {int (*open) (struct block_device …

Flutter编译一直显示Running Gradle task ‘assembleDebug‘

&#x1f525; 目前开发的Android Studio版本 &#x1f525; &#x1f525; 当前Flutter SDK 版本 &#x1f525; Flutter 3.10.6 • channel stable • https://github.com/flutter/flutter.git Framework • revision f468f3366c (3 周前) • 2023-07-12 15:19:05 -0700 Eng…

MySQL之深入InnoDB存储引擎——Buffer Pool

文章目录 一、空闲链表的管理二、缓冲页的哈希处理三、Flush链表的管理四、LRU链表的管理五、脏页刷新六、多Buffer Pool实例 InnoDB存储引擎是基于磁盘存储的&#xff0c;并将其中的记录按照页的方式进行管理。在数据库系统中&#xff0c;由于CPU速度与磁盘速度之间的鸿沟&…

docker xserver是什么

在Docker环境中&#xff0c;XServer是一个用于图形显示的X Window系统服务器。X Window系统是一种常用的图形用户界面&#xff08;GUI&#xff09;系统&#xff0c;允许在图形化桌面环境中运行应用程序。 当在Docker容器中运行需要图形界面的应用程序时&#xff0c;通常需要将…

用户权限提升Sudo

目录 前言 一、su的用法 二、sudo提权 总结 前言 sudo是linux系统管理指令&#xff0c;是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具&#xff0c;如halt&#xff0c;reboot&#xff0c;su等等。换句话说通过此命令可以让非root的用户运行只有root才有权限…

XML(eXtensible Markup Language)

目录 为什么需要XML? 一 XML语法 1.文档声明 2.元素 语法: 3.属性 4.注释 5.CDATA节 二 树结构 三 转义字符 四 DOM4J 1.XML解析技术 2.dom4j介绍 3.dom4j基本使用 XML 指可扩展标记语言&#xff08;eXtensible Markup Language&#xff09;。 XML 被设计用来传…

Killing LeetCode [83] 删除排序链表中的重复元素

Description 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 Intro Ref Link&#xff1a;https://leetcode.cn/problems/remove-duplicates-from-sorted-list/ Difficulty&#xff1a;Easy Tag&am…

全面讲解最小二乘法

常见的最小二乘法我们就不多说了&#xff0c;下面主要介绍一下最小二乘法的一些先进方法。 正则化的最小二乘法 在使用常见的最小二乘法进行回归分析时&#xff0c;常常会遇到过拟合的问题&#xff0c;也就是在训练数据集上表现的很好&#xff0c;但是在测试数据集上表现的很…