数据结构(python版)—— 2、前期知识与算法分析

发布时间 2023-06-15 16:48:08作者: 重拾初心的青年人

从C转到python(一)

C:hello World!

#include <stdio.h>
​
int main()
{
    // say hello
    printf("Hello World!\n")
}

1-Compile编译到机器码

2-Link与各种库链接

3-Execute执行目标程序

Python:Hello World!

def main():
    # say hello
    print("Hello World!")
​
main()

1- Run!跑代码

# say hello
printf("Hello World!") # main都是太啰嗦,一行OK

从C转python(二)

高斯1加到100

# include <stdio.h>
​
int main()
{
    int s,i;
    s = 0;
    // adding numbers 1 to 100
    for(i = 0;i < 100; i++)
    {
        s += (i + 1);
    }
    printf("Sum= %d",s);
}
s = 0
# adding number 1 to 100
for i in range(100):
    s += (i + 1)
print("Sum=",s)
​

从C转python(三)

c:检验素数

#include <stdio.h>
#include <math.h>
​
int main()
{
    int n,i,flag = 0;
    printf("Please input number:")
    scanf("%d",&n);
    for(i = 2;i < sqrt(n);i++)
        if(n % i == 0)
        {
            flag = 1;
            break;
        }
    if(flag == 1)
        printf("%d is NOT a prime number.",n);
    else
        printf("%d is a prime number.",n);
}
from math import sqrt
n = int(input("Please input number:"))
for i in range(2,int(sqrt(n))):
    if n % i == 0:
        print(f"{n} is NOT a prime number.")
        break
else:
    print(f"{n} is a prime number.")

从C转python(四)

C:打印一个朴素的三角形

#include <stdio.h>
​
int main(){
    int n,i,j;
    printf("Please input number:");
    for(i = 0; i < n; i++){
        for(j = 0;j < i;i++)
            printf("*");
        printf("\n");
    }
}
n = int(input("Please input number:"))
for i in range(n):
    print("*" * i)

人生苦短,我用python

对比程序,还是算法?

如何对比两个程序?

看起来不同,但解决同一个问题的程序,哪个“更好”?

程序和算法的区别

算法是对问题解决的分布描述

程序则是采用某种编程语言实现的算法,同一个算法通过不同的程序员采用不同的编程语言,能产生很多程序

累积求和问题

我们来看一段程序,完成从1到n的累加,输出总和

设置累计变量=0

从1到n循环,逐次累加到累计变量

返回累计变量

def sum0fN(n):
    theSum = 0
    for i in range(1, n + 1):
        theSum = theSum + i
    return theSum
print(sum0fN(10))
    

再看第二段程序是否感觉怪怪的?

但实际上本程序功能与前面那段相同

这段程序失败之处在于:变量命名词不达意,以及包含了无用的垃圾代码

def foo(tom):
    fred = 0
    for bill in range(1,tom + 1):
        barney = bill
        fred = fred + barney
    return fred
    
print(foo(10))

算法分析的概念

比较程序的“好坏”,有更多因素

代码风格、可读性等等

我们主要感兴趣的是算法本身特性

算法分析主要就是从计算资源消耗的角度来批判和比较算法

更高效利用计算资源,或者更少占用计算资源的算法,就是好算法

从这个角度,前述两段程序实际上是基本相同的,它们都采用了一样的算法来解决累计求和问题

说到代码风格和可读性

为什么Python的强制缩进是好的?

语句快功能和视觉效果统一

说到代码风格和可读性

代码不像看起来那样运行

image.png

计算资源指标

那么何为计算资源?

一种是算法解决问题过程中需要的存储空间或内存

但存储空间受到问题自身数据规模的变化影响要区分哪些存储空间是问题本身描述所需,哪些是算法占用,不容易

另一种是算法的执行时间

我们可以对程序进行实际运行测试,获得真实的运行时间

运行时间检测

Python中有一个time模块,可以获取计算机系统当前时间

在算法开始前和结束后分别记录系统时间,即可得到运行时间

import time
time.time()

累计求和程序的运行时间检测

用time检测总运行时间

返回累计和,以及运行时间(秒)

import time
start = time.time()
....
end = time.time()
t = end - start

在交互窗口连续运行5次看看

1到10000累加

每次运行约需0.0007秒

第二种无迭代的累计算法

利用求和公式的无迭代算法

def sum0fN3(n):
    start = time.time()
    theSum = (n * (n + 1)) / 2
    end = time.time()
    return theSum,end - start

采用同样的方法检测运行时间

10,000;100,000;1,000,000

10,000,000;100,000,000

需要关注的两点

这种算法的运行时间比前种都短很多

运行时间与累计对象n的大小没有关系(前种算法是倍数增长关系)

新算法运行时间几乎与需要累计的数目无关

观察一下第一种迭代算法

包含了一个循环,可能会执行更多语句

这个循环运行次数跟累加值n有关系,n增加,循环次数也增加

def sum0fN(n):
    theSum = 0
    for i in range(1,n + 1):
        theSum = theSum + i
    return theSum
print(sum0fN(10))

运行时间检测的分析

但关于运行时间的实际检测,有点问题

关于编程语言和运行环境

同一个算法,采用不用的编程语言编写,放在不同的机器上运行,得到的运行时间会不一样,有时候会大不一样:

比如吧非迭代算法放在老旧机器上跑,甚至可能慢过新机器上的迭代算法

我们需要更好的方法来衡量算法运行时间

这个指标与具体的机器、程序、运行时段都无关

算法时间度量指标

一个算法所实施的操作数量或步骤数可作为独立于具体程序/机器的度量指标

哪种操作跟算法的具体实现无关?

需要一种通用的基本操作来作为运行步骤的计量单位

赋值语句是一个合适的选择

一条赋值语句同时包含了(表达式)计算和(变量)存储两个基本资源

仔细观察程序设计语言特性,除了与计算资源无关的定义语句外,主要就是三种控制流语句和赋值语句,而控制流仅仅起了组织语句的作用,并不实施处理。

赋值语句执行次数

分析SumOfN的赋值语句执行次数

对于“问题规模”n

赋值语句数量T(n)=1+n

那么,什么是问题规模?

def sum0fN(n):
    theSum = 0
    for i in range(1,n+1):
        theSum = theSum + i
    return theSum

问题规模影响算法执行时间

问题规模:影响算法执行时间的主要因素

在前n个整数累计求和的算法中,需要累计的整数个数合适作为问题规模的指标

前100,000个整数求和对比前1,000个整数求和,算是同一问题的更大规模

算法分析的目标是要找出问题规模会怎么影响一个算法的执行时间

数量级函数Order of Magnitude

基本操作数量函数T(n)的精确值并不是特别重要,重要的是T(n)中起决定性因素的主导部分

用动态的眼光看,就是当问题规模增大的时候,T(n)中的一些部分会盖过其它部分的贡献

数量级函数描述了T(n)中随着n增加而增加速度最快的主导部分

称作“大O”表达法,记作O(f(n)),其中f(n)表示T(n)中的主导部分

确定运行时间数量级大O的方法

例1:T(n)=1+n

当n增大时,常数1在最终结果中显得越来越无足轻重

所以可以去掉1,保留n作为主要部分,运行时间数量级就是O(n)

例2:T(n)=5n平方+27n+1005

image.png

影响算法运行时间的其它因素

有时决定运行时间的不仅是问题规模

某些具体数据也会影响算法运行时间

分为最好、最差和平均情况,平均状况体现了算法的主流性能

对算法的分析要看主流,而不被某几种特定的运行状况所迷惑

常见的大O数量级函数

通常当n较小时,难以确定其数量级

当n增长到较大是,容易看出其主要变化量级

image.png

image.png

image.png

image.png

“变位词”判断问题

问题描述

所谓“变位词”是指两个词之间存在组成字母的重新排列关系

如:heart和earth,python和typhon

为了简单起见,假设参与判断和两个词仅由小写字母构成,而且长度相等

解题目标:写一个bool函数,以两个词作为参数,返回这两个词是否变位词

可以很好展示同一问题的不同数量级算法

解法1:逐字检查

解法思路

将词1中的字符逐个到词2中检查是否存在

存在就“打勾”标记(防止重复检查)

如果每个字符都能找到,则两个词是变位词

只要有1个字符找不到,就不是变位词

image.png

程序技巧

实现“打勾”标记:将词2对应字符设为None

由于字符串是不可变类型,需要先复制到列表中

image.png

代码:

image.png

image.png

解法2:排序比较

解题思路

将两个字符串都按照字母顺序排好序

再逐个字符对比是否相同,如果相同则是变位词

有任何不同就不是变位词

image.png
排序比较-算法分析

image.png

解法3:暴力法

image.png
算法分析:

image.png

解法4:计数比较

image.png

程序代码

image.png

算法分析

image.png

image.png

Python数据类型的性能

前面我们了解了“大O表示法”以及对不同的算法的评估

下面来讨论下Python两种内置数据类型上各种操作的大O数量级

列表list和字典dict

这是两种重要的Python数据类型,后面的教程会用来实现各种数据结构

通过运行试验来估计其各种操作运行时间数量级

image.png

List列表

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

from __main__ import x

image.png

image.png

image.png

image.png

dict数据类型

image.png

image.png

image.png

image.png