典型排序算法(冒泡,选择,直接插入,快速排序,归并排序)-自我总结要点+C++代码解释

前言

这次复习数据结构和C++,总结了常见的几种排序算法,虽然C++中不需要自己实现排序算法,但熟悉各种排序算法的核心思想还是重要的,同时也方便自己将来复习回顾。

注意:自己是在一个项目里实现的,因此用的vector作为保存元素的容器,不过不影响大局,与数组同理

冒泡排序

核心思想:
从左到右遍历,相邻两个元素相互比较,然后按符合升序(降序)来决定是否交换两个元素,即在一轮次遍历中找到一个最大的放到最后位置,下一次找第二大的放置在倒数第二个位置上。
即是一个二重循环,外层循环表示进行多少轮的比较(一轮找出一个最大的),内层循环就在这一轮的比较中不断的比较以及交换相邻的两个元素。

优化方法:

  1. 每一轮找出一个最大的放在末尾,在之后的比较中就不需要再比较这个元素了,可以减少内层循环的比较次数
  2. 注意到,当交换发生,说明还没有达到有序;如果有一轮遍历过程中没有发生交换,说明已经有序,可以提前退出,不需要再继续进行循环比较的过程

代码

void buble_sort(vector<int>& a)
{
	auto len = a.size();
	for (size_t i = 0; i < len-1; ++i) 				//n个数只需要比较n-1次即可  也可从下面数组越界a[j+1]来考虑
		{
			for (int j = 0; j < len - i-1; ++j) 	       //第一个优化方法 不需要比较后面的已经排序的数
			{
				if (a[j] > a[j + 1])
					swap(a[j], a[j + 1]);                 
			}
		}
}

第二种优化方法的代码:

void buble_sort(vector<int>& a)			         	//主要增加flag,其余大致相同
{
	bool flag = false;				        		 //主要增加的flag标识 用来判断是否有序
	while (!flag)				             		 //无序,进入算法
	{
		flag = true; 								 //先假定已有序
		for (size_t i = 1; i < len ; ++i)             
		{
				if (result[i-1] > result[i])
				{
					swap(result[i], result[i - 1]);
					flag = false;					  //若发生交换则说明是无序的 ,重置flag
				}
		}
		len--;
	}
}

选择排序

核心思想:
每一趟在未排序的记录中选取最小的记录作为有序序列中第i(i递增)个记录
即第一轮选出最小的元素,然后和a[0](第一个位置)交换,成为第一个有序元素,再在剩余的元素中选出最小的,和a[1]交换(第二个位置),再在剩余的数中重复上述过程。

代码核心:
在每一轮次中找出最小元素的位置,然后交换元素

代码:

void select_sort(vector<int>& a)
{
	auto len = a.size();
	for (size_t i = 0; i < len - 1; ++i)                           //每一轮次 轮次数
		{
			size_t min = i;
			for (size_t j = i; j < len ; ++j)                      //找出最小元素的位置  从i开始即可 前面的是已经选择的最小的
			{
				if (a[j] < a[min])
				{
					min = j;
				}
			}
			swap(a[min], a[i]);                                    //找到最小位置 和第i个元素交换
		}
}

直接插入排序

核心思想:
初始化为空的有序元素序列,然后依次插入每一个元素,插入时就按照关键值大小(本算法即为数值的大小)插入相应的位置中

代码核心:
序列分为有序和无序两部分,取第一个元素作为初始有序区,然后第二个开始,依次插入到有序区的合适位置,直到排好序

代码

void insert_sort(vector<int>& a)
{
	auto len = a.size();
		for (size_t i = 1; i < len; ++i)                 //从第二个数字(无序区)开始遍历,相当于第一个数作为初始有序区 
		{
			for (size_t j = i; j > 0; --j)               //从无序区的选出一个数  然后插入到有序区(不停的比较换序)
			{
				if (a[j] < a[j - 1])
					swap(a[j], a[j - 1]);
			}
		}
}

快速排序

核心思想:
递归 分治
在一个序列中选出一个基准数,以基准元素把序列分成两部分(小于该元素和大于该元素的)(确定了该元素的最终位置),对着两部分再进行递归调用快排算法。

代码核心:
就是找到一个基准(轴值),并将元素序列依据基准分为大小两个部分,找到基准所在的位置

找到一篇比较好的博客 学习下(挖坑填数+分治法):博客

其主要方法:
对挖坑填数进行总结
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

自己总结
实际上就是前后换位置,目的是大于基准的数都在后面,后面的坑要大数来填(从前遍历找大数),前面的坑要小数来填(从后遍历找小数)
最后就是i=j时 ,j后面的全都大于基准,i前面的全部小于基准

代码(注释很详细)

void quick_sort(vector<int>& a, int left, int right)
{
	if (left >= right) { return; }                        //判断排序个数是否大于1 否则直接退出排序
	if (left < right)
	{
		size_t i = left, j = right;  
		int X = a[i];                                     //将第一个数作为基准
		while(i<j)
		{
			while (i < j&&a[j] >= X)                      //从后面开始遍历 大于X则不管 继续
			{
				--j;
			}
			if (i < j)                                    //出现了a[j]<X 即有一个小于X的数出现了
			{
				a[i++] = a[j];                            //用这个小于X的数去占坑a[i] 同时i++ 此时j的位置 需要一个大于X的数来占坑
			}
			
			while (i < j&&a[i] < X)                       //现在需要有一个大于X的数 从i开始遍历 小于则不管 继续
			{
				++i;
			}
			if (i < j)                                    //出现了a[i]>X ,即有一个大于X的数出现了
			{
				a[j--] = a[i];                            //用这个大于X的数去占刚才空出的 j 的位置的坑 同时j-- 此时又需要小于X的数去填占刚才i的坑 ,如此往复
			}
		}
		a[i] = X;                                         //while(i<j) 的循环出来的时候 就是i=j时 此时j后面的数 全都大于X i前面的数全部小于X 完成任务

		//以上完成找基准 按基准前后分割 确定基准的位置
		quick_sort(a, left, i - 1);                       //递归调用即可
		quick_sort(a, i + 1, right);
	}
}

归并排序

核心思想:
分治+递归
将一个序列分成两个子序列,(两个子序列再不断递归调用(分成更小的子序列再合并)算法),子序列排序完成后,再合并两个有序子序列

他人的照片

代码核心:
重点是合并操作(合并两个有序序列),分的步骤就是不停的递归调用(每次都是二分之一部分)来分,直到最后每一个子序列只有一个元素(自然就有序了),然后不停的合并。

代码:

void merge_sort(vector<int> &a, size_t first, size_t last, vector<int>& tmp)            
{
	if (first < last) 
	{
		size_t mid = (first + last) / 2;                            //二分之一分
		merge_sort(a, first, mid, tmp);		                      //前半部分递归调用  tmp相当于一个暂时中转的容器
		merge_sort(a, mid + 1, last, tmp);                         //后半部分
		merge(a, first, mid, last, tmp);                           //最为关键的合并部分
	}
}


void merge(vector<int>&a, size_t first, size_t mid, size_t last, vector<int>& tmp)        //合并操作 将两个有序子序列合并成一个
{
	size_t i = first, j = mid + 1,k=0;
	while (i <= mid&&j <= last)
	{
		if (a[i] <= a[j])                             //选择小的元素并入tmp
		{
			tmp[k++] = a[i++];
		}
		else
		{
			tmp[k++] = a[j++];
		}
	}                                                 //跳出循环时说明 有一个子序列遍历完成 只需要将另外一个续到tmp上即可         
	while (i <= mid)
	{
		tmp[k++] = a[i++];
	}
	while (j <= last)
	{
		tmp[k++] = a[j++];
	}
	for (size_t i = 0; i < k; ++i)                     //最后结果在tmp中 转移到a  
	{
		a[first + i] = tmp[i];
	}

}

总结

这次总结了常用到的几种排序算法,参考各种博文,自己总结了算法,代码核心要点以及具体的C++实现
简单排序:冒泡排序,选择排序,直接插入排序
高级排序:快速排序,归并排序

待补充:

  1. 还有希尔排序以及堆排序没有总结实现
  2. 7种算法都实现后,要放在一起进行比较总结,涉及时间复杂度分析,稳定性分析,应用场景分析
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值