数据结构--时间复杂度和空间复杂度概念及实例

1数据结构

1.1 什么是数据结构?

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

1.2 什么是算法?

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

2时间复杂度与空间复杂度

2.1 算法效率

算法效率分为两种:一种是时间效率,一种是空间效率。时间效率被称为时间复杂度,空间效率被称为空间复杂度。时间复杂度主要衡量的是一个算法的运行速度,空间复杂度主要衡量的是一个算法所需要的额外空间。
在计算机的发展早期,计算机的存储容量很小,所以对空间复杂度很在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以,目前我们更加关注计算机的时间复杂度。

2.2 时间复杂度

2.2.1 时间复杂度的概念

一个算法执行所消耗的时间,从理论上是很难计算出来的,且意义不大。因为一个算法的真正运行时间取决于cpu,运行环境等等条件,在不同计算机上算法的运行时间是不同的,甚至在不同时刻算法的运行时间也不同。
所以,时间复杂度定义为:算法中的基本操作的执行次数。

观察下面代码,计算一下fun的基本操作执行了多少次?

void fun(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);
}

对于fun函数来说,基本操作的执行次数为:
F ( n ) = n 2 + 2 ∗ n + 10 F(n)=n^{2}+2*n + 10 F(n)=n2+2n+10
则fun函数的时间复杂度为:
F ( n ) = n 2 + 2 ∗ n + 10 F(n)=n^{2}+2*n + 10 F(n)=n2+2n+10

2.2.2 大O的渐进表示法

实际开发中,我们并不需要准确的知道每个算法中基本操作的具体执行次数,我们只需要了解一个大致的执行次数即可,换句话说我们只需要了解执行次数的数量级即可,那么这里使用的方法称为大O的渐进表示法

  • 数量级

对于数字14来说,它的数量级为10
对于数字140来说,它的数量级为100
对于数字1400来说,它的数量级为1000
数量级是指数量的尺寸和大小的级别。

  • 大O符号

用来描述函数渐进行为的数学符号

  • 推导大O阶方法

1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

使用大O 的渐进表示法来描述fun的时间复杂度:
O ( n 2 ) O(n^{2}) O(n2)
显然,大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

  • 最好、最坏和平均情况

有些算法会存在最好、最坏和平均情况,如在数组中查找某个元素并返回其数组下标。
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)

如在一个长度为N的数组中搜索一个数据x:

最坏情况:N次找到,即最后找到
平均情况:N/2次找到
最好情况:1次找到

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

2.2.3 常见时间复杂度的计算

实例1:

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

fun1的基本操作的执行了2N+10次,通过推导大O阶方法知道,时间复杂度为O(N)。
实例2:

void fun2(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);
}

fun2的基本操作的执行了N+M次,通过推导大O阶方法知道,时间复杂度为O(N+M)。
实例3:

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

fun3的基本操作的执行了100次,通过推导大O阶方法知道,时间复杂度为O(1)。
实例4:

//strchr函数功能为在一个串str中查找给定字符character的第一个匹配之处
const char* strchr(const char* str, int character);

该函数的功能为在一个字符串查找字符character并返回该字符的位置。这个查找函数的基本操作执行次数分为:

最好情况:1次
最坏情况:N次
平均情况:N/2次

时间复杂度一般依据最坏情况,所以该函数的时间复杂度为O(N)。
实例5:

void Swap(int* a, int* b) {
	int c = *a;
	*a = *b;
	*b = c;
}
//冒泡排序 --从小到大
void BubbleSort(int* a, int n) {
	assert(a);//异常处理
	for (int end = n; end > 0; --end) {
		int exchange = 0;
		for (int i = 1; i < end; ++i) {
			if (a[i - 1] > a[i]) {
				//两个数据进行比较,前面一个数据大于后一个数据
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		//如果遍历整个数组,发现没有数据进行交换,即每个元素均小于等于后一个元素
		//则无须在进行排序,直接结束循环即可
		if (exchange == 0)
			break;
	}
}

对于BubbleSort函数来说,它存在

最好情况:数组为顺序,执行N次
最坏情况:数组为逆序,执行N*(N+1)/2次
平均情况

这里我们考虑最坏情况,即数组为逆序,时间复杂度为:O(N^2)。分析过程如下:
在这里插入图片描述
实例6:

//二分查找法
int BinarySearch(int* a, int  n, int x) {
	assert(a);
	int begin = 0;
	int end = n - 1;
	while (begin < end) {
		int mid = ((end - begin) >> 1) + begin; //计算end与begin的中间值,右移1位相当于除以2
		if (a[mid] < x) {
			begin = mid - 1;
		}
		else if(a[mid]>x){
			end = mid;
		}
		else {
			return mid;
		}
	}
	return -1;
}

对于BinarySearch函数来说,它存在

最好情况:执行1次
最坏情况:约执行logN次,这里的logN是以2为底,以N为对数。
平均情况

这里我们考虑最坏情况,时间复杂度为:O(logN)。分析如下:

第一次查找:在长度为N的数组中查找值,取中间值进行比较
第二次查找:在长度为N/2的数组中查找值,取中间值进行比较
第三次查找:在长度为N/(2^2)的数组中查找值,取中间值进行比较

第logN次查找:在长度为N/(2^logN)的数组中查找值,即在长度为1的数组中查找,无论是否找到均跳出循环,结束查找。

实例7:

//求阶乘
long long Factorial(int N) {
	return N < 2 ? N : Factorial(N - 1) * N;
}

Fibonacci中函数调用操作为N次,时间复杂度为O(N),分析过程如下:
在这里插入图片描述
实例8:

//斐波那契函数
long long Fibonacci(int N) {
	return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2);
}

Fibonacci函数的时间复杂度为O(2^N),分析过程如下:
在这里插入图片描述

2.3 空间复杂度

2.3.1 空间复杂度的概念

一个算法中所有变量实际占的内存空间很难估量,且意义不大。因为数据类型在内存中所占的内存数与计算机的系统有关系,在不同计算机上算法所占的内存是不同的。
所以,空间复杂度定义为:算法中变量的个数。
空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

2.3.2 常见空间复杂度的计算

实例1:

void Swap(int* a, int* b) {
	int c = *a;
	*a = *b;
	*b = c;
}
//冒泡排序 --从小到大
void BubbleSort(int* a, int n) {
	assert(a);//异常处理
	for (int end = n; end > 0; --end) {
		int exchange = 0;
		for (int i = 1; i < end; ++i) {
			if (a[i - 1] > a[i]) {
				//两个数据进行比较,前面一个数据大于后一个数据
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		//如果遍历整个数组,发现没有数据进行交换,即每个元素均小于等于后一个元素
		//则无须在进行排序,直接结束循环即可
		if (exchange == 0)
			break;
	}
}

BubbleSort中变量为a、n、end、exchange、i,变量个数为5,故该算法的空间复杂度为O(1)。
实例2:

//斐波那契函数
long long* Fibonacci(int 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;
}

Fibonacci函数中的变量有:n、i、fibArray、以及fibArray所指向的n+1个空间,所有该算法中共有n+4个变量,故空间复杂度为O(n)。
实例3:

//求阶乘
long long Factorial(int N) {
	return N < 2 ? N : Factorial(N - 1) * N;
}

Factorial递归调用了N次,开辟了N个栈帧,每个栈帧使用了1个空间,故空间复杂度为O(N)。内存存储如下:
在这里插入图片描述
实例4:

//斐波那契函数
long long Fibonacci(int N) {
	return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2);
}

Fibonacci函数的空间复杂度为O(N),分析过程如下:
在这里插入图片描述
注:本文章所用代码的头文件为:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值