【数据结构】:排序(一)

🌈个人主页@ꪔ小林Y

个人专栏《C++小白闯关日记》《C语言小白闯关日记》,《数据结构入门------从原理到实战》

🍀代码信条 :每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应

目录

排序的分类

1.插入排序

(一)直接插入排序

  • 基本思想
    把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
  • 代码实现
c 复制代码
//(1)直接插入排序
void InsertSort(int* arr, int n)
{
	for (int i = 0;i < n - 1;i++)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else {
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}
  • 测试一下
c 复制代码
#include"Sort.h"
void printArr(int* arr, int n)
{
	for (int i = 0;i < n;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test01()
{
	int a[] = {5,2,6,4,8,9,1,8};
	int n= sizeof(a) / sizeof(a[0]);
	printf("排序之前");
	printArr(a, n);
	//直接插入排序
	InsertSort(a, n);
	printf("排序之后");
	printArr(a, n);
}
int main()
{
	test01();
	return 0;
}
  • 运行结果

直接插入排序的时间复杂度为O(n^2) (最坏情况)

O(n^2)(最好情况)

(二)希尔排序(缩小增量法)

希尔排序是在直接插入排序的基础上改进而来,综合来讲效率是要高于直接插入排序的

  • 基本思想 :先选定一个整数(一般为gap=n/3+1 ),把待排序文件所有记录分成各组,所有距离相等的记录分在同一组内,并对每一组分开排序(直接插入排序),然后gap=gap/3+1 得到下一个整数,再将数组分成各组,进行插入排序。
    当gap=1时,就相当于直接插入排序。gap>1,是预排序

至于选定的整数为什么是gap=n/3+1 ,解释一下:

当n=6时,

  • 我们先选定gap=gap/2,则后面的划分为:3 ,1,0。最后一组为零是不行的
  • 在选定gap=gap/2,则后面的划分为:3,0。这也是不可行的
  • 当我们选定gap=gap/3+1:则后面的划分为:3,2,1。刚刚好,直接进行排序。
  • 图示:



  • 代码实现
c 复制代码
//(2) 希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
			for (int i = 0;i < n - gap;i ++)
			{
				int end = i;
				int tmp = arr[end + gap];
				while (end >= 0)
				{
					if (arr[end] > tmp)
					{
						arr[end + gap] = arr[end];
						end -= gap;
					}
					else {
						break;
					}
				}
				arr[end + gap] = tmp;
			}
	}
}
  • 测试一下:
c 复制代码
#include"Sort.h"
void printArr(int* arr, int n)
{
	for (int i = 0;i < n;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test01()
{
	int a[] = {5,2,6,4,8,9,1,8};
	int n= sizeof(a) / sizeof(a[0]);
	printf("排序之前");
	printArr(a, n);
	//希尔排序
	ShellSort(a, n);
	printf("排序之后");
	printArr(a, n);
}
int main()
{
	test01();
	return 0;
}
  • 运行结果:

希尔排序的时间复杂度为:n^(1.3)

2.选择排序

(一)堆排序

堆排序我们之前学到过,其实它是属于选择排序,这里就不再提了,忘记的小伙伴可以回去复习一下【堆排序】

(二)直接选择排序

  • 基本思想:选择排序的思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
  • 代码实现:
c 复制代码
#include"Sort.h"
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
//(1)直接选择排序
void SelectSort(int* arr, int n)
{
	for (int i = 0;i < n;i++)
	{
		int mini = i;
		for (int j = i + 1;j < n;j++)
		{
			if (arr[j] < arr[mini])
			{
				mini = j;
			}
		}
		Swap(&arr[mini], &arr[i]);
	}
}
  • 测试:
c 复制代码
#include"Sort.h"
void printArr(int* arr, int n)
{
	for (int i = 0;i < n;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test01()
{
	int a[] = {5,2,6,4,8,9,1,8};
	int n= sizeof(a) / sizeof(a[0]);
	printf("排序之前");
	printArr(a, n);
		//直接选择排序
	SelectSort(a, n);
	printf("排序之后");
	printArr(a, n);
}
int main()
{
	test01();
	return 0;
}
  • 运行结果:

直接选择排序的时间复杂度为O(n^2)

其实直接插入排序的这个代码还可以优化,但是时间复杂度不能再优化了:我们可以同时找最大值和最小值,然后进行交换。

优化后代码如下:
c 复制代码
//(1)直接选择排序
void SelectSort(int* arr, int n)
{	//优化后
	int begin = 0,end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		for (int i = begin+1;i <= end;i++)
		{
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
		}
		//注意maxi==begin时需要特殊处理
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&arr[begin], &arr[mini]);
		Swap(&arr[end], &arr[maxi]);
		begin++;
		end--;
	}
}
  • 图解:

  • 运行结果:

3.交换排序

(一)冒泡排序

因为之前的C语言学到过冒泡排序,所以数据结构这里就不再细讲了。
【冒泡排序】

  • 以下为优化后的冒泡排序代码演示:
c 复制代码
//交换函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
//交换排序
//(1)冒泡排序
void BubbleSort(int* arr, int n)
{
	int exchange = 0;
	for (int i = 0;i < n;i++)
	{
		for (int j = 0;j < n - i - 1;j++)
		{
			if (arr[j] > arr[j + 1])
			{
				exchange = 1;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (exchange = 0)
		{
			break;
		}
	}
}

(二)快速排序

递归版本的快速排序
  • 基本思想 :(基于二叉树结构的交换排序方法)任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到左右元素都排列在相应位置上为止
找基准值------hoare版本:

如何找基准值,并将基准值放到指定的位置上?

以下图示为hoare版本的找基准值方法

基准值找好之后:
right:从右往左走,找比基准值小的
left:从左往右走,找比基准值大的

  • 代码演示
c 复制代码
//找基准值
int _QuicSort(int* arr, int left, int right)
{
	int keyi = left;
	left++;
	while (left <= right)
	{
		//right:从右往左,找比基准值小的
		while (left<=right&&arr[right] > arr[keyi])
		{
		//内层while循环不能等于,数组数据重复的情况下会造成时间复杂度变为n^2
			--right;
		}
		//left:从左往右,找比基准值大的
		while (left <= right&&arr[left] <arr[keyi])
		{
			++left;
		}
		//交换left和right
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--])
			//left和right交换完要++,--,否则死循环
		}
	}
	//right的位置就是基准值的位置
	Swap(&arr[keyi], &arr[right]);
	return right;
}
//(2)快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left > right)
	{
		return;
	}
	//找基准值keyi
	int keyi = _QuicSort(arr, left, right);
	//根据基准值将数组一分为二
	//左序列[left,基准值-1]  右序列[基准值+1,right]
	QuickSort(arr,left,keyi-1 );
	QuickSort(arr,keyi+1,right);
}
  • 测试一下:
c 复制代码
void test01()
{
	int a[] = {6,1,2,7,9,3};
	int n= sizeof(a) / sizeof(a[0]);
	printf("排序之前");
	printArr(a, n);
	//快速排序
	QuickSort(a, 0, n- 1);
	printf("排序之后");
	printArr(a, n);
}
int main()
{
	test01();
	return 0;
}
  • 运行结果

递归的时间复杂度=单次递归时间复杂度*递归次数

快速排序的时间复杂度为:O(nlogn)

找基准值------挖坑法:
  • 思路:创建左右指针,首先从右向左找出比基准值小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准值大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分解值放入当前的"坑"中,返回当前坑下标就是基准值的位置
  • 图示:
  • 代码实现:
c 复制代码
//找基准值-挖坑法版本
int _QuicSort1(int* arr, int left, int right)
{
	int hole = left;
	int key = arr[hole];
	while (left<right)
	{
		while (arr[right] > key)
		{
			right--;
		}
		arr[hole] = arr[right];
		hole = right;
		while (arr[left] < key)
		{
			++left;
		}
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = key;
	return hole;
}
找基准值------lomuto前后指针法
  • 思路:创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边
  • 图示:
  • 代码实现:
c 复制代码
//找基准值-lomuto双指针指针法
int _QuicSort2(int* arr, int left, int right)
{
	int prev = left, cur = prev + 1;
	int keyi = left;
	while (cur <= right)
	{
		//cur数据和基准值比较
		if (arr[cur] < arr[keyi]&&++prev!=cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		++cur;
	}
	Swap(&arr[keyi], &arr[prev]);
	return prev;
}
非递归版本的快速排序

非递归版本的快速排序借助数据结构------栈。

排序完成,栈为空,如下图:

  • 代码实现:
    这里会用到栈的部分代码,忘记的可以回看一下这节哦:数据结构------栈
c 复制代码
#include"Sort.h"
#include"Stack.h"
//非递归版本的快速排序--借助数据结构------栈
void QuickSortNorR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);
	while (!STEmpty(&st))
	{
		//取栈顶两次
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);
		//在[begin,end]范围内用lomuto双指针法找基准值
		int keyi = begin;
		int prev = begin, cur = prev + 1;
		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
			{
				Swap(&arr[cur], &arr[prev]);
			}
			++cur;
		}
		Swap(&arr[keyi], &arr[prev]);
		keyi = prev;
		//左序列[begin,keyi-1]
		//右序列[keyi+1,end]
		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}

	}
	STDesTroy(&st);
}
  • 测试:
c 复制代码
void test01()
{
	int a[] = {6,1,2,7,9,3};
	int n= sizeof(a) / sizeof(a[0]);
	printf("排序之前");
	printArr(a, n);
	//非递归版本的快速排序
	QuickSortNorR(a, 0,n - 1);
	printf("排序之后");
	printArr(a, n);
}
int main()
{
	test01();
	return 0;
}
  • 运行:

🎊本期数据结构------排序(一)的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流------ 技术路上多碰撞,才能更快进步

觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习。下期不见不散✌️

相关推荐
Chat_zhanggong3451 小时前
K4A8G165WC-BITD产品推荐
人工智能·嵌入式硬件·算法
百***48072 小时前
【Golang】slice切片
开发语言·算法·golang
墨染点香2 小时前
LeetCode 刷题【172. 阶乘后的零】
算法·leetcode·职场和发展
做怪小疯子2 小时前
LeetCode 热题 100——链表——反转链表
算法·leetcode·链表
linweidong2 小时前
4399 Go开发面试题及参考答案(下)
排序算法·http状态码·消息推送·topk·go并发·tcp握手·并发模型
做怪小疯子4 小时前
LeetCode 热题 100——矩阵——旋转图像
算法·leetcode·矩阵
努力学习的小廉4 小时前
我爱学算法之—— BFS之最短路径问题
算法·宽度优先
高山上有一只小老虎4 小时前
构造A+B
java·算法
木头左5 小时前
缺失值插补策略比较线性回归vs.相邻填充在LSTM输入层的性能差异分析
算法·线性回归·lstm