【秋招Learning_note】| 拿捏排序算法!

引言

排序算法是计算机科学中的一个基本且重要的领域,用于将一组数据元素按照一定的顺序(如升序或降序)重新排列。接下来通过这篇文章整理出了几个重要排序算法的知识点和考点。

插入排序

首先介绍的是插入排序算法,插入排序在我们生活中是最常见的,是大家最熟悉的一种排序算法,比如说,大家在打牌或者打麻将等等情况下大多数都是使用这种算法,将扑克牌分成两组:已排序的扑克牌和未排序的扑克牌。然后,从未排序的组中挑选一张扑克牌,并将其放在已排序组中的正确位置。

通过动画演示将数组{6,5,3,1,8,7,2,4}使用插入排序算法,实现将数组元素从小到大排序。
在这里插入图片描述

插入排序动画演示

插入排序算法步骤描述
1、从第一个元素开始,可以认为这个元素以及被排序好了;
2、取出下一个元素(a),在前面已经完成排序的元素序列中从后往前扫描;
3、如果当前的元素(b)大于新元素(a),就把该元素(b)移动到下一个位置; 重复步骤3,直到找到已排序序列中元素小于或等于新元素(a)的位置;
4、将新元素(a)插入到该位置;
重复步骤2~4。
最终完成排序
在动画演示中,红色元素就相当于是a。

假设现在有一个整数数据对象为{8,2,10,7,6},通过下图来理解每一步的实现
请添加图片描述

插入算法代码实现

代码实现

#include<iostream>
#include<vector>
using namespace std;

void Insert_sorting(vector<int>&nums)
{
	int n = nums.size();
	// 直接认为第一个元素是一个有序序列 所以从第二个元素开始
	for (int i = 1;i < n;i++)
	{
		int temp = nums[i]; 
		int j;
		for (j = i - 1;j >= 0 && temp < nums[j];j--)
		{
			nums[j + 1] = nums[j];
		}
		nums[j+1] = temp;
	}
}
void Output(vector<int>num)
{
	int n = num.size();
	for (int i = 0;i < n;i++)
	{
		cout << num[i] << "  ";
	}
}
int main()
{
	vector<int> nums = { 8,6,5,2,3,4,7,9,1 }; // 要排序的对象
	Insert_sorting(nums);
	Output(nums);
	
	return 0;
}

插入排序的时间复杂度
最佳情况: O(n),如果列表已经排序,其中 n 是列表中元素的数量。
平均情况: O( n 2 n^{2} n2 ),如果列表是随机排序的。
最坏情况: O( n 2 n^{2} n2 ),如果列表是反向顺序的。

插入排序的空间复杂度
因为这个算法不需要使用额外的对象空间,所以空间复杂度为O(1)

插入排序的优点
1、易于实现,容易理解。
2、对于小列标和近似排序的对象使用起来比较高效。
3、节省空间。

插入排序的缺点
1、对于元素多的大列表来说比较低效。

选择排序

选择排序算法是一个简单而有效的排序算法,算法思想是: 反复从列表的未排序部分中选择最小(或最大)元素,并将其与未排序部分的第一个元素交换。对剩余的未排序部分重复此过程,直到整个列表排序完毕。

通过动画演示将数组{8,5,2,6,9,3,1,4,0,7}使用选择排序算法,实现将数组元素从小到大排序。请添加图片描述

选择排序代码实现

代码实现

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

// 将大的放在右边
void SelectSort_right(vector<int>& nums)
{
	int n = nums.size();
	for (int i = n - 1;i >= 0;i--)
	{
		int element_max = 0;
		int index = 0;
		for (int j = 0;j < i;j++)
		{
			if (nums[j] > element_max)
			{
				element_max = nums[j];
				index = j;
			}
		}
		int temp = nums[i];
		nums[i] = nums[index];
		nums[index] = temp;
	}
}
// 将小的放在左边
void SelectSort_left(vector<int>& nums)
{
	int n = nums.size();
	for (int i = 0;i < n;i++)
	{
		int element_min = INT_MAX;
		int index = 0;
		for (int j = n - 1;j >= i;j--)
		{
			if (nums[j] < element_min)
			{
				element_min = nums[j];
				index = j;
			}
		}
		int temp = nums[i];
		nums[i] = nums[index];
		nums[index] = temp;
	}
}
void Output(vector<int>& nums)
{
	int n = nums.size();
	for (int i = 0;i < n;i++)
	{
		cout << nums[i] << "  ";
	}
}
int main()
{
	vector<int>nums1 = { 24,5,7,8,154,816,891,24 };
	vector<int>nums2 = { 24,5,7,8,154,816,891,24 };
	cout << "SelectSort_right(nums1):" << endl;
	SelectSort_right(nums1);
	Output(nums1);
	cout << endl;
	cout << "SelectSort_left(nums1):" << endl;
	SelectSort_left(nums2);
	Output(nums2);
	return 0;
}

选择排序的时间复杂度分析
因为需要由两个嵌套循环实现的,所以选择排序算法的时间复杂度为O( n 2 n^{2} n2);

  • 一次循环逐个选择数组元素 = O(N)
  • 另一个循环将该元素与每个其他数组元素进行比较 = O(N)
  • 因此总复杂度 = O(N) * O(N) = O(N*N) = O( N 2 N^{2} N2 )

选择排序的空间复杂度
在算法的时间中没有使用额外的内存空间,所以空间复杂度为O(1)。

优点
容易理解,适用于小型数据集
缺点
在大型数据集中效果不好

冒泡排序

冒泡排序可以算是最简单的排序算法了,工作原理就是: 如果相邻元素的顺序是错误的,就交换他们两个元素的位置,由于它们的平均和最坏情况下的时间复杂度都是O( N 2 N^{2} N2 ),所以不适用于较多元素的对象。

使用冒泡排序实现将数组对象按照升序排序。
请添加图片描述
冒泡排序算法步骤
从左侧遍历并比较相邻元素,较大的元素放在右侧。 这样,最大元素首先被移动到最右端。
然后继续此过程,找到第二大值并将其放置,依此类推,直到数据排序完毕。

冒泡排序代码实现

代码实现

#include<iostream>
#include<vector>
using namespace std;

void Bubbling_Sorting(vector<int>& nums)
{
	int n = nums.size();
	for (int i = 0;i < n;i++)
	{
		for (int j = 1;j < n;j++)
		{
			if (nums[j - 1] > nums[j])
			{
				int temp = nums[j - 1];
				nums[j - 1] = nums[j];
				nums[j] = temp;
			}
		}
	}
}
void Output(vector<int>nums)
{
	int n = nums.size();
	for (int i = 0;i < n;i++)
	{
		cout << nums[i] << "  ";
	}
}
int main()
{
	vector<int>nums = { 6,5,3,1,8,7,2,4 };
	Bubbling_Sorting(nums);
	Output(nums);
	return 0;
}

冒泡排序的时间复杂度
从上述代码中不难看出冒泡排序的时间复杂度为:O( n 2 n^{2} n2),需要依赖于两个嵌套for循环来实现排序。

快速排序 !!!

快速排序算法作为所有的排序算法中,算是最优的一种算法,是一种基于分治算法的排序算法,先选择一个元素作为基准值,并根据该基准值 进行分区,将小于所选中心点放在左边,大于所选中心点的放在右边,分成两个区间,然后递归地对两个区间进行排序。

快速排序算法的重点
1、首先要找到正确的基准值
2、基准值的左侧都是小于中心点的元素
3、基准值的右侧都是大于中心点的元素
然后递归地对分出来的左右两个区间进行排序算法
快速排序算法的动画演示如下所示,这里的演示中所选的基准值不是最左边也不是最右边,是随机选择的。

请添加图片描述

快速排序代码实现

代码实现

#include<iostream>
#include<vector>
using namespace std;

int getindex(vector<int>&nums,int left,int right) // 实现分区并返回基准值的位置下标 
{
	int pivot = nums[right]; // 默认以最右边的元素值作为基准值 
	int i = left - 1;
	
	for(int j = left; j <= right - 1;j++) // 对应区间进行排序 
	{
		if(nums[j] < pivot)
		{
			i++;
			swap(nums[i],nums[j]);
		}
	}
	swap(nums[i + 1],nums[right]); // 将最右边的基准值放在当前区间内的正确排序位置 
	return (i + 1);  
}

void QuickSort(vector<int>&nums,int left,int right)
{
	if(left < right)
	{
		int index = getindex(nums,left,right);
		// 递归调用 基准值分的左右两个区间,再次进行排序 
		QuickSort(nums,index + 1,right);
		QuickSort(nums,left,index - 1);
	}
}

int main()
{
	vector<int>nums = {10,25,81,36,95,41,29};
	int n = nums.size();
	QuickSort(nums,0,n-1);
	for(int i = 0;i < n;i++)
	{
		cout<<nums[i]<<"	";
	}
	return 0;
 } 

快速排序时间复杂度
因为快速排序算法采用的是分而治之的算法,结合递归调用,对每个区域内的序列完成排序从快速的实现整个序列的排序。
时间复杂度主要依赖于分区排序的效果
分区排序: 在上面这段代码中,我们每次是取最右侧的元素作为基准值,若基准值能够将整个序列平均的分成两个差不多的区间,元素数量大致相等,这样每次递归处理的数据量就减半,那么就能够实现最佳效果。若基准值无法平均的分成两个区间,存在极端情况的话就会造成最差的效果。

平均情况:O( n l o g ( n ) nlog(n) nlog(n))
最佳情况:O( n l o g ( n ) nlog(n) nlog(n)) 每次分区情况都能够平均的划分成平衡的两部分,每次递归调用处理的数据量减半,而递归的深度为log n(n是数组的长度)
最坏情况:O( n 2 n^{2} n2) :每次分区操作都导致数组被划分成极不平衡的两部分(比如,每次分区后,基准值都是数组中的最小或最大元素),那么快速排序的性能会退化到O( n 2 n^{2} n2)。这是因为每次递归调用处理的数据量并没有显著减少,递归深度接近n。

快速排序的空间复杂度
因为没有使用到额外的内存空间,空间复杂度为:O(1)

优点:
1、它是一种分而治之的算法,可以使解决问题变得更容易。
2、它对大型数据集非常有效。

缺点:
它的最坏时间复杂度为 O( n 2 n^{2} n2 ),这发生在基准值选择不当的情况下。对于小数据集来说,这不是一个好的选择。

合并排序

排序原理: 建立在归并操作上,是分治法的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列。即将待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序子序列合并为整体有序序列。
实现方式: 递归地将当前序列平均分割成两半,若序列长度为偶数,则分成两个长度相等的子序列;若序列长度为奇数,则分成一个长度为length/2的子序列和一个长度为length/2+1的子序列,对子序列继续进行排序,最终将有序子序列合并为整体有序序列。
合并操作通常需要一个与原数组大小相同的辅助数组来暂存子序列的合并结果。
时间复杂度
合并排序算法无论什么情况下,它的算法时间复杂度都是O( n l o g ( n ) nlog(n) nlog(n)),由于需要使用额外的内存空间来存储数据的排序情况,所以空间复杂度为O(n)

总结

本篇文章记录了几种不同的排序算法,每个算法有不同的思想,不同的原理,也各有优劣。对于上文介绍到的算法中,最快的算法是快速排序算法、合并排序算法,但是快速排序算法稳定性没有合并算法的稳定性高,有的时候达到的时间效果并不是最好,而合并排序算法的算法时间稳定性很高,每次都能达到理想速度,但是他需要使用额外的内存空间才能实现排序效果。
所以对于不同的情况之下,我们需要综合考虑来选择算法。

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值