排序算法的C++实现

排序算法比较

排序方法时间复杂度(最好)时间复杂度(最坏)时间复杂度(平均)空间复杂度稳定性
冒泡排序O(n)O(n2)O(n2)O(1)稳定
选择排序O(n)O(n2)O(n2)O(1)不稳定
插入排序O(n)O(n2)O(n1.3)O(1)稳定
希尔排序O(n)O(n2)O(n2)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(n2)O(nlogn)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
计数排序O(n+k)O(n+k)O(n+k)O(n+k)稳定
桶排序O(n)O(n2)O(n+k)O(n+k)稳定
基数排序O(n+k)O(n+k)O(n+k)O(n+k)稳定

稳定性:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,则具备稳定性。

1. 冒泡排序

在这里插入图片描述

2. 选择排序

在这里插入图片描述

原理:在已给的序列中选择最大(最小)的数放到末尾,再从剩余元素中选择最大的,放到未排序的序列末尾,重复这个过程。直到全部排序完成。

选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。比如 5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。

3. 插入排序

在这里插入图片描述
原理:对于未排序序列,默认首元素是有序的,从后往前扫描已排序数据,将未排序元素依次插入到已经有序序列的对应位置。

4. 希尔排序

在这里插入图片描述
原理:希尔排序是将待排序的序列分成若干待排序的子序列,分别进行插入排序。关键在于间隔序列的设定。希尔排序是第一个突破O(n2)的排序算法。
在这里插入图片描述

5. 归并排序

在这里插入图片描述
原理:如果要排序一个数组,先将数组从中间分成前后两部分,对前后两部分分别排序,再将排好序的两部分合并在一起。
在这里插入图片描述

归并排序时间复杂度分析
对n个数组进行归并的时间复杂度是T(n),分解成两个子数组进行排序的时间复杂的是T(n/2),合并有序数组的操作时间复杂度是O(n)。所以归并排序的时间复杂度就是:

T(n) = 2T(n/2) + n
= 2
(2T(n/4) + n/2) + n = 4T(n/4) + 2n
= 4
(2T(n/8) + n/4) + 2n = 8T(n/8) + 3n

= 2^k * T(n/2^k) + k * n

n/2^k 表示分解后的数据规模, k表示把数据规模分解到1时的分解次数,当算法完成,数据规模分解到1,此时n/2^k=1,k=log2n。将k带回原公式,可以计算得到T(n)=Cn+nlog2n,时间复杂度就是O(nlogn)。

6. 快速排序

在这里插入图片描述
原理:在数列中挑选一个数,称为基准(pivot),将比这个数小的元素放在左半边,其余元素放在右半边。递归地选择基准,并对两边的元素进行排序。

代码实现
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

//冒泡排序基础
void bubbleSort1(vector<int> &q)
{
	for (int i = q.size() - 1; i > 0; i--)
	{ 
		for (int j = 0; j < i; j++)
		{
			if (q[j] > q[j + 1])
			{
				swap(q[j], q[j + 1]);
			}
		}
	}
}
//冒泡排序改进
void bubbleSort2(vector<int>& q)
{
	for (int i = q.size() - 1; i > 0; i--)
	{
		bool flag = false;
		for (int j = 0; j < i; j++)
		{
			if (q[j] > q[j + 1])
			{
				swap(q[j], q[j + 1]);
				flag = true;
			}
		}
		//若内层循环没有进行一次交换说明已经有序,直接break
		if (!flag) break;
	}
}

//选择排序
void selectSort(vector<int>& q)
{
	for (int i = 0; i < q.size(); i++)
	{
		//int max = q[i];
		for (int j = i + 1; j < q.size(); j++)
		{
			if (q[i] > q[j])
			{
				swap(q[i], q[j]);
			}
		}
	}
}

//插入排序
void insertSort(vector<int>& q)
{
	for (int i = 1; i < q.size(); i++)
	{
		int t = q[i], j;
		for (j = i - 1; j >= 0; j--)
		{
			if (q[j] > t)
			{
				q[j + 1] = q[j];
			}
			else
				break;
		}
		q[j + 1] = t;

	}
}

//归并排序
//l,r分别代表区间的左右
void merges_sort(vector<int>& q, int l, int r)
{
	if (l >= r) return; //只有一个元素

	int mid = (l + r) / 2;

	merges_sort(q, l, mid);  //左边做归并
	merges_sort(q, mid + 1, r); //右边做归并

	//归并操作
	static vector<int> w; //辅助数组
	w.clear();

	int i = l, j = mid + 1;//两个指针指向两个序列的起点
	while (i <= mid && j <= r)
	{
		//判断当前两个指针指向的数哪个小
		if (q[i] < q[j])
		{
			w.push_back(q[i++]);
		}
		else
		{
			w.push_back(q[j++]);
		}
	}
	while (i <= mid) w.push_back(q[i++]);
	while (j <= r) w.push_back(q[j++]);

	for (int i = l, j = 0; j < w.size(); i++, j++)
	{
		q[i] = w[j];
	}
}

//快速排序
void quick_sort(vector<int>& q, int l, int r)
{
	if (l >= r) return;
	int i = l - 1, j = r + 1, x = q[l + r >> 1];// x:基准,可以写成随机
	while (i < j)
	{
		do j--; while (q[j] > x); //大于x在右边
		do i++; while (q[i] < x); //小于x在左边
		if (i < j) swap(q[i], q[j]);
	}
	quick_sort(q, l, j), quick_sort(q, j + 1, r);
}


int main()
{
	int n;
	vector<int>q;
	
	cin >> n;
	for (int i = 0, t; i < n; i++)
	{
		cin >> t;
		q.push_back(t);
	}

	//bubbleSort2(q);
	//selectSort(q);
	
	//insertSort(q);
	//merges_sort(q, 0, q.size() - 1);
	quick_sort(q, 0, q.size() - 1);
	for (auto x : q) cout << x <<' ';
	cout << endl;

	return 0;
}

7. 堆排序

在这里插入图片描述

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

代码实现
//堆的两种操作:(此处实现的是大根堆)
//1. 自顶向下
void push_down(vector<int>& heap, int size, int u) // u:当前节点
{
	//left:当前节点u的左子节点, right:当前节点u的右子节点
	int t = u, left = 2 * u, right = 2 * u + 1; 
	if (left <= size && heap[left] > heap[t]) t = left;
	if (right <= size && heap[right] > heap[t]) t = right;
	if (t != u) //当存在左或右子节点大于当前节点
	{
		swap(heap[u], heap[t]);
		push_down(heap, size, t);
	}
}

//2. 自下而上
void push_up(vector<int>& heap, int u)
{
	while (u / 2 && heap[u / 2] < heap[u]) //父节点的值小于当前节点
	{
		swap(heap[u / 2], heap[u]);
		u /= 2;
	}
}

//堆排序

void heap_sort(vector<int>& q, int n)
{
	int size = n;
	//建堆
	for (int i = 1; i <= n; i++)
	{
		push_up(q, i);
	}

	for (int i = 1; i <= n; i++)
	{
		swap(q[1], q[size]); //将最大值和最后一个元素交换位置
		size--;
		push_down(q, size, 1);
	}
}

8.计数排序

在这里插入图片描述
说明:计数排序是桶排序的一种特殊情况。当要排序的数据范围并不大,比如n个数据,只有k种数字,就可以将数据划分为k个桶,每个桶内数据相同,即可省掉桶内数据排序时间。
计数排序只能用在数据范围不大的场景中。

代码实现
//计数排序
void couting_sort(vector<int>& q, int n)
{
	vector<int> cnt(101, 0); //创建101个数,每个数初始化成0
	//统计一下每种数出现多少个
	for (int i = 1; i < n; i++)
	{
		cnt[q[i]]++;
	}
	for (int i = 1, k = 1; i <= 100; i++)//排序范围1-100
	{
		while (cnt[i])  //只要当前数还没有用完
		{
			q[k++] = i; //每有一个i,下标后移一位
			cnt[i]--;  //每写一个数,减少一次
		}
	}
}

9. 桶排序

在这里插入图片描述
原理:划分成若干个桶,先把每个数映射到各个桶里面去,各个桶里面再做排序。桶内的排序方式可以随意选择。桶间有序,桶内无序。

桶排序的极端情况就是每个数就是一个桶,这样就退化成了计数排序。
另外一种比较特殊的划分桶的方式就是按照每一位来划分,这样就是基数排序。

10. 基数排序

在这里插入图片描述
原理:先按照低位进行排序,再按照高位进行排序,直到进行到最高位。也有按照优先级顺序进行排序。

代码实现
//基数排序

//辅助函数,表示取当前数的第几位
int get(int x, int i) //x:当前数字,i:表示要取第几位
{
	while (i--)
	{
		x /= 10;
	}
	return x % 10;
}

void radix_sort(vector<int>& q, int n)
{
	vector<vector<int>>cnt(10); //定义10个桶

	//假设在三位数以内的数字进行排序
	for (int i = 0; i < 3; i++) //循环3次,第一次按个位,第二次按十位,第三次按百位
	{
		//循环之前,将每个桶的数据清空
		for (int j = 0; j < 10; j++)
		{
			cnt[j].clear();
		}
		//按照个位排序
		for (int j = 1; j <= n; j++)
		{
			//将第i位数字,依次放到桶里
			cnt[get(q[j], i)].push_back(q[j]);
		}

		//循环所有不同的数值。每次看一位数,数值只有0到9这10个
		for (int j =0, k = 1; j < 10; j++) //k:代表前面有多少个数,就是当前要放第几个数
		{
			for (int x : cnt[j])
			{
				q[k++] = x;
			}
		}
	}
}
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值