数据结构拯救者【时间复杂度、空间复杂度--1】

目录

数据结构前言

1.算法的复杂度

2.时间复杂度

2.1 时间复杂度的概念

2.2 大O的渐进表示法

2.3 时间复杂度存在最好、平均和最坏情况

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

3.空间复杂度

注意:时间累积(一去不复返),空间不累计(可重复利用)

4. 常见时间复杂度以及复杂度oj练习


 

数据结构前言

什么是数据结构?

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

什么是算法?

算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单 来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。


1.算法的复杂度

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

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


2.时间复杂度

2.1 时间复杂度的概念

算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来才能知道。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

请计算一下Func1中++count语句总共执行了多少次? 

void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
 for (int j = 0; j < N ; ++ j)
 {
 ++count;
 }
}
 
for (int k = 0; k < 2 * N ; ++ k)
{
 ++count;
}
int M = 10;
while (M--)
{
 ++count;
}
printf("%d\n", count);
}

找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

                                

N=10 F(N)= 130
N=100 F(N)= 10210
N=1000 F(N)= 1002010
N=10000 F(N)= 100,020,010

我们可以发现,当N越大,2*N+10对结果的影响就越小,N^2占结果的大头。如果我们用F(N)表示时间复杂度,每个算法都需要仔细地求出对应函数十分的复杂,实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数(估算数量级),于是我们使用大O的渐进表示法。


2.2 大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

推导大O阶方法:

1.用常数1取代运行时间中的所有加法常数

2.在修改后的运行次数函数中,只保留最高阶项

3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N^2)

N=10 F(N)= 100
N=100 F(N)= 10,000
N=1000 F(N)= 1,000,000
N=10000 F(N)= 100,000,000

去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数

计算Func3的时间复杂度?

void Func3(int N, int M)
{
 int count = 0;
 for (int k = 0; k < M; ++ k)
 {
 ++count;
 }
 for (int k = 0; k < N ; ++ k)
 {
 ++count;
 }
 printf("%d\n", count);
}

 答案:O(M+N),一般情况下都会用N做未知数,但是M+N都是不知道大小(未知数),M和N都需要留下

如果有说明N远大于M,则为O(N);如果M远大于N,则为O(M);如果N和M一样大,O(N)或者O(M)都可以


2.3 时间复杂度存在最好、平均和最坏情况

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

例如:在一个长度为N数组中搜索一个数据x 最好情况:1次找到 最坏情况:N次找到 平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)


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

计算Func2的时间复杂度?   

void Func2(int N)
{
 int count = 0;
 for (int k = 0; k < 2 * N ; ++ k)
 {
 ++count;
 }
 int M = 10;
 while (M--)
 {
 ++count;
 }
 printf("%d\n", count);
}

答案:O(N)

大O阶法则:2.在修改后的运行次数函数中,只保留最高阶项  3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。当N趋于无穷大时,N表示数量级(亿万富翁,一亿两亿都还是亿万富翁),+10对其无太大影响,同时去掉系数

计算Func4的时间复杂度?

void Func4(int N)
{
 int count = 0;
 for (int k = 0; k < 100; ++ k)
 {
 ++count;
 }
 printf("%d\n", count);
}

答案:O(1)

只要是常数,时间复杂度都是用1替代。大O阶法则:1.用常数1取代运行时间中的所有加法常数

计算strchr的时间复杂度?

const char * strchr ( const char * str, int character );//在字符串中查找一个字符

答案:O(N),基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为 O(N)


计算冒泡排序的时间复杂度?

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;
 }
}

答案:O(N^2)

算时间复杂度不能取数循环,不一定准确,一定要看算法思想进行计算

冒泡排序比较了N-1次,准确的次数:F(N)=N-1+N-2+N-3+...2+1基本操作执行最好N次,最坏执行了(N*(N-1)/2次(等差数列),通过推导大O阶方法+时间复杂度一般看最坏,时间复杂度为 O(N^2),最好的情况是O(N)


计算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)

基本操作执行最好1次O(1),最坏情况是找不到或者只剩一个数找到,当找到最后一个数后,×2,x2,最终得到原数组,同理,折半了多少次就除几个2,假设折半查找了x次,2^x=N

因为在文本中不好写对数,于是我们简写成logN在算法分析中表示是底数为2,对数为N。有些地方会写成lgN(数学中lgN是以10为底,这种写法有歧义,最好写成logN)


计算阶乘递归Fac的时间复杂度?

long long Fac(size_t N)
{
 if(0 == N)
 return 1;
 
 return Fac(N-1)*N;
}

答案:O()N)

通过计算分析发现总共递归N+1次,递归内部是常数次,Fac(N)Fac(N-1)Fac(N-2)...时间复杂度为O(N)。


计算斐波那契递归Fib的时间复杂度?

long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

答案:O(2^N)

等比数列,2^0+2^1+...+2^(N-2)=2^(N-1)-1  

 


 

3.空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。

空间复杂度不是程序占用了多少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;
 }
}

答案:O(1)

使用了常数个额外空间,所以空间复杂度为 O(1),提供了N个数的数组并不算在我们算法内,空间复杂度是因为这个算法而开辟才计算在内,只有四个临时变量因为算法被创建了,其中exchange虽然销毁,但是还是使用原空间


计算Fibonacci的空间复杂度?

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;
}

答案:空间复杂度为 O(N)

malloc是因为算法而开辟了n+1个空间


计算阶乘递归Fac的空间复杂度?

long long Fac(size_t N)
{
 if(N == 0)
 return 1;
 
 return Fac(N-1)*N;
}

答案:空间复杂度为 O(N)

开辟栈帧,总共开辟n+1个栈帧,每个栈帧为O(1)


计算斐波那契递归Fib的空间复杂度?

long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

答案:空间复杂度为 O(N)

函数先计算Fib(N)Fib(N-1)Fib(N-2)..,再去往右计算,函数往下递归,返回后函数销毁,空间复杂度为O(N)

void test1()
{
	int a = 0;
	printf("%p\n", &a);
}
void test2()
{
	int b = 0;
	printf("%p\n", &b);
}
int main()
{
	test1();
	test2();
	return 0;
}

可以看到地址是一样的,具体详见我的函数栈帧篇C语言拯救者 番外篇 (函数栈帧的创建和销毁讲解)_北方留意尘的博客-CSDN博客_函数的栈帧


4. 常见时间复杂度以及复杂度oj练习

114514 O(1) 常数阶
3n+4 O(N) 线性阶
3n^2+4n+5 O(N^2) 平方阶
3log(2)n+4 O(logN) 对数阶
2n+3nlog(2)n+14 O(nlogN) nlogn阶
n^3+2n^2+4n+6 O(N^3) 立方阶
2^n O(2^N) 指数阶

 4.1 Leetcode:数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

 思路:

要求O(N)时间复杂度内完成,我们可以使用异或的特性来完成,两个相同的数字异或在一起为0。只要内容有相同,异或不在乎顺序,让缺失的数字和对应的数组异或即可

int missingNumber(int* nums, int numsSize){
  int x=0;

  for(int i=0;i<numsSize;i++)
  {
   x ^= nums[i];
  }
  for(int j=0;j<numsSize+1;j++)
  {
   x^=j;
  }
  return x;
}

 轮转数组 

思路:

 如果我们用通常的方法,时间复杂度至少为O(N),遍历一遍数组,我们只能使用三步辗转法来戒出这道题(几乎很难想到)

void reverse(int*nums,int left,int right)
{
    while(left<right)
    {
        int tmp=nums[left];
        nums[left]=nums[right];
        nums[right]=tmp;
        --right;
        ++left;
    }
}


void rotate(int* nums, int numsSize, int k){
    k%=numsSize;    
    reverse(nums,numsSize-k,numsSize-1);
    reverse(nums,0,numsSize-k-1);
    reverse(nums,0,numsSize-1);

}

猜你喜欢

转载自blog.csdn.net/weixin_63543274/article/details/124237885