(数据结构与算法)经典排序算法

六个经典排序算法

重新学习《数据结构与算法》,理一理这些经典排序算法。

今天说说这几种常见的经典排序算法:
冒泡排序、选择排序、插入排序
希尔排序、快速排序、计数排序
(以升序为例)
冒泡排序
基本原理:
对两个相邻的元素进行比较,如果第一个比第二个大则两者交换。
同样的操作下,遍历整个数组,则将最大值置于末尾。
同样的道理,只需要对每一个元素都遍历一遍,则能依次将最大值置于(该次遍历的)末位。
(因为最小的值慢慢“浮”到最上面,所以称为:冒泡)
时间复杂度: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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值