算法的时间复杂度

算法的时间复杂度

  • ⭐算法效率
  • ⭐时间复杂度
  • 时间复杂度
  • 时间复杂度的概念
    • 例1
  • 大O的渐进表示法
    • 例2
    • 例3
    • 例4
    • 例5
    • 例6
    • 例7

⭐算法效率

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

⭐时间复杂度

一个程序的运行,依赖于其本身的算法设计以及不同的机器性能。单纯地只是从计算其运算时间的角度来评估一个算法是不合理的。
因此引进了时间复杂度

时间复杂度

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

时间复杂度的概念

一个算法所花费的时间与其中语句的执行次数成正比例。算法中的基本操作的执行次数,为算法的时间复杂度

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

Func1执行的基本操作次数
F(N)=N^2+2N+10
但实际上当N非常大时,最高数量级的N^2对F(N)会起到决定性影响。
并且在实际中,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

大O的渐进表示法

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

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

例2

// 计算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)=2N+10
我们只需要抓大头,for循环这条语句是最主要的。
因此Func2的时间复杂度为O(N)

例3

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

Func3函数中,M和N是两个未知数。
当M>>N时,算法的时间复杂度为:O(M)
当N>>M时,算法的时间复杂度为: O(N)
当M=N时,算法的时间复杂度为O(M)或者O(N)
但由于我们不清楚M和N的具体关系,因此这个算法的时间复杂度为:O(M+N)更准确。

例4

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

Func4是可以清楚地知道for语句语句会执行100次,因此这个算法的时间复杂度为O(1)

例5

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

如果这里换成1000,000实际上算法的时间复杂度还是O(1)。
这主要依赖于CPU的强大。CPU的运算速度是十分快的。
算法的时间复杂度_第1张图片
这是由于此,对于CPU而言,运行100和1000,1000000 语句,其时间都是一样的。

例6

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

这个库函数表示,在字符串s中查找字符c,返回字符c第一次在字符s中出现的位置,如果未找到字符c,则返回NULL。
这个需要分情况讨论。
比如说,调用这个函数,并传递参数,字符串"hello,world"。
最好的情况是,查找的字符,我们传递的是字符"h",那么这样就只进行了1次查找。
最坏的情况是,查找的字符,我们传递的参数是"d"那么这样就遍历了一次字符数组11次才查找到。或者我们传递的参数是"a",遍历了数组,没有查找到。
平均情况是,11/2=5次
在这种情况中我们关注的是算法最坏的情况。

另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界) 例如:在一个长度为N数组中搜索一个数据x
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

例7

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

这个算法是冒泡排序。
冒泡排序,是两个相邻元素进行比较,交换;重复这个过程,将最大(重)的沉下去。重复上述,将第二大再沉下去。…
最好情况是,无需进行交换。
最坏情况是,假如有n个数。
第一次交换为n-1次。
第二次交换为n-2次。
第三次交换为n-3次。

第n-1次交换为1次
所以总次数为
为(n-1)+(n-2)+(n-3)+…+1=[(n-1)+1]*(n-1)/2=n(n-1)/2次。
故冒泡算法的时间复杂度为
O(N^2)

你可能感兴趣的:(数据结构与算法,算法,数据结构,c语言)