数据结构算法的时间复杂度问题

目录

1、前言

2、大O的渐进表示法 (Big O notation)

3、常见时间复杂度举例

4、常见时间复杂度的对比


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

我们根据此代码可以得出这段代码要运行次数:F(N)=N^2 + N + 10,但在实际应用中我们一般不一定要计算精确的执行次数,而是用大O的渐进表示法。

2、大O的渐进表示法 (Big O notation)

是用于描述函数渐进行为的数学符号。 推导大O阶方法:

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

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

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

类似于高数中的找大头。

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

但值得注意的是有些算法的时间复杂度存在最好、平均和最坏情况:

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

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

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

在实际中一般情况关注的是算法的最坏运行情况。

3、常见时间复杂度举例

1.

// 计算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);
}

 这里精确的时间复杂度为:F(N)=N^2 + N + 10,使用O表示法时间复杂度为O(N)。

2.

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

精确的时间复杂度为:F(N)=M + N 

当M>>N时,时间复杂度O(M),当M<<N时,时间复杂度O(N)。

3.

// 计算Func4的时间复杂度?
void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}

这里将程序的循环值设为常数,所以时间复杂度为O(1)。

4.

const char * strchr ( const char * str, int character );

查找字符串中第一个出现的字符返回指向 C 字符串
str 中第一个出现的字符的指针。
终止空字符被视为 C 字符串的一部分。因此,也可以定位它以检索指向字符串末尾的指针。

这个就是在字符串中寻找一个字符,对于这种就要分好坏情况;
最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为 O(N)。

5.

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

两个for循环嵌套使用,一共执行N^2次,故时间复杂度为O(N^2)。

6.

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(log2)。

7.

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

	return Fac(N - 1) * N;
}

递归的时间复杂度就是多次调用的累加,时间复杂度为O(N)。若在递归中加入for循环

for(size_t i = 0; i < N ; i++)
{
  //……
}

时间复杂度变为O(N^2)。

8.

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

双路递归要注意,时间是累积的,而空间是可以重复利用的,递归某一分路,在函数返回时会系统会自动销栈,将不用的空间返回,让后续递归可以继续使用,故双路递归的时间复杂度为O(N)。

4、常见时间复杂度的对比

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值