关于时间复杂度的一点简单总结

本文介绍了时间复杂度的概念及其重要性,并通过三个实例讲解如何优化算法以降低时间复杂度,包括快速幂运算、排序算法及素数判断。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于时间复杂度的一点简单总结

算法的复杂度分为时间复杂度和空间复杂度,今天在这里主要讲一下时间复杂度。

一.什么是时间复杂度:

​ 时间复杂度,简单来说,就是通过程序语句的执行次数来估计程序运行时间的一个函数,用O表示。(在相同硬件和软件的情况下);可以把它认为就是花费时间的函数的数量级。

二.为什么要注意时间复杂度?

简单来说,就是为了提高计算机工作的效率,因为现实生活中,通常处理的是大量的数据,所以需要通过优化程序,使得提升工作效率。以及为分析程序效率提供了便利;

三.时间复杂度的估算:*

一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号 ),简称时间复杂度。
以上是时间复杂的数学定义:
在这里f(n)代表的是算法需要的增长速度,O(n)就是把f(n)的最高项系数去掉之后,保留的最高项次数,就是表示的是一个数量级。简单来说就是我们估算出f(n)的函数,然后再对它进行O运算的结果。

几个简单的判定方法:
1.常数级是O(1):就是不管n多大,始终是一个常数;

比如说执行一条语句,a+b;

2.通常循环是O(n)(一层);(因为执行n次常数级操作)

​ 嵌套通常情况是O(n^m);

3.采用二分策略可以降到log2(n);

四.几个简单降低时间复杂度的例子:

e.g.1(一个简单的例子)

计算(a^b)%mod;

​ 通常我们会这样做:

#include<stdio.h>
#include<time.h>
const long long mo = 500;
#define ll long long
int main() {
  ll a, b, tmp;
  scanf("%lld%lld", &a, &b);
  clock_t start = clock();
  tmp = a;
  b--;
  while (b) {a = a * tmp % mo; b--;};
  clock_t end = clock();
  printf("%f", (double)(end - start) / CLOCKS_PER_SEC);
  printf("%lld\n", a);
  return 0;

}

通过简单的估算我们可以得到时间复杂度是O(n)

这个程序在一定数据范围下的运行时间我们是可以接受的,但是想想如果b很大的情况下呢?
比如b=10^17呢?(通常1s约等于O(10^8));意思就是说在b很大的情况下,我们要等很久才能出结果;

那么这种情况我们该怎么解决呢?

其实刚刚的过程就像是我们一个个的枚举计算,就像用手搬砖,一次能做的拿的东西就只有一个。

我们其实可以换种思路来运算:

比如我们要求2^64
我们可以把它看成(4)^32,(16)^16…..,就是每次给降幂的一半,把底数上升到它的平方;
这样我们的计算次数就会减少;
但是对于2^65来说,我们不能直接分解,我们可以把它看成2^64*2,设置一个临时变量来储存结果,当指数市集是奇数的时候就用临时变量乘一下,然后继续分解;

#include<stdio.h>
#include<time.h>
#define ll long long
const ll mo = 500;

ll quick_pow(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {   
        if (b % 2)ans = (ans * a) % mo; 
         b >>= 1;   
         a = (a * a) % mo; 
     }
    return ans;
}

int main() {
    ll a, b;
    scanf("%lld%lld", &a, &b);
    time_t start = clock() / 1000;
    time_t end = clock() / 1000;
    printf("%lld\n", quick_pow(a, b));
    printf("%f", (double)(end -  start) / CLOCKS_PER_SEC);
    return 0;
}     

这里我用了同一组数据来测试程序

​ 2 100000000000000000(17个0)

对于第一个程序运行的时间是:。。。。。。

​ 很久 ,我用手机测得时间,10分钟都没出来结果

而对于第二个运行的时间就少的多:
这里写图片描述

分析:因为每次都将指数除二,比如我们计算2^64,我们最多计算几次(2^64约等于10^18);

我们就可以把它转换成log2(n)的算法;

总结:计算机虽然计算速度比较快,但是我们也得让它有效率,有些情况下我们可以借助数学性质来降低时间复杂度;

e.g.2

* 关于排序:

​ 就是想到排序,我们大一的可能在c语言课上接触到的有冒泡,选择排序等,这里我主要讲两个排序:

​ 冒泡排序,归并排序(降序):

​ 先看冒泡排序:

      for(int i=0;i<n-1;i++)
         {
            for(int j=0;j<n-1-i;j++)
               {
                  if(a[j]>a[j+1])
                    {
                      int tmp=a[j];
                      a[j]=a[j+1];
                      a[j+1]=tmp;
                    }
               }
         }

这个算法的时间复杂度就是O(N^2);

还是刚刚那个问题,如果数据范围很大呢?

这里我们介绍的是归并排序:
归并归并,就是先归再并,简单来说,就是我们把一个数列一分为二,然后再把子区间一分为二,直到整个区间只有一个数字(构造一个树形结构),然后我们合并两个区间的时候就可以利用树的后序遍历合并,就是每次将两个区间的首位数进行比较,把数字大的放在新数组的前面,对与有一个区间没有元素了,就把另一个区间的数依次加入队尾,再从小区间合并到大区间,最后完成排序。

代码实现如下:

void sort_x(int l,int r)
 {
    int mid=(l+r)/2,i,j,tmp;
    if(r>l)
    {
        sort_x(l,mid);
        sort_x(mid+1,r);
        tmp=l;
        for(i=l,j=mid+1;i<=mid&&j<=r;)
        {
            if(a[i]>a[j])
            {
                c[tmp++]=a[j++];

            }
            else c[tmp++]=a[i++];
        }
        if(i<=mid) for(;i<=mid;) c[tmp++]=a[i++];
        if(j<=r) for(;j<=r;) c[tmp++]=a[j++];
        for(i=l;i<=r;i++) a[i]=c[i];
    }
}

实例对比:
对于这样一组数据:
程序一:
这里写图片描述

​ 程序二
这里写图片描述
可以看到:
下面那个程序运行时间远远比第一个快;

e.g.3;

判断素数:
思路一:试除法:

bool is(int x)
     {
        if(x<=1)return false;
        for(int i=2;i<sqrt(x);i++)
           {
            if(x%i==0)return false;
           }
        return true;
      }

时间复杂度:O(sqrt(n))

对于这个程序有两个问题,第一个就是无法在短时间内判断一个大数为素数,第二个问题就是无法灵活的处理多次询问的问题。

我们先来看第二个问题:(下面的范围:n<=1^6);

如果我们要查询很多个数是不是素数,最暴力的方法就是每个数都判断一下,假设有m次询问(m<10^6),

那么每次的时间复杂度就是O(sqrt(n)),当然对于一定范围内我们可以利用数组打表,询问就只用一次就可以解决:

bool ok[1000006]={0];
for(int i=1;i<=1000000;i++)
    if(is(i))ok[i]=1;

再分析一下,打表的话就有一个固定的开销,;好像并没有优化的样子;

其实我们可以利用一下素数的性质,就是除了二以外,每个数的只能被1和它本身整除,意思就是说,任何数的倍数都是合数,还有一个定理是任何合数都可以分解成几个素数的积,这样的目的就是为了避免数字重复被处理;

int tot=1;
bool ok[1000006];
int prime[1000006];
ok[0]=0;ok[1]=0;
for(int i=2;i<=1000000;i++)
{
    if(!ok[i])
    {  
        prime[tot++]=i;
        for(int j=1;j<=tot&&i*prime[j]<=1000000;j++)
        {
            ok[i*prime[j]]=0;
            if(!(i%prime[j]))break;
         }
     }
}

分析:虽然是一个二重循环,但是我们每次都会把后面的数据,给筛选掉,并且内层循环里面的次数取决于素数的个数,而素数的又比较少,所以该执行次数的数量级就是O(n)级别的,这样就比上面的程序快了很多。
第一个问题的话有兴趣的读者可以去了解一下米勒拉宾算法。这个算法可以在短时间内判断一个大数是不是素数。

总结:

​ 解决问题的思路有很多,我们解决问题的思路决定了我们解决问题的时间。从上面的例子我们可以看出 ,通常情况下我们可以借助数学解决问题,比如说数列求和。。。也可以借助一些数据结构:比如说我们的排序还有一种堆排序,也有利用二进制性质的树状数组可以快速求出前缀和,解决集合关系的并查集。同时我们可以借助一些算法思想,比说分治。当然我们也可以借助一下计算机本身的运算机制(二进制),位运算。

以上就是我的简单总结,总结内容偏简单,有什么不足请大家指出。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值