数据结构初阶 复杂度

1 算法效率

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

2 时间复杂度

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数(指数学上的函数式),它定量描述了该算法的运行时间。一个算法执行所耗费的时间难以具体的算出来,它与环境有关,由于一个算法所花费的时间与其中语句的执行次数成正比例,于是我们定义算法中的基本操作的执行次数为算法的时间复杂度。

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

以上代码count语句总共执行了N^2+N*2+10次,我们得到一个函数式:F(N) = N^2+N*2+10

N = 10 F(N) = 130
N = 100 F(N) = 10210
N = 1000 F(N) = 1002010

N越大,后面两项对函数值的影响越小,(N较小的时候我们不考虑,认为他们是一样的,因为CPU跑得非常快,比较没有意义)故在实际中我们计算时间复杂度时,不一定要计算精确的执行次数,只需要知道大概执行次数,这里我们使用大O的渐进表示法(进行大概的估算,计算算法属于哪个量级),例如上述代码时间复杂度可用O(N^2)表示。

#include <stdio.h>
#include <time.h>
int main()
{
	int begin1 = clock();//clock函数可捕捉从程序开始运行到调用clock()的ms数
	int n = 100000000;
	int x = 10;
	for (int i = 0; i < n; i++)
	{
		x++;
	}
	int end1 = clock();
	printf("%dms\n", end1-begin1);
	return 0;
}

(0ms代码中间差异小于1ms)

2.1 大O的渐进表示法

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

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

例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) = 2*N+10 -> 时间复杂度为O(N) (系数也要去掉)

例2:

// 计算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)或者O(max(M+N))(如果有说明,M远大于N,O(M),N远大于M,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),不是代表一次,是代表常数次

2.2 常见量级

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );
//底层是查找一个字符
while(*str)
{
    if(*str == character)
    {
        return str;
    }
    else
    {
        str++;
    }
}

要分最好、最坏和平均去查找

 最坏情况:任意输入规模的最大运行次数(上界)
 平均情况:任意输入规模的期望运行次数
 最好情况:任意输入规模的最小运行次数(下界)

最好是O(1),最坏是O(N),平均是O(N/2) (1+2+...+N)/N

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

2.3 例题

1、消失的数字

思路一:求和0到n,再依次减去数组中值(O(N))
int missingNumber(int* nums, int numsSize)
{
	int N = numsSize;
	int ret = (0 + N)*(N + 1) / 2;
	for (int i = 0; i < numsSize; ++i)
	{
		ret -= nums[i];
	}
	return ret;
}
思路二:异或(相同为0,相异为1)

1、交换律: A ^ B = B ^ A

2、结合律: ( A ^ B ) ^ C = A ^ ( B ^ C )

3、自反性: A ^ B ^ B = A 

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

2、轮转数组

用大O的渐进表示法,F(N) = k*(N-1),时间复杂度为O(k*N),因为k%=N,最好情况k = 0,最坏为k = N-1

思路一:暴力求解
void rotate(int* nums, int numsSize, int k) {
    k = k%numsSize;
    while(k--)
    {
        int tmp = nums[numsSize-1];
        for(int i = numsSize -1;i>0;i--)
        {
            nums[i] = nums[i-1];
        }
        nums[0] = tmp;

    }
}
思路二:

//先写一个逆置函数
void reverse(int*nums,int left ,int right)
{
    while(left<right)
    {
        int tmp = nums[left];
        nums[left]=nums[right];
        nums[right] = tmp;
        left++;
        right--;
    }
}
void rotate(int* nums, int numsSize, int k) {
   k%=numsSize;
   reverse(nums,0,numsSize-1-k);
   reverse(nums,numsSize-k,numsSize-1);
   reverse(nums,0,numsSize-1);
}

(解题写出思路选时间复杂度最优)

3、计算时间复杂度

void fun(int n)
{
    int x = 0;
    for(int i = 1;i < n;i*=2)
    {
        ++x;
    }
    printf("%d\n",x);
}

O(log₂N),为了方便可省略底数O(log N),只有以2为底可省略

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

二分查找,当这个查找区间只剩一个值时最坏,N/2/2/.../2 = 1,假设查找x次,x = log₂N,时间复杂度为O(log₂N)

二分查找与暴力查找差别非常大:

二分查找的缺点是外强中干,实际不太中用,这与排序有关,但不是主要原因,因为排序只用排一次,后面查找可以一直用,主要原因是结构,它必须用数组结构,不方便插入删除,需要挪动数据,链表也不行,链表不能用二分查找,下标不能够随机访问

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值