数据结构与算法基础

程序设计 = 数据结构 + 算法

数据结构分为逻辑结构与物理结构

逻辑结构:是指数据对象中数据元素之间的相互关系;物理结构:是指数据的逻辑结构在计算机中的存储形式。

逻辑结构可以分为:集合结构、线性结构、树形结构、图形结构。
在这里插入图片描述
数据元素的存储形式有两种:顺序存储和链式存储

顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。

链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以连续,也可以是不连续的。
在这里插入图片描述

算法:比如计算1+2+…+99+100 = ?如果一个数字一个数字的加便不是明智之举,而高斯利用利用自己总结出的公式 ( 1 + n ) ∗ n / 2 (1+n)*n / 2 (1+n)n/2 就可以轻松的计算1加到任何一个数字,而高斯总结的这个公式就可以看做一个算法。

算法具有五个基本特征:输入、输出、有穷性、确定性、可行性

输入:算法具有零个或多个输入。

输出:算法至少有一个或多个输出。

有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。

确定性:算法的每一个步骤都具有确定的含义,不会出现二义性;算法在一定条件下,只有一条执行路基你给,相同的输入只能有唯一的输出结果;算法的每个步骤都应该被精确定义而无歧义。

可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。

算法效率的度量方法

时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。
空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。

时间频度: 一个算法中的语句执行次数称为语句频度或时间频度。记为 T ( n ) T(n) T(n)

时间复杂度: 时间频度 T ( n ) T(n) T(n)中, n n n 称为问题的规模,当 n n n 不断变化时,时间频度 T ( n ) T(n) T(n)也会不断变化。一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用 T ( n ) T(n) T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,$T(n) /f(n) 的 极 限 值 为 不 等 于 零 的 常 数 , 则 称 的极限值为不等于零的常数,则称 f(n) 是 是 T(n) 的 同 数 量 级 函 数 , 记 作 的同数量级函数,记作 T(n)=O(f(n))$,它称为算法的渐进时间复杂度,简称时间复杂度

下图为 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)的大 O O O 时间复杂度

在这里插入图片描述

推导大O阶
推导大O阶,我们可以按照如下的规则来进行推导,得到的结果就是大O表示法:

  1. 用常数1来取代运行时间中所有加法常数。
  2. 修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数。

常数阶
在这里插入图片描述

int sum = 0, n = 100;
printf("Change the world by program!");
sum = (1+n)*n/2;

面算法的运行的次数的函数为 f ( n ) = 3 f(n)=3 f(n)=3,根据推导大 O O O阶的规则1,我们需要将常数3改为1,则这个算法的时间复杂度为 O ( 1 ) O(1) O(1)

线性阶

在这里插入图片描述

一般含有非嵌套循环设计线性阶,线性阶就是随着问题规模 n n n 的扩大,地应计算次数呈现直线增长。

int i, n = 100, sum = 0;
for( i = 0; i < n; i++)
{
    sum += i;
}

上面这个代码的时间复杂度就是 O ( n ) O(n) O(n)

平方阶
在这里插入图片描述

int i, j, n = 100;
for(i = 0; i < n; i++)
{
    for(j = 0; j < n; j++)
    {
        printf("I can make it\n");
    }
}

内层循环执行100次,外层循环执行100次,总共执行100*100 次,就是n的平方,即时间复杂度为 O ( n 2 ) O(n^2) O(n2) .

注意下面程序的时间复杂度:

int i, j, n = 100;
for(i = 0; i < n; i++)
{
    for(j = i; j < n; j++)
    {
        printf("I can make it\n");
    }
}

分析如上代码,由于当i=0时,内层循环执行了n次,当i = 1时,内循环执行n-1次…,当i = n -1 时,内循环执行1次,所以中的执行次数应该为: n + ( n − 1 ) + . . . + 2 + 1 = n ( n + 1 ) / 2 n + (n-1) + ... + 2 + 1 = n(n+1)/2 n+(n1)+...+2+1=n(n+1)/2 .

n ( n + 1 ) / 2 = n 2 / 2 + n / 2 n(n+1)/2 = n^2 / 2 + n / 2 n(n+1)/2=n2/2+n/2 , 根据推到大O的策略,第二条只保留最高项,所以 n / 2 n/2 n/2 去掉,第三条,去除最高次项相乘的常数,最终获得 O ( n 2 ) O(n^2) O(n2) .

对数阶
在这里插入图片描述

int i = 1, n = 100;
while(i < n) 
{
    i = i * 2;
}

由于每次 i * 2 之后,就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。

2 x = n 2^x=n 2x=n 得到 x = l o g 2 n x = log_2 n x=log2n ,所以这个循环的执行时间复杂度为 O ( l o g n ) O(log n) O(logn)

函数调用的时间复杂度分析
int i, j;
for(i = 0;i < n;i++)
{
    function(i);
}
void function(int count) {
    printf("%d", count);
}

function函数的时间复杂度为 O ( 1 ) O(1) O(1), 所以整体的时间复杂度就是循环的次数为 O ( n ) O(n) O(n).

int i, j;
for(i = 0;i < n;i++)
{
    function(i);
}
void function(int count) {
    int j;
    for(j = count; j < n; j++)
    {
        printf("%d", j);
    }
}

事实上此时的代码和平方阶举的第二个例子是一样的,function内部的循环次数随着count的增加而减少,根据之前的分析,该程序算法时间复杂度为 O ( n 2 ) O(n^2) O(n2);

n++; //1
function(n); //1 
for(i = 0; i < n; i++) 
{
    function(i);
}
for(i = 0; i < n; i++)
{
    for(j = i; j < n; j++)
    {
        printf("%d", j);
    }
}
void function(int count) {
    int j;
    for(j = count; j < n; j++)
    {
        printf("%d", j);
    }
}

在这里插入图片描述

此时的程序的算法时间复杂度还是 O ( n 2 ) O(n^2) O(n2)

常见的时间复杂度表

例子时间复杂度装逼术语
123456 O ( 1 ) O(1) O(1)常数阶
3 n + 4 3 n + 4 3n+4 O ( n ) O(n) O(n)线性阶
3 n 2 + 4 n + 5 3 n^2 + 4 n + 5 3n2+4n+5 O ( n 2 ) O(n^2) O(n2)平方阶
3 log ⁡ 2 n + 4 3 \log_2 n + 4 3log2n+4 O ( log ⁡ n ) O(\log n) O(logn)对数阶
2 n + 3 n log ⁡ 2 n + 14 2 n + 3 n \log_2 n + 14 2n+3nlog2n+14 O ( n log ⁡ n ) O(n\log n) O(nlogn)nlogn阶
n 3 + 2 n 2 + 4 n + 6 n^3 + 2 n^2 + 4 n + 6 n3+2n2+4n+6 O ( n 3 ) O(n^3) O(n3)立方阶
2 n 2^n 2n O ( 2 n ) O(2 ^ n) O(2n)指数阶

该表所对应的图:

在这里插入图片描述

常用的时间复杂度所耗费的时间从小到大依次为:

O ( 1 ) < O ( log ⁡ n ) < O ( n ) < O ( n log ⁡ n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

最坏时间复杂度与平均时间复杂度

例如我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为 O ( 1 ) O(1) O(1) ,最坏情况下就是这个数字在最后一个位置,那么时间复杂度为$O(n) $ .

平均运行时间就是期望的运行时间。

而我们平时进行时间复杂度分析一般只考虑最坏时间复杂度。

写代码的时候完全可以用空间来换取时间

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值