常见的排序算法 【复习笔记】

注意:

  1. 后面的排序算法实现都只考虑升序,对于逆序,只有知道原理,实现很容易

  2. 案例题:

题目描述:将读入的 N 个数从小到大输出 ( 1 <= N <=10e5)

输入描述:第一行一个正整数 N

第二行 N 个空格隔开的正整数 ai

输出描述:给定的 N 个数从小到大输出,空格隔开,行末换行无空格

输入:5

4 2 4 5 1

输出:1 2 4 4 5

1. 插入排序

1.1 算法思想

每次将一个待排序元素按照关键字大小插入到已排序好的序列中

时间复杂度:1. 当整个序列升序,时间复杂度O(n)

  1. 当整个序列逆序,时间复杂度O(N^2)

1.2 代码实现

cpp 复制代码
​#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n;

void insert_sort()
{
	//从第二个元素开始,依次枚举待排序元素
	for (int i = 2; i <= n; i++)
	{
		//先存储要排序的元素
		int tmp = a[i];
		//依次和前面的元素比较
		int j = i - 1;
		while ( a[j] > tmp&&j >= 1)
		{
			a[j+1] = a[j];
			j--;
		}
		//当出循环时,要么a[j]<=tmp,要么遍历完已排序序列
		//直接在j+1的位置放入元素
		a[j+1] = tmp;
	}
}
int main()
{
	cin >> n;
	//从a[1]开始放入元素
	for (int i = 1; i <= n; i++) cin >> a[i];

	//插入排序
	insert_sort();
	
	for (int i = 1; i <= n; i++)cout << a[i] << " ";

	return 0;
}

​

2. 选择排序

2.1 算法思想

每次找出未排序序列的最小元素,然后放入有序序列后面

时间复杂度:总会遍历一遍所有未排序序列,时间复杂度O(n^2)

2.2 代码实现

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n;

void selection_sort()
{
	//待排序序列为 [i,n]
	for (int i = 1; i < n; i++)
	{
		//从 [i,n] 中选择最小值的位置
		int pos = i;
		for (int j = i+1; j <= n; j++)
		{
			if (a[pos] > a[j]) pos = j;
		}
		//进行交换,将最小元素放入有序序列后面
		swap(a[i], a[pos]);
	}
}

int main()
{
	cin >> n;
	//从a[1]开始放入元素
	for (int i = 1; i <= n; i++) cin >> a[i];

	//选择排序
    selection_sort();
	
	for (int i = 1; i <= n; i++)cout << a[i] << " ";

	return 0;
}

3. 冒泡排序

3.1 算法思想

进行 n -1 趟操作,每一趟进行多次比较,每次比较相邻的两个元素,如果满足排序条件,就交换;每趟可以将一个元素放到正确位置,像气泡一样,气泡大的慢慢冒出来,较大的数慢慢到最终的位置上

对于最朴素的冒泡排序,如果数组本身就是升序,还是会进行 n -1 趟操作,时间复杂度O(n^2)

但我们可以进行优化,如果有一趟操作,所有的元素都没有交换,证明数组已经是升序了,不用进行接下来的操作

优化后的冒泡排序时间复杂度:

  1. 数组升序,只扫描一遍,时间复杂度O(n)

  2. 数组逆序,时间复杂度O(n^2)

3.2 代码实现

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n;

void bubble_sort()
{
	//进行 n - 1 趟排序
	for (int i = n; i > 1; i--)
	{
		//标记是否交换元素
		bool flag = false;
		// [ 1,i] 为待排序区间
		for (int j = 1; j < i; j++)
		{
			//每一趟比较相邻元素
			if (a[j] > a[j + 1])
			{
				swap(a[j], a[j + 1]);
				flag = true;
			}
			
		}
		//如果没交换元素,直接结束
		if (flag==false) return;
	}
}

int main()
{
	cin >> n;
	//从a[1]开始放入元素
	for (int i = 1; i <= n; i++) cin >> a[i];

	//冒泡排序
    bubble_sort();
	
	for (int i = 1; i <= n; i++)cout << a[i] << " ";

	return 0;
}

4. 堆排序

4.1 算法思想

本质是优化选择排序,将数据放入堆(堆和STL ------ priority------queue)中,可以快速得到待排序元素的最小值或最大值

步骤:1. 建堆,升序大根堆,降序小根堆。

注意: 建堆不是新建一个堆结构,将数据放入新建的堆结构中。而是将数组的逻辑结构化为堆结构

具体方法:从倒数第一个非叶子结点开始,倒着到根结点位置,每个结点向下调整

  1. 排序,每次将堆顶元素和堆最后一个元素交换,堆元素就少一个,将堆顶元素向下调整。直到堆剩一个元素

时间复杂度:1. 建堆的时间复杂度通过每个结点的调整层数计算约为O(n)

  1. 排序的时间复杂度为O(nlog n)

所以总的时间复杂度为O(nlog n)

4.2 代码实现

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n;

//向下调整
void down(int parent, int len)
{
	int child = parent * 2;
	while (child <= len)
	{
		if (child + 1 <= len && a[child] < a[child+1])child++;
		if (a[parent] >= a[child])return;
		swap(a[parent], a[child]);

		parent = child;
		child = parent * 2;
	}
}

void heap_sort()
{
	//建堆,从第一个非叶结点开始
	for (int i = n / 2; i >= 1; i--)
	{
		down(i, n);
	}
	//排序
	for (int i = n; i > 1; i--)
	{
		swap(a[i], a[1]);
		//堆元素减去1
		down(1, i-1);
	}
}
int main()
{
	cin >> n;
	//从a[1]开始放入元素
	for (int i = 1; i <= n; i++) cin >> a[i];

	//堆排序
    heap_sort();
	
	for (int i = 1; i <= n; i++)cout << a[i] << " ";

	return 0;
}

5. 快速排序

5.1 算法思想

5.1.1 常规快排

在待排序区间任取一个元素作为基准元素,按照这个基准元素的大小将区间元素分为左右两个部分,左边区间元素小于基准元素,右边区间元素大于等于基准元素。再递归排序基准值左区间和基准值右区间(其实,快速排序有点像树的先序遍历)

5.1.2 优化

有两种极端情况:

a. 如果每次选择基准元素为待排序序列的最左边元素,那如果整个序列升序,右区间每次划分都会很长,递归层数就是 n 层,那时间复杂度就退化为O(n^2)

b. 如果每次选择基准元素为待排序序列的中间元素,那如果整个序列为相同元素,那时间复杂度依旧会退化为O(n^2)

优化一:随机选择基准元素

cpp 复制代码
#include<iostream>
#include<ctime>
using namespace std;

int main()
{
	//种下一个种子
	srand(time(0));
	//获得一个随机数
	rand();

	return 0;
}

优化二:数组分三块(数组分块问题 【刷题反思】-CSDN博客

按基准元素,小于基准元素的放入左边区间,等于基准元素的放入中间区间,大于基准元素的放入右边区间。中间区间不用管,递归处理左右区间即可

经过优化,块排的时间复杂度为O(nlog n)

5.2 代码实现

cpp 复制代码
#include<iostream>
#include<ctime>
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n;

int get_rand(int left,int right)
{
	//保证随机元素在区间内
	return  a[rand() % (right - left + 1) + left];
}

void quick_sort(int left, int right)
{
    //递归结束条件
    if (left >= right) return;
	//得到随机基准元素
	int q = get_rand(left,right);

	//排序,数组分三块
	int l = left - 1, i = left, r = right + 1;
	while (i < r)
	{
		if (a[i] < q) swap(a[++l], a[i++]);
		else if (a[i] == q)i++;
		else swap(a[--r], a[i]);
	}
	
	//递归处理左右区间
	quick_sort(left, l);
	quick_sort(r, right);
}
int main()
{
	cin >> n;
	//从a[1]开始放入元素
	for (int i = 1; i <= n; i++) cin >> a[i];

	//快速排序
    //种下一个随机数种子
    srand(time(0));
    quick_sort(1,n);
	
	for (int i = 1; i <= n; i++)cout << a[i] << " ";

	return 0;
}

6. 归并排序

6.1 算法思想

归并排序的思想是分治思想,先将整个数组区间从中间一分为二,然后把左右区间排序合并。这是递归实现的,每次都将先将区间一分为二,再排序(这一过程类似树的后序遍历)

时间复杂度:每次都是一分为二,时间复杂度可以稳定O(nlog n)

6.2 代码实现

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int a[N];
int n;

//创建辅助数组合并两个有序序列
int tmp[N];
void merge_sort(int left, int right)
{
	//递归结束条件
	if (left >= right) return;
	//先一分为二
	int mid = (left + right) / 2;
	//左右区间先有序
	merge_sort(left, mid);
	merge_sort(mid + 1, right);

	//合并左右两个有序区间
	int cur1 = left, cur2 = mid+1, i = left;
	while (cur1 <= mid && cur2 <= right)
	{
		if (a[cur1] <= a[cur2]) tmp[i++] = a[cur1++];
		else tmp[i++] = a[cur2++];
	}
	while (cur1 <= mid)tmp[i++] = a[cur1++];
	while(cur2 <= right)tmp[i++] = a[cur2++];
	for (int j = left; j <= right; j++)
	{
		a[j] = tmp[j];
	}
}
int main()
{
	cin >> n;
	//从a[1]开始放入元素
	for (int i = 1; i <= n; i++) cin >> a[i];

	//归并排序
    merge_sort(1,n);
	
	for (int i = 1; i <= n; i++)cout << a[i] << " ";

	return 0;
}
相关推荐
闻缺陷则喜何志丹几秒前
【二分查找 前缀和】P10429 [蓝桥杯 2024 省 B] 拔河|普及+
c++·算法·前缀和·蓝桥杯·二分查找·洛谷·拔河
Jason_Orton16 分钟前
决策树(Decision Tree):机器学习中的经典算法
人工智能·算法·决策树·随机森林·机器学习
筏.k30 分钟前
动态规划之 “完全背包“ ------P8646 [蓝桥杯 2017 省 AB] 包子凑数
算法·蓝桥杯·动态规划
clownAdam1 小时前
通俗易懂的分类算法之支持向量机详解
算法·支持向量机·分类·数据挖掘
飞3001 小时前
淘天集团算法岗-计算机视觉(T-Star Lab)内推
人工智能·算法·计算机视觉·业界资讯
apcipot_rain1 小时前
【密码学——基础理论与应用】李子臣编著 第二章 古典密码 课后习题
算法·网络安全·密码学
New_Teen1 小时前
C++小课堂——变量的声明,赋值和初始化
开发语言·c++·笔记·学习
_Itachi__2 小时前
LeetCode 热题100 21. 合并两个有序链表
算法·leetcode·链表
北顾南栀倾寒3 小时前
[杂学笔记]迭代器的原理、进程与线程的区别、.vector的内存管理、vim的命令模式指令集合、多线程的最大问题、HTTP协议与HTPPS协议区别
开发语言·c++·笔记·http·vim
萌の鱼3 小时前
leetcode 240. 搜索二维矩阵 II
数据结构·c++·算法·leetcode·矩阵