六个经典排序算法
重新学习《数据结构与算法》,理一理这些经典排序算法。
今天说说这几种常见的经典排序算法:
冒泡排序、选择排序、插入排序
希尔排序、快速排序、计数排序
(以升序为例)
冒泡排序
基本原理:
对两个相邻的元素进行比较,如果第一个比第二个大则两者交换。
同样的操作下,遍历整个数组,则将最大值置于末尾。
同样的道理,只需要对每一个元素都遍历一遍,则能依次将最大值置于(该次遍历的)末位。
(因为最小的值慢慢“浮”到最上面,所以称为:冒泡)
时间复杂度:O(n²)
空间复杂度:O(1)
排序的稳定性:稳定
代码实现C:
//冒泡排序,data为待排序数组,data_num为数组长度
void bubble_sort(int* data) {
int temp; //用于交换
int Flag = 0; //标志位
for (int i = 0; i < data_num - 1; i++) {
for (int j = data_num - 1; j > i; j--) {
if (data[j] < data[j - 1]) {
swap(data[j - 1], data[j]);//两个数值交换
Flag = 1; //判断该轮遍历中有交换
}
}
if (Flag == 0)break; //如果该轮遍历中无交换,则直接退出
else Flag = 0;
}
}
选择排序
基本原理:
遍历一次数组,找到最小值,将最小值交换到数组第一位
遍历剩下的数组,再次找到剩下的最小值,将最小值交换到剩下的第一位
同理,只需要对每一个位置,遍历一次找到最小值并放到该位置上,即可完成升序排序。
(每一次都选择要交换的元素,所以称为:选择排序)
时间复杂度:O(n²)
空间复杂度:O(1)
排序的稳定性:不稳定
代码实现C:
//选择排序,data为待排序数组,data_num为数组长度
void select_sort(int* data) {
for (int i = 0; i < data_num - 1; i++) {
int k = i; //k用来记录当次循环中的最小值
for (int j = i + 1; j < data_num ; j++) if (data[k] > data[j]) k = j;
//找到该次遍历中的最小值,用k记录下最小值下标
swap(data[k],data[i]);
}
}
插入排序
基本原理:
将待排序数组中的第一位取出来,当成已排序数组的第一位。
取出待排序数组中的第二位,在已排序数组中从后往前找到合适位置并插入到该位置。
按照第二步,依次遍历整个待排序数组。即可完成排序
(因为“已排序数组”其实是在原数组上操作的,也即在原数组上,把后部分的数依次插入到原数组前部分来,所以只需要一个变量临时存交换值,不需要另外的空间,所以空间复杂度为O(1))
(因为整个过程都是从待排序数组里取出元素,再插入到已排序数组中,故称为:插入排序)
时间复杂度:O(n²)
空间复杂度:O(1)
排序的稳定性:稳定
代码实现C:
//插入排序,data为待排序数组,data_num为数组长度
void insert_sort(int* data) {
int temp;//用于交换
for (int i = 1; i < data_num; i++) { //将第一位当作已排序数组后,从第二位开始找起
temp = data[i];
int j = i - 1;
while (temp < data[j] && j >= 0) { //找到合适位置,不能 等于 ,排序为稳定排序
data[j + 1] = data[j];
j--;
}
data[j + 1] = temp;
}
}
<比较冒泡、选择、插入三种排序方法>
虽然冒泡排序、选择排序、插入排序,三个方法的时间复杂度都是O(n²),但是三者在实现过程中还是存在很多效率区别的。
经过测试下:
随机数范围均为:[0,10000]
数量级:10000
测试结果:
冒泡排序:663ms
选择排序:108ms
插入排序:55ms
结论:冒泡排序 < 选择排序 < 插入排序
希尔排序
上面我们说的插入排序,是直接插入排序。可以发现,插入排序在平均移动位数比较少的时候,插入起来就很快。所以,希尔排序其实也算是一种特殊方式的插入排序。
基本原理:
设定一个步长,对该步长下的各个元素进行插入排序。
步长逐渐减小,并同步骤一一样的操作。
如: 5 9 8 2 3 4,我们设步长为3,那就可以把每一个步长下的数据分出来: 5跟2,9跟3,8跟4,进行分别插入排序:得到 2 3 4 5 8 9
这样的一次操作后,我们发现在gap步长下,前面的普遍小于后面的。也就是说,这个阶段内位置移动次数每次都少于等于 data_num / gap,这样就减少了很多移动次数。
相同道理下,步长逐渐减少到1为止。
从趋势上,就是每一次步长遍历完,整体上前面为升序,然后在步长逐渐减小下,升序也逐渐确定下来。
时间复杂度:O(n^1.5)
空间复杂度:O(1)
排序的稳定性:不稳定
附上大神详解:https://blog.csdn.net/qq_39207948/article/details/80006224
代码实现C:
//希尔排序,shell_sort_fun为其调用到的插入排序,data为待排序数组,data_num为长度
void shell_sort_fun(int* data, int star,int gap) {
int temp;
for (int i = star + gap; i < data_num; i += gap) {
temp = data[i];
int j = i - gap;
while (j >= 0 && temp < data[j]) {
data[j + gap] = data[j];
j -= gap;
}
data[j + gap] = temp;
}
}
void shell_sort(int* data) {
int gap = data_num / 2;
while (gap >= 1) {
for (int i = 0; i < gap; i++) {
shell_sort_fun(data, i, gap);
}
gap = gap / 2;
}
}
快速排序
基本原理:
首先在数组中,找一个元素作为“基准”
开始遍历,比“基准”大的往后排,比“基准”小的往前排,直到前后相同时(也即:找到该“基准”放在中间位置,往前比“基准”小,往后比“基准”大)
在该“基准”的两侧,分别开始找下一个“基准”,再同样的操作下。
简单理解就是:用“基准”来把数组分成两个区间,往两个区间继续找“基准”来分区间(递归)
时间复杂度:O(nlogn)
空间复杂度:O(1)
(但在递归过程中要一直保留数据,所以从这个递归的过程来说,空间复杂度应该在 O(logn)~O(n))
排序的稳定性:不稳定
附上大神详解:https://www.sohu.com/a/246785807_684445
代码实现C:
// 快速排序,data为待排序数组,quick_sort_fun为基准划分区间的过程
int quick_sort_fun(int* data, int low, int high) {
int key;
key = data[low];
while (low < high) {
while (low < high && data[high] >= key)high--;
if (low < high)data[low++] = data[high];
while (low < high && data[low] <= key)low++;
if (low < high)data[high--] = data[low];
}
data[low] = key;
return low;
}
void quick_sort(int* data, int start, int end)
{
int pos;
if (start < end){
pos = quick_sort_fun(data, start, end);
quick_sort(data, start, pos - 1);
quick_sort(data, pos + 1, end);
}
}
计数排序
基本原理:
先遍历一遍整个待排序数组,找到最大值max,最小值min
新建一个计数数组a,长度为:max - min + 1(用来计数)
再遍历一遍整个待排序数组,并用计数数组来记录每一个数值出现的次数,
用数组的(下标 + min)来表示对应的数值,数组内存的index来表示该数值出现的次数。
遍历一遍计数数组,并将各个数值按个数输出到原数组。即已完成排序。
时间复杂度:Ο(n+k)(其中k是整数的范围)
空间复杂度:O(k)(其中k是整数的范围)
[这是一种牺牲空间来换取时间的做法,当整输范围不太大时,明显快于比较排序算法,但当O(k)>O(n*log(n)) 时,却反而效率不如比较排序]
排序的稳定性:不稳定
代码实现C:
//计数排序,data为待排序数组。该程序默认范围为[0,10000],所以省略min
void count_sort(int* data) {
int max = data[0];
int k = 0;
//找到最大值
for (int i = 0; i < data_num; i++)if (data[i] > max) max = data[i];
int* n_data = (int*)malloc(sizeof(int) * (max + 1));//n_data计数数组
memset(n_data, 0, sizeof(int) * (max + 1));//初始化计数数组
for (int i = 0; i < data_num; i++)n_data[data[i]] += 1;
for (int i = 0; i < max + 1; i++) {
for (int j = 0; j < n_data[i]; j++)data[k++] = i;
}
}
总结一下:
排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 | 测试用时/ms |
---|---|---|---|---|
冒泡排序 | O(n²) | O(1) | 稳定 | 64754 |
选择排序 | O(n²) | O(1) | 不稳定 | 10415 |
插入排序 | O(n²) | O(1) | 稳定 | 5490 |
希尔排序 | O(n^1.5) | O(1) | 不稳定 | 24 |
快速排序 | O(nlogn) | O(1) | 不稳定 | 14 |
计数排序 | O(n+k),(k为整数范围) | O(k) | 不稳定 | 0.3(两次0ms,一次1ms) |
(测试环境:10W个[0,10000]的随机数,三次平均值)
除此今天介绍的这些经典排序算法外,还有很多经典高效的排序算法:
堆排序、桶排序、基数排序、归并排序等等等,日后有时间再继续写叭~
有什么不对的地方,很欢迎指正谢谢~
附上全部代码:
#include <stdio.h>
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;
#define data_num 100000
#define rand_num 10000
void make_data(int* data) {
for (int i = 0; i < data_num; i++) {
data[i] = rand() % rand_num;
}
}
void swap(int& a, int& b) {
int temp;
temp = a;
a = b;
b = temp;
}
//冒泡排序
void bubble_sort(int* data) {
int temp; //用于交换
int Flag = 0; //标志位
for (int i = 0; i < data_num - 1; i++) {
for (int j = data_num - 1; j > i; j--) {
if (data[j] < data[j - 1]) {
swap(data[j - 1], data[j]);//两个数值交换
Flag = 1; //判断该轮遍历中有交换
}
}
if (Flag == 0)break; //如果该轮遍历中无交换,则直接退出
else Flag = 0;
}
}
//选择排序
void select_sort(int* data) {
for (int i = 0; i < data_num - 1; i++) {
int k = i; //k用来记录当次循环中的最小值
for (int j = i + 1; j < data_num ; j++) {
if (data[k] > data[j]) k = j;
}
swap(data[k],data[i]);
}
}
//插入排序
void insert_sort(int* data) {
int temp;//用于交换
for (int i = 1; i < data_num; i++) { //将第一位当作已排序数组后,从第二位开始找起
temp = data[i];
int j = i - 1;
while (temp < data[j] && j >= 0) { //找到合适位置,不能 等于 ,排序为稳定排序
data[j + 1] = data[j];
j--;
}
data[j + 1] = temp;
}
}
//希尔排序
void shell_sort_fun(int* data, int star,int gap) {
int temp;
for (int i = star + gap; i < data_num; i += gap) {
temp = data[i];
int j = i - gap;
while (j >= 0 && temp < data[j]) {
data[j + gap] = data[j];
j -= gap;
}
data[j + gap] = temp;
}
}
void shell_sort(int* data) {
int gap = data_num / 2;
while (gap >= 1) {
for (int i = 0; i < gap; i++) {
shell_sort_fun(data, i, gap);
}
gap = gap / 2;
}
}
// 快速排序
int quick_sort_fun(int* data, int low, int high) {
int key;
key = data[low];
while (low < high) {
while (low < high && data[high] >= key)high--;
if (low < high)data[low++] = data[high];
while (low < high && data[low] <= key)low++;
if (low < high)data[high--] = data[low];
}
data[low] = key;
return low;
}
void quick_sort(int* data, int start, int end)
{
int pos;
if (start < end){
pos = quick_sort_fun(data, start, end);
quick_sort(data, start, pos - 1);
quick_sort(data, pos + 1, end);
}
}
//计数排序
void count_sort(int* data) {
int max = data[0];
int k = 0;
for (int i = 0; i < data_num; i++) {
if (data[i] > max) max = data[i];
}
int* n_data = (int*)malloc(sizeof(int) * (max + 1));
memset(n_data, 0, sizeof(int) * (max + 1));
for (int i = 0; i < data_num; i++)n_data[data[i]] += 1;
for (int i = 0; i < max + 1; i++) {
for (int j = 0; j < n_data[i]; j++) {
data[k++] = i;
}
}
}
int main() {
//生成num个随机数
srand(0);
int data[data_num];
make_data(data);
printf("初始数组: ");
//for (int i = 0; i < data_num; i++)printf("%d ", data[i]);
printf("\n");
time_t time = clock();
//bubble_sort(data);//冒泡排序
//select_sort(data);//选择排序
//insert_sort(data);//插入排序
//shell_sort(data);//希尔排序
//quick_sort(data, 0, data_num - 1);//快速排序
count_sort(data);//计数排序
cout << clock() - time << "ms" << endl;
printf("排序后数组:");
//for (int i = 0; i < data_num; i++)printf("%d ", data[i]);
return 0;
}