数据结构与算法:排序

p.s.自用

p.ss.基于C语言

目录

基于插入

直接插入排序算法

希尔(shell)排序算法 / 缩小增量排序

基于交换

冒泡排序算法

 快速排序

基于选择

简单选择排序算法

堆排序算法

堆的应用

其他

归并排序【外部排序】

基于计数的排序

计数排序(以空间换时间)

桶排序(以空间换时间)

基数排序


*相关概念*

就地排序:在排序的过程中,只使用到了存储数据的空间,没有使用其他额外空间
非接地排序:在排序的过程中,使用了其他额外空间
->空间复杂度

内部排序:待排序的数据一次性放到内存中
外部排序:待排序的数据不能一次性放到内存中

稳定排序:排序前后,相同数据的相对位置没有发生变化
不稳定排序:排序前后,相同数据的相对位置发生了变化

p.s.所有排序默认从1位置开始

基于插入

直接插入排序算法

思路:在添加新的数据时,使用顺序查找的方式找到该数据要插入的位置

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

类别:就地排序 & 内部排序 & 稳定排序

//直接插入排序
for (int i = 2; i <= n; i++)//由于第一个数据不用排序,可直接从第二个数据开始
{
	int t = a[i];//保存待排序的数据
	int ind = i;//下标
	//有序区:1 到 i-1
	for (int j = 1; j < i; j++)
	{
		if (a[j] > t)
		{
			ind = j;
			break;
		}
	}
	for (int j = i; j > ind; j--)
		a[j] = a[j - 1];
	a[ind] = t;
}
//直接插入排序-改良版
for (int i = 2; i <= n; i++)//由于第一个数据不用排序,可直接从第二个数据开始
{
	int t = a[i];//保存待排序的数据
	int j;
	for (j = i; j > 0; j--)
	{
		if (t < a[j - 1])
			a[j] = a[j - 1];
		else
			break;
	}
	a[j] = t;
}

优化方案: 折半插入排序、2-路插入排序

希尔(shell)排序算法 / 缩小增量排序

思路:在直接插入排序算法的基础上,对待排序的数据进行分组,先对每组进行排序,然后不断缩小组数,不断排序,最终缩小为1组

时间复杂度:与增量序列的选择有关,最坏时间复杂度是O(n^2)

类别:就地排序 & 内部排序 & 不稳定排序

//希尔插入排序
int k = 0;
for (int d = n / 2; d >= 1; d = d / 2)//分组数
{
	k++;//计算趟数
	for (int i = 1 + d; i <= n; i++)//以增加d分组,对每组进行直接插入排序
	{
		int t = a[i];//保存待排序的数据
		int j;
		for (j = i - d; j >= 1; j = j - d)
		{
			if (t < a[j])
				a[j + d] = a[j];
			else
				break;
		}
		a[j + d] = t;
	}
}

基于交换

交换:根据数据之间的比较,把每个数据都放在应在的位置

冒泡排序算法

思路:通过不断比较两个相邻元素,若这两个元素乱序,则交换位置,从而实现每趟都把最大的数据交换到最后

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

类别:就地排序 & 内部排序 & 稳定排序

//冒泡排序
for (int i = 1; i <= n - 1; i++)//循环次数
{
	for (int j = 1; j <= n - i; j++)
	{
		if (a[j] > a[j + 1])
		{
			int t = a[j];
			a[j] = a[j + 1];
			a[j + 1] = t;
		}
	}
}
//冒泡排序-优化
for (int i = 1; i <= n - 1; i++)//循环次数
{
	int flag = 0;
	for (int j = 1; j <= n - i; j++)
	{
		if (a[j] > a[j + 1])
		{
			flag = 1;
			int t = a[j];
			a[j] = a[j + 1];
			a[j + 1] = t;
		}
	}
	if (!flag)//表示已排序完成
		break;
}
 快速排序

思路:基于递归

首先选定一个基准数x作为比较的标准,把比x小的数据放在x前面,比x大的数据放在x后面;之后x把序列分成了两部分,这两部分都是乱序的,再分别快排;
基准数的选择:第一个数、最后一个数、中间位置的数

时间复杂度:O(nlogn)

类别:就地排序 & 内部排序 & 不稳定排序

//快速排序-以数组第一个数为基准数
void Quick_sort(int a[], int l, int r)
{
	if (l < r)
	{
		int i, j, x;
		i = l; j = r;
		x = a[l];
		while (i < j)
		{
			while (i < j && a[j] > x)
				j--;
			if (i < j)
			{
				a[i] = a[j];
				i++;
			}
			while (i < j && a[i] < x)
				i++;
			if (i < j)
			{
				a[j] = a[i];
				j--;
			}
		}
		a[i] = x;
		Quick_sort(a, l, i - 1);
		Quick_sort(a, i + 1, r);
	}
}

基于选择

选择:每次选出一个合法的数据放在其应在的位置

简单选择排序算法

思路:每趟从待排序区中选择一个最小的数,放到待排序区的第一个位置,从而实现升序排列。

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

类别:就地排序 & 内部排序 & 不稳定排序

//简单选择排序
for (int i = 1; i < n; i++)
{
	int minn = i;
	for (int j = i + 1; j <= n; j++)
	{
		if (a[j] < a[minn])
			minn = j;
	}
	int t = a[minn];
	a[minn] = a[i];
	a[i] = t;
}
堆排序算法

时间复杂度:O(nlogn)

类别:就地排序 & 内部排序 & 不稳定排序

思路:

1.建堆:用一个数组保存
1)自我初始化:在原数组的基础上进行调整
从子树入手,由小及大调整每颗子树。
对于每颗子树,向下调整;让根节点和其左右孩子比较,最小值和根节点交换,继续向下调整子树

void downAdjust(int a[], int i, int n)
{
	int parent = i;
	int child = 2 * i;
	while (parent * 2 <= n)
	{
		child = 2 * parent;
		if (child + 1 <= n && a[child + 1] < a[child])//查找最小的孩子
			child = child + 1;
		if (a[parent] < a[child])//父亲比最小孩子小
			break;
		else
		{
			int t = a[parent];
			a[parent] = a[child];
			a[child] = t;
			parent = child;
		}
	}
}

2)通过插入建堆
每插入一个数据,就调整一次。新插入的数据放在最后,若比父亲大/是根节点就不调整,否则就向上调整

void upAdjust(int a[], int i)
{
	int child = i;
	int parent = child / 2;
	while (child > 1)
	{
		parent = child / 2;
		if (a[parent] <= a[child])
			break;
		else
		{
			int t = a[parent];
			a[parent] = a[child];
			a[child] = t;
			child = parent;
		}
	}
}

2.循环n次,每次输出最小的数
输出最小的数 根节点a[1]
删掉a[1] -> 让堆中最后一个节点替换a[1],然后重新对a[1]向下调整

//1.1)自我初始化的参考主函数
int main()
{
	int a[105] = { 0 };
	int n;//待排序的数据个数
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	//堆排列
	//1.建堆
	//1.1)自我初始化
	for (int i = n / 2; i >= 1; i--)//枚举
		downAdjust(a, i, n);
	//2.循环n次,每次输出最小的数
	int dsize = n;
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", a[1]);
		a[1] = a[dsize--];
		downAdjust(a, 1, dsize);//调整
	}
}
//1.2)插入建堆的参考主函数
int main()
{
	int a[105] = { 0 };
	int n;//待排序的数据个数
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		//1.2)插入建堆
		upAdjust(a, i);
	}
	//2.循环n次,每次输出最小的数
	int dsize = n;
	for (int i = 1; i <= n; i++)
	{
		printf("%d ", a[1]);
		a[1] = a[dsize--];
		downAdjust(a, 1, dsize);//调整
	}
}
堆的应用

1. 优先队列 -> 堆

2.输入一亿个数,选出前100个最大的输出?要考虑空间和时间复杂度 -> 小顶堆
    先读入100个数,建立小顶堆
    然后,当读入一个数x,
        若x<=a[1],直接读入下一个数
        若x>a[1],a[1]=x,向下调整为小顶堆
    最后,小顶堆中的数据就是答案

其他

归并排序【外部排序】

思路:先分到最小,再排序合并

时间复杂度:O(nlogn)

类别:非就地排序 & 外部排序 & 稳定排序

//二路归并
void merge(int a[], int l, int mid, int r)//在合并过程中把顺序排好
{
	int i = l;
	int j = mid + 1;
	int t[105] = { 0 };
	int k = 0;//t数组的下标
	while (i <= mid && j <= r)
	{
		if (a[i] <= a[j])
		{
			t[k] = a[i];
			k++;
			i++;
		}
		else
		{
			t[k] = a[j];
			k++;
			j++;
		}
	}
	while (i <= mid)
	{
		t[k] = a[i];
		k++;
		i++;
	}
	while (j <= r)
	{
		t[k] = a[j];
		k++;
		j++;
	}
	for (int g = 0; g < k; g++)
		a[l + g] = t[g];
}
void mergeSort(int a[], int l, int r)//把数组a的[l,r]从中间位置分为两部分
{
	if (l < r)
	{
		int mid = (l + r) / 2;
		//分割l-mid
		mergeSort(a, l, mid);
		//分割mid+1-r
		mergeSort(a, mid + 1, r);
		//合
		merge(a, l, mid, r);
	}
}

int main()
{
	int a[105] = { 0 };
	int n;//待排序的数据个数
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	//归并排序
	mergeSort(a, 1, n);
	//输出
	for (int i = 1; i <= n; i++)
		printf("%d ", a[i]);
}
基于计数的排序
计数排序(以空间换时间)

统计一下每个数出现的次数,然后直接按次数输出即可

类别:非就地排序 & 内部排序 & 不稳定排序

缺点:
a.无法对负整数/小数进行排序;【可优化】
b.及其浪费空间;
c.是一个不稳定排序;【可优化】

桶排序(以空间换时间)

将各数据根据规则分别放入桶内,再在各个桶内进行排序

基数排序

从数据的最低有效位到最高有效位 逐位 比较(先补齐位数:在较小数据前加0)

类别:非就地排序 & 内部排序 & 稳定排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值