排序算法汇总

目录

前言

一、插入排序

二、选择排序和冒泡排序

1.选择排序

2.冒泡排序

三、归并排序

         1、分治算

         2、归并排序

四、快速排序

总结



前言

对于各类网站、APP来说,后台服务器每天处理最多就是根据用户的搜索喜好来分类排序各种产品,因此排序算法是目前用途最广泛的算法之一。本文将通过介绍几种常见排序算法的模拟实现来分析时间复杂度和空间复杂度这两个概念。

一、插入排序

玩过斗地主的小伙伴都知道,在抓到一张新牌后会将新牌插到原来牌中合理的位置,使之成为排序好的牌(当然可能有小伙伴习惯于牌无序)。这其实就是插入排序法的模拟过程。

具体步骤:例如给定一个长为10的数组arr[10] = {2,4,1,5,7,9,10,3,6,8}。

首先默认0位置的2有序,然后指针来到1位置的4,4>2有序,不用交换。然后指针来到2位置的1,此时1<4,arr[2]和arr[1]交换,指针来到1位置,此时1<2,arr[0]和arr[1]交换,指针<0,退出循环,0-2范围有序,接下来指针来到3位置重复此过程,一直到arr[10]数组全部有序,循环结束。

具体代码实现:

//插入排序
void insertSort(int arr[], int len)
{
	for (int i = 1; i < len; i++)
	{
		for (int j = i - 1; j >= 0; j--)
		{
			if (arr[j + 1] < arr[j])
			{
				swap(arr, j, j + 1);
			}
		}
	}
}

笔者这里想介绍另一种插入排序的实现算法:来自于<算法导论>书中的方法,它更接近与日常所说的插入扑克牌的过程。

void insertSort2(int arr[], int len)
{
	int key = 0;
	for (int i = 1; i < len; i++)
	{
		//1、先用key保存arr[i]的值
		//2、从arr[i-1]开始,依次比较key和数组的值
		//3、值大的向右移动
		//4、最后将key插入合适的位置
		key = arr[i];
		int j = i - 1;
		while (j >= 0 && arr[j] > key)
		{
			arr[j + 1] = arr[j];
			j--;
		}
		arr[j + 1] = key;
	}
}

 时间复杂度和空间复杂度:

算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。

算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间从理论上说,是不能算出来的,但一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

对于插入排序算法:当i = 1时 基本语句执行1次,i = 2 基本语句执行2次...... 当i=n-1时 基本语句执行n次,总共执行F(N) = 1 + 2 +......+N = 1/2*N^2 + 1/2*N

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶

在实际中一般情况关注的是算法的最坏运行情况
因此使用大O的渐进表示法以后,插入排序的时间复杂度为:O(N^2)

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

由于插入排序没有申请额外的数组空间,使用了常数个额外空间,所以空间复杂度为 O(1)
 

二、选择排序和冒泡排序

1.选择排序

  选择排序的过程大致为:假设0位置为最小值,首先从数组0-N-1上依次和0位置数作比较选出最小的值放在0位置,再假设1位置数为最小值,从1-N-1位置依次和1位置的数比较,选出最小值放在1位置......以此类推最后在N-2 - N-1位置选出最小值放在N-2位置,数组有序。

代码如下(示例):

//选择排序
void selectSort(int arr[], int len)
{
	for (int i = 0; i < len - 1; i++)
	{
		int j = 0;
		for (j = i + 1; j < len; j++)
		{
			if (arr[j] < arr[i])
			{
				swap(arr, i, j);
			}
		}
	}
}

时间复杂度:和选择排序一样,基本语句程序次数呈现出等差数列,因此时间复杂度为O(N^2)

空间复杂度:由于没有申请额外的数组空间,因此创建的几个临时变量空间复杂度为O(1) 

2.冒泡排序

  冒泡排序的流程为:在数组下标0-N-1上从前往后两两比较选出最大的数放在下标N-1上,再从0-N-2上两两比较选出最大的数放在下标N-2上......以此类推,在0-1上选出最大的数放在下标1位置,数组有序。

代码如下(示例):

//冒泡排序
void bubbleSort(int arr[], int len)
{
	
	for (int i = 0; i < len - 1; i++)//趟数
	{
		int flag = 1; //假设这一趟有序
		for (int j = 0; j < len - 1 - i; j++)
		{
			flag = 0;
			if (arr[j + 1] < arr[j])
			{
				swap(arr, j + 1, j);
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

时间复杂度:和选择排序一样,基本语句程序次数呈现出等差数列,因此时间复杂度为O(N^2)

空间复杂度:由于没有申请额外的数组空间,因此创建的几个临时变量空间复杂度为O(1)  


三、归并排序

    1、分治算法

          在介绍归并排序之前,首先介绍一下分治算法: 将原问题分解为几个规模较小但与原问题类似的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解

分治模式在每层递归时都有三个步骤:

        1、分解原问题为若干子问题,这些子问题是原问题的规模较小的实例

        2、解决这些子问题,递归地求解各子问题。如果子问题的规模足够小达到递归base case则直接求解返回

        3、合并这些子问题的解成为原问题的解

    2、归并排序

           归并排序算法完全遵循分治模式,其操作如下:

         1、分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列

         2、解决:使用归并排序递归地排序两个子序列

         3、合并:合并两个已排序的子序列以产生已排序的答案

           具体算法的执行流程可以通过如下图来展示:

        

具体代码实现:

//归并排序
void merge(int arr[], int left, int mid, int right)
{
	int len = right - left + 1;
	int* help = (int*)malloc(len * sizeof(int));
	int i = left;
	int j = mid + 1;
	int k = 0;
	while (i <= mid && j <= right)
	{
		//相等时先拷贝左边
		help[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
	}
	while (j <= right)
	{
		help[k++] = arr[j++];
	}
	while (i <= mid)
	{
		help[k++] = arr[i++];
	}
	for (int i = 0; i < len; i++)
	{
		arr[left + i] = help[i];
	}
	free(help);
	help = NULL;
}
void mergeSort(int arr[], int left, int right)
{
	//base case
	if (left >= right)
	{
		return;
	}
	int mid = left + ((right - left) >> 1);
	int len = right - left + 1;
	mergeSort(arr, left, mid);
	mergeSort(arr, mid + 1, right);
	merge(arr, left, mid, right);
}

时间复杂度:

  笔者在这里要介绍一下Master公式:master公式用于计算递归程序的时间复杂度

T(N) = a*T(N/b) + O(N^d)

其中N表示问题的规模,a表示递归的次数也就是生成的子问题数,N/b表示子问题的规模。O(N^d)表示除了递归操作以外其余操作的时间复杂度

结论(证明省略):
①当d<logb a时,时间复杂度为O(N^(logb a))
②当d=logb a时,时间复杂度为O((N^d)*logN)
③当d>logb a时,时间复杂度为O(N^d

归并排序算法中:T(N) = 2*T(N/2) + O(N) 因此满足②,时间复杂度为O(N*logN)

空间复杂度:

递归代码的空间复杂度并不能像时间复杂度那样累加。尽管每次合并操作都需要申请额外的内存空间,但在合并完成之后,临时开辟的内存空间就被释放掉了。在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是 O(n)

              

四、快速排序

快速排序的基本思想是:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小。则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

快速排序的关键在于切分,这个过程使得数组满足下面三个条件:

①对于某个j,a[j]已经排定

②a[lo]到a[j-1]中所有的元素都不大于a[j]

③a[j+1]到a[hi]中的所有元素都不小于a[j]

我们就是通过递归地调用切分来排序的

具体代码的实现:

//快速排序
int partition(int arr[], int left, int right)
{
	//左右扫描指针
	int i = left;
	int j = right + 1;
	int div = arr[left];
	while (1)
	{
		//扫描左右,检查扫描是否结束和是否交换元素
		while (arr[++i] < div)
		{
			if (i == right)
			{
				break;
			}
		}
		while (arr[--j] > div)
		{
			if (j == left)
			{
				break;
			}
		}
		if (i >= j)
		{
			break;
		}
		swap(arr, i, j);
	}
	swap(arr, left, j);
	return j;
}
void quickSort(int arr[], int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int j = partition(arr, left, right);
	quickSort(arr, left, j - 1);
	quickSort(arr, j + 1, right);
}

时间复杂度分析:这里涉及到概率学相关证明,有兴趣的小伙伴可以去看《算法导论》中关于随机快排时间复杂度的证明,为O(N*logN)

空间复杂度:由于没有再申请额外的数组空间,因此空间复杂度为O(1)

总结


以上就是今天要讲的所有内容,本文介绍了5种常见的排序算法的原理和实现,并以此展开了时间复杂度和空间复杂度的概念,分析了这五种算法的时间和空间复杂度,希望这篇文章能够给大家带来些许帮助,谢谢各位小伙伴的支持!

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
排序算法中,时间复杂度是评估算法性能的重要指标。根据引用和引用的内容,下面是一些常见排序算法的时间复杂度汇总: 1. 冒泡排序:冒泡排序是一种简单但效率较低的排序算法。最坏情况下,冒泡排序的时间复杂度是O(n^2),其中n是待排序元素的数量。最好情况下,当数据已经有序时,冒泡排序的时间复杂度是O(n)。 2. 插入排序:插入排序算法根据待排序序列中的元素逐个插入已排序序列的合适位置。最坏情况下,插入排序的时间复杂度也是O(n^2)。最好情况下,当数据已经有序时,插入排序的时间复杂度是O(n)。 3. 选择排序:选择排序是一种简单的排序算法,每次从未排序的部分选择最小(或最大)的元素,然后放到已排序部分的末尾。选择排序的时间复杂度始终为O(n^2),无论数据是否有序。 4. 快速排序:快速排序是一种高效的排序算法,基于分治的思想。最坏情况下,快速排序的时间复杂度是O(n^2),但通常情况下,快速排序的平均时间复杂度是O(nlogn)。 5. 归并排序:归并排序是一种稳定且高效的排序算法,基于分治和合并的思想。归并排序的时间复杂度始终为O(nlogn),无论数据是否有序。 综上所述,不同的排序算法其时间复杂度不同。冒泡排序和插入排序的时间复杂度是O(n^2),选择排序的时间复杂度也是O(n^2),而快速排序和归并排序的时间复杂度是O(nlogn)。请注意,这些时间复杂度都是在最坏情况下估计的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值