数据结构之复杂度

🌈引言

🚀我们利用时间复杂度和空间复杂度来衡量某个算法的效率。复杂度是学习数据结构的基础。接下来就让我们来了解了解复杂度。

在这里插入图片描述

🌈前提

🌳数据结构是什么?

🚀数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
说白了就是在内存中管理数据结构。像数组型结构、链式结构、哈希结构…

🚀Q:为什么需要各种各样的数据结构呢?
答:因为在实践当中会有各种各样的需求,不同的数据结构有它们自己的优势。就好比“运输”,我们一般用货车来拉货。用公交车来载人。如果你用大货车来载人,可以是可以,但还是有点不妥。

🚀Q:在内存中管理数据的本质是什么?
答:增删查改!

🌳算法是什么?

🚀算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
算法的本质是:对数据进行一些处理从而达到一些你想要的结果!

🌈正文

🌳时间复杂度

  • 什么是时间复杂度?

🚀时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

  • 时间复杂度难道是让你去计算某个算法运行的时间吗?

🚀答:当然不是,每个人的电脑又不是一摸一样的,配置不同,程序运行也是有快慢之分的。具体的时间根机器的配置有关。 那么时间复杂度又是计算什么呢?—算法大概执行的次数。 这里要将一种表示时间复杂度的表示方法:大0渐进表示法。

  • 一般计算方法

🚀没有递归时,看循环执行了多少次!
🚀有递归时,每次递归时有循环就看循环执行了多少次。没有循环,每次递归看成一次!

  • 大O渐进表示法

🚀基本规则

1、用常数1取代运行时间中的所有加法常数。O(1)

2、在修改后的运行次数函数中,只保留最高阶项。假如是N^2+2N写成O(N ^2)
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。例如2N+3写成O(N)
4、另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

🚀常见时间复杂度计算举例

实例一:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 计算Func2的时间复杂度?
void Func2(int N)
{
	int count = 0;
	for (int k = 0; k < 2 * N; ++k)//循环执行了2N次
	{
		++count;
	}
	int M = 10;
	while (M--)//循环执行了10次
	{
		++count;
	}
	printf("%d\n", count);
}

在这里插入图片描述
实例二:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 计算Func3`在这里插入代码片`的时间复杂度?
void Func3(int N, int M)
{
	int count = 0;
	for (int k = 0; k < M; ++k)//计算了M次
	{
		++count;
	}
	for (int k = 0; k < N; ++k)//计算了N次
	{
		++count;
	}
	printf("%d\n", count);
}

在这里插入图片描述
实例三:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 计算Func4的时间复杂度?
void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)//循环执行了100次(常数项)
	{
		++count;
	}
	printf("%d\n", count);
}

在这里插入图片描述
实例四:

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

在这里插入图片描述
这个函数的返回值是返回目标字母的地址。

🚀这里就要用到大O渐进表示法中取最坏情况:1.找到最后一个字母;2.没找到所以时间复杂度为O(N).

实例五:

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)//冒泡排序算法
{
 assert(a);
 for (size_t end = n; end > 0; --end)
 {
 	int exchange = 0;
 	for (size_t i = 1; i < end; ++i)
	 {
 			if (a[i-1] > a[i])
 		{
 			Swap(&a[i-1], &a[i]);
 			exchange = 1;
	 	}
 	}
	 if (exchange == 0)
	 break;
 }
}

在这里插入图片描述
实例六:

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
	assert(a);
	int begin = 0;
	int end = n - 1;
	// [begin, end]:begin和end是左闭右闭区间,因此有=号
	while (begin <= end)
	{
		int mid = begin + ((end - begin) >> 1);
		if (a[mid] < x)
			begin = mid + 1;
		else if (a[mid] > x)
			end = mid - 1;
		else
			return mid;
	}
	return -1;
}

🚀 答:时间复杂度为O(logN).

在这里插入图片描述

🚀Q:有人会说:哪时间复杂度是以3或4为底的对数呢?
答:只有log以2为底才会简写成logN,其他对数不可以省略它们的底!

实例七:

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
 if(0 == N)
 return 1;
 
 return Fac(N-1)*N;
}

🚀 这是一个没有循环的递归,则每次递归看成1,一共N-1次递归。那么时间复杂度为:O(N)
在这里插入图片描述

实例八

//计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

在这里插入图片描述
在这里插入图片描述

🚀注意时间复杂度不需要计算那么精确,对它估算即可。

🌳空间复杂度

  • 什么是空间复杂度?

🚀空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数(但也不完全是,比如递归怎么算空间复杂度)。
空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

  • 实例分析

实例一:

// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{
 assert(a);
 for (size_t end = n; end > 0; --end)
 {
 	int exchange = 0;
 	for (size_t i = 1; i < end; ++i)
 	{
 		if (a[i-1] > a[i])
 		{
 			Swap(&a[i-1], &a[i]);
			 exchange = 1;
 		}
	 }
	if (exchange == 0)
 	break;
 }
}

🚀没有递归,计算时间复杂度就是数额外开辟的变量个数,注意是额外。不少人会将参数算进去,这是不对的。 题目中,额外开辟的变量有:
size_t end = n、int exchange = 0、size_t i = 1这三个变量。所以空间复杂度为:O(1) .

实例二:

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{
 	if(n==0)
 	return NULL;
 
 	long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
 	fibArray[0] = 0;
 	fibArray[1] = 1;
 	for (int i = 2; i <= n ; ++i)
 	{
 	fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
 	}
 return fibArray;
}

🚀 这里计算的递归的空间复杂度,那可数不了开辟的变量的个数。这就需要数开辟栈帧空间(额外开的空间)的个数。为啥?因为空间复杂度计算的就是额外开辟的空间,而我们开辟的栈帧就是选用递归算法而额外开辟的空间。
🚀Fib()函数的调用如下图:
在这里插入图片描述

🚀空间是可以重复利用的!递归算法就是调用适当栈帧,解决问题。切记递归的深度不能太深,否则会出现栈溢出的现象。
🚀上图中调用的堆栈有:Fib(5)、Fib(4)、Fib(3)、Fib(2)(和Fib(1)调用的是同一块栈帧),一共开辟的5个栈帧。
🚀实例中,一共开辟了N-2个栈帧,所以空间复杂度为:O(N).

🌳课后习题

  • 轮转数组(https://leetcode.cn/problems/rotate-array/description/)
    在这里插入图片描述

1.方法一:挪动数组

  • 右旋1次
    在这里插入图片描述

  • 右旋2次
    在这里插入图片描述

  • 右旋3次
    在这里插入图片描述

🚀这种算法的时间复杂度为:O(k*N),这里的k指的是右旋的次数,k的取值应该小于数组元素的个数。所以时间复杂度是O(K * N)或O(N^2)(k取最坏的打算,k = N-1)都行;空间复杂度为:O(1).

void rotate(int* nums, int numsSize, int k) {
    k %= numsSize;//确保k<numsSize,防止访问数组越界
    while (k--)//右旋次数
    {
        int n = numsSize - 1;//n为数组最后一个元素的下标
        int tmp = nums[numsSize - 1];//tmp保存最后一个元素
        while (n)//注意不可以写成n--,不然会影响下面的结果,也就是n提前减1
        {
            nums[n] = nums[n - 1];//从后向前交换
            n--;
        }
        nums[0] = tmp;//将tmp的值放入首元素
    }
}

在这里插入图片描述

🚀这个算法leetcode上过不去,可能是对时间复杂度有要求!

2.方法二:
1.申请一个同样大小的数组;
2.先将后k个元素拷贝进数组;
3.再将前numsSize-k个元素拷贝进数组;
4.最后将数组中的所有元素拷贝到原数组去。
在这里插入图片描述

void copy(int*des,int*sor,int len)//用来拷贝
{
    int i = 0;
  while(len--)
  {
      des[i] = sor[i];
      i++;
  }
}
void rotate(int* nums, int numsSize, int k){
k%=numsSize;//使得k小于numsSize
int *nums2 = (int*)malloc(sizeof(int)*numsSize);
//copy(nums2,nums+numsSize-k,k);
//copy(nums2+k,nums,numsSize-k);
//copy(nums,nums2,numsSize);

//也可以用现成的函数memcpy
memcpy(nums2,nums+numsSize-k,k*sizeof(int));
memcpy(nums2+k,nums,(numsSize-k)*sizeof(int));
memcpy(nums,nums2,numsSize*sizeof(int));
free(nums2);
nums2 = NULL;
}

在这里插入图片描述

在这里插入图片描述

  • 方法3:
    1.前n-k个元素逆置(n是numsSize的缩写)
    2.后k个元素逆置
    3.再整体逆置

🚀这种算法不容易想到,属于那种考验智商的。所以我还是推荐第二种。

void reverse(int*arr,int len)//逆置
{
    int start = 0;//首元素下标
    int end = len-1;//最后一个元素下标
    while(start<end)
    {
        //交换两个数
        int tmp = arr[start];
        arr[start] = arr[end];
        arr[end] = tmp;
        
        start++;
        end--;
    }
}
void rotate(int* nums, int numsSize, int k){
k%= numsSize;//使得k小于numsSize
reverse(nums,numsSize-k);
reverse(nums+numsSize-k,k);
reverse(nums,numsSize);
}

在这里插入图片描述

🌈结尾

🚀今天就分享到这里,希望对你有帮助!

在这里插入图片描述

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值