c中的基础算法

本文详细介绍了算法的时间复杂度,包括时间频度、时间复杂度的概念以及如何根据时间频度求复杂度。文章通过实例展示了如何分析算法执行次数并推算时间复杂度,同时讲解了常见时间复杂度的比较。此外,还提到了几种基础排序算法的时间复杂度及其优缺点。
摘要由CSDN通过智能技术生成

1.时间复杂度

算法:由专门的人设计出来,应用在特定领域,解决特定问题的处理过程或者处理模型。
两个指标:时间复杂度/空间复杂度
算法最优解:理论上的时间复杂度 + 空间复杂度 + 实际压力测试

1.1 关于时间频度
请看示例:

int aFunc(void) {
   
printf("Hello, World!\n");      		//  需要执行 1 次
return 0;                        // 	需要执行 1 次
}

执行以上代码,需要执行2次运算

再看示例:

int aFunc(int n) {
   
for(int i = 0; i<n; i++) {
            		// 需要执行 (n + 1) 次
printf("Hello, World!\n");      	// 需要执行 n 次
}
return 0;       						// 需要执行 1 次
}

执行以上代码,需要执行 (n + 1 + n + 1) = 2n + 2 次运算

我们把算法需要执行的运算次数用输入大小n的函数表示,即T(n),也称为时间频度 。


1.2 关于时间复杂度

在前面介绍到的时间频度中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。
为此,我们引入时间复杂度概念。

为了估算算法需要的运行时间和简化算法分析,我们引入时间复杂度的概念。

一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示。

若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n))

则可以称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

这样用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。

一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

常见的算法时间复杂度以及效率上的高低顺序:
O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }

1.3 根据时间频度求复杂度

当我们拿到算法的执行次数函数 T(n) 之后怎么得到算法的时间复杂度呢?

步骤主要分为以下几步:
1)去掉运行时间中的所有加法常数。
2)在修改后的运行次数函数中,只保留最髙阶项。
3)如果最高阶项存在且不是1,则去除与这个项相乘的常数。

示例一:普通求和算法

#include <stdio.h>
int main()
{
   
    int i, sum = 0, n = 100;	/* 执行1次 */
    for( i = 1; i <= n; i++)	/* 执行 n+1 次 */
    {
   
        sum = sum + i;			/* 执行n次 */
        //printf("%d \n", sum);
    }
    printf("%d", sum);			/* 执行1次 */

分析过程:
1)该算法所用的时间(算法语句执行的总次数)为:
f(n) = 1 + ( n + 1 ) + n + 1 = 2n + 3

2)保留最髙阶项
而当n不断增大,比如我们这次所要计算的不是 1 + 2 + 3 + 4 + … + 100 = ? 而是 1 + 2 + 3 + 4 +… + n = ?
其中 n 是一个十分大的数字,那么由此可见,上述算法的执行总次数(所需时间)会随着 n 的增大而增加,但是在 for 循环以外的语句并不受 n 的规模影响(永远都只执行一次)。

故:上述算法的执行总次数简单的记做: 2n

3)去除与最高项相乘的常数

即2n变为n

所以,算法的时间复杂度为:O(n)

示例2:高斯求和算法

int main()
{
   
    int sum = 0, n = 100;	/* 执行1次 */
    sum = (1 + n) * n/2;	/* 执行1次 */
 
    printf("%d", sum);		/* 执行1次 */
}

这个算法的时间复杂度: O(3),但一般记作 O(1)。
从感官上我们就不难看出,从算法的效率上看,O(3) < O(n) 的,所以高斯的算法更快,更优秀。

练习:试着推算下如下算法的时间复杂度:

int main()
{
   
    int i, j, x = 0, sum = 0, n = 100;		/* 执行1次 */
    for( i = 1; i <= n; i++)
    {
   
        sum = sum + i;
        //printf("%d \n", sum);
        for( j = 1; j <= n; j++)
        {
   
            x++;                			/* 执行n*n次 */
            sum = sum + x;
        }
    }
    printf("%d", sum);						/* 执行1次 */
}

分析过程:

1)先计算执行总次数:
执行总次数 = 1 + (n + 1) + n*(n + 1) + n*n + (n + 1) + 1 = 2n2 + 3n + 3

2)保留最髙阶项
这里的最高阶是 n 的二次方,所以算式变为:执行总次数 = 2n^2

3)去除与最高项相乘的常数
这里 n 的二次方不是 1 所以要去除这个项的相乘常数,算式变为:执行总次数 = n^2

因此最后我们得到上面那段代码的算法时间复杂度表示为: O( n^2 )

练习:

  1. 求该方法的时间复杂度
void aFunc(int n) 
{
   
    for (int i = 0; i < n; i++) 
	{
   
        for (int j = i; j < n; j++) 
		{
   
            printf("Hello World\n");
        }
    }
}

执行总次数 = (n+1) + n*(n+1) + n*n = 2n^2 + 2n +1 = O(n^2)

  1. 求该方法的时间复杂度
void aFunc(int n) 
{
   
    for (int i = 2; i < n; i++) 
	{
   
        i *= 2;
        printf("%i\n", i);
    }
}

执行总次数 = (n+1) + n + n = 3n+1 = O(n)

  1. 求该方法的时间复杂度
long aFunc(int n) 
{
   
    if (n <= 1) 
	{
   
        return 1;
    } else 
	{
   
        return aFunc(n - 1) + aFunc(n - 2);
    }
}

显然运行次数,T(0) = T(1) = 1,同时 T(n) = T(n - 1) + T(n - 2) + 1,这里的1是其中的加法算一次执行。
显然 T(n) =T(n-1)+T(n-2)是一个斐波那契数列,通过归纳证明法可以证明,当n>=1时,T(n)<(5/3)^n,同时当 n > 4 时 T(n) >= (3/2)^n。
所以该方法的时间复杂度可以表示为 O((5/3)^n),简化后为 O(2^n)。

2.常用排序查找算法

2.1 排序算法

2.1.1 冒泡排序
1)实现过程:
从头开始遍历,相邻两个数比较,如果后面比前面小,就交换。然后再从头遍历n遍。实际上是把最大的数排到了后面,后面是已经排好序的。

2)用c实现最优解测试

    void bubble_sort(int n,int *a)
    {
   
        int i,j,temp;
        for(j=0;j<n;j++)
        {
   
            for(i=0;i<n-j;i++)
            {
   
                if(a[i] > a[i+1])
                {
   
                    temp = a[i];
                    a[i] = a[i+1];
                    a[i+1] = temp;
                }
            }
        }
        for(i=0;i<n;i++)
            printf("a[%d]=%d\t",i,a[i]);
    }

3)计算出时间/空间复杂度,并总结优缺点

2.1.2 选择排序
1)实现过程:
原理:从后面挑出最小的值,放在第一个,依次循环.
过程:标记最小的那个数的下标,与第一个交换,依次循环.

2)用c实现最优解测试

    void select(int n,int *a)
    {
   
        int i,j,temp,min;
        min = a[0];
        
        for(j=0;j<n;j++)
        {
   
            for(i=j;i<n;i++)
            {
   
                if(min >= a[i])
                {
   
                    min = a[i];
                    temp = a[j];
                    a[j] = a[i];
                    a[i] = temp;
                }
            }
        }
        
        for(i=0;i<n;i++)
            printf("a[%d]=%d\t",i,a[i]);
        printf("\n");
    }

3)计算出时间/空间复杂度,并总结优缺点
时间复杂度:O(n^2)
优缺点:选择排序比冒泡排序交换次数少
冒泡排序稳定性好,冒泡最好是O(n),选择排序最好最坏都是O(n^2)
稳定性:5 8 5 2 9 第一遍第一个元素5会和2交换,那么原序中两个5相对位置就改变
不稳定性:我们现在所做的拿数据简单的做测试对破坏顺序当然没什么关系。但是如果是那结构体来排序呢?比如一个结构体里面包含一个人的学号和分数,要求在分数相等的情况下再按照学号排序,这个时候顺序就有关系了

2.1.3 插入排序
1)实现过程:
1)从第一个元素开始,该元素可以认为已经被排序
2)取出下一个元素,在已经排序的元素序列中从后向前扫描
3)如果该元素(已排序)大于新元素,将该元素移到下一位置
4)重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5)将新元素插入到该位置中
6)重复步骤2

2)用c实现最优解测试

    void insert(int n,int *a)
    {
        int i,j,temp;
        
        for(j=1;j<n;j++)
        {
            temp = a[j];
            for(i=j-1;i>=0 && temp<a[i];i--)
                a[i+1] = a[i];
            a[i+1] = temp;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值