> 文档中心 > 【Python数据结构与算法】(一)基本概念和算法分析

【Python数据结构与算法】(一)基本概念和算法分析


【Python数据结构算法】(一)基本概念和算法分析

  • ✨本文收录于《Python数据结构与算法》专栏,此专栏主要记录如何python学习数据结构与算法笔记以及练习题。
  • 🌸个人主页:JoJo的数据分析历险记
  • 📝个人介绍:小编大四统计在读,目前保研到统计学top3高校继续攻读统计研究生
  • 💌如果文章对你有帮助,欢迎✌关注、👍点赞、✌收藏、👍订阅专栏

文章目录

  • 【Python数据结构与算法】(一)基本概念和算法分析
  • 基本概念
  • 1. 数据结构
  • 2. 算法
  • 3. 算法分析
    • 3.1 时间复杂度
    • 3.2 空间复杂度
    • 3.3 时间复杂度的判断

基本概念

1. 数据结构

  • 数据结构 : 数据结构是一种特定的计算机存储、组织数据的方式。其宗旨是使计算机能够高效的使用数据。

  • 数据是一个抽象的概念,将其进行分类后得到程序设计语言中的基本类型。如:int,float,char等。数据元素之间不是独立的,存在特定的关系,这些关系便是结构。数据结构指数据对象中数据元素之间的关系。Python给我们提供了很多现成的数据结构类型,这些系统自己定义好的,不需要我们自己去定义的数据结构叫做Python的内置数据结构,比如列表、元组、字典。而有些数据组织方式,Python系统里面没有直接定义,需要我们自己去定义实现这些数据的组织方式,这些数据组织方式称之为Python的扩展数据结构,比如栈,队列等。

  • 抽象的数据类型(ADT):数列,树,表格等等。

  • 对于某一类型的数据或者某一个数据集以及对该数据集的各种操作
    即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。

2. 算法

算法基本概念: 是在有限的时间内解决问题的一系列指令。

效率
  • 时间:执行算法所花的时间

  • 空间:执行算法所占的内存

因此在对比两个算法时,如果一个算法的时间比较快,但是占用的空间太大, 也不能说明这个算法就好

目标
  • 首先程序能够解决问题
  • 设计能够高效解决问题的数据结构与算法
  • 评价程序的效率和正确性
算法面试时需要注意的问题:
  • 明确题目需求,要主动与面试官交流

  • 给出思路

  • 分析出时间复杂度和空间复杂度

  • 写出程序

  • 写的程序能不能给出简单的测试案例

案例1:找出丢失的数字

假设有n-1个数字,这些数字的范围是[1,n],且n-1个数字没有重复,请找出缺失的数字.

明确题目需求:这些数字是不是排好序的?

给出思路:先排序在进行二分查找;求和相减;使用异或来解决问题。

3. 算法分析

3.1 时间复杂度

时间复杂度就是用来估计算法的运行时间的一个式子(单位)。
  • 最好的情况:算法完成工作最少运行时间
  • 最差的情况:算法完成工作最多运行时间
  • 平均情况:算法完成工作平均需要多少基本操作,即平均时间复杂度

对于最优时间复杂度,其价值不大,因为它没有提供什么有用信息,其反映的只是最乐观最理想的情况,没有参考价值。

对于最坏时间复杂度,提供了一种保证,表明算法在此种程度的基本操作中一定能完成工作。

对于平均时间复杂度,通常很难测定。

因此,我们通常关注算法的最差情况的运行时间

“大O记法”:

对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=c*g(n),就说函数g是f的一个渐近函数(忽略常数),记为f(n)=O(g(n))。也就是说,在趋向无穷的极限意义下,函数f的增长速度受到函数g的约束,亦即函数f与函数g的特征相似。类似数学分析中的高阶无穷小的思想,大家可以这样去理解。

时间复杂度:

假设存在函数g,使得算法A处理规模为n的问题示例所用时间为T(n)=O(g(n)),则称O(g(n))为算法A的渐近时间复杂度,简称时间复杂度。

3.2 空间复杂度

空间复杂度:评估算法内存占用大小的式子

表达方式也是用大O的方法。

  • 算法使用了几个变量:O ( 1 ) O(1) O(1)
  • 算法使用了长度为n的一维列表:O ( n ) O(n) O(n)
  • 算法使用了m行n列的二维列表:O ( m n ) O(mn) O(mn)

3.3 时间复杂度的判断

常见时间复杂度如下:

O ( 1 ) < O ( l o g n ) < O ( n ) < O ( n l o g n ) < O ( n 2 ) < O ( 2 n ) O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(2^n) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2n),其中 O ( l o g n ) = O ( l o g 2 n ) O(logn)=O(log_2n) O(logn)=O(log2n)

具体的案例说明如下:
  • O(1):
print("Hello World")
  • O(n):
for i in range(n):    print('Hello World')
  • O (n2 ) O(n^2) O(n2)
for i in range(n):    for j in range(n): print('Hello World')

注意: 这里的时间复杂度相当于一个单位,因此我们只关注最高阶的情况。例如:

for i in range(n):    print("Hello World")    for j in range(n): print('Hello World')

上面这个虽然相当于是 n 2 + n n^2+n n2+n个计算,但是我们记为 O ( n 2 ) O(n^2) O(n2)。可以看出一个循环相当于一个n,如果减半呢?

  • O ( l o g n ) O(logn) O(logn)
while n > 1:    print(n)    n = n//2

此时循环减半,它的复杂度是 O ( l o g n ) O(logn) O(logn),例如,当n=64时候,打印的输出为:64,32,16,8,4,2.一共六次,等价于 l o g 2 64 = 6 log_264=6 log264=6

如何快速判断算法复杂度:

下面介绍最基本情况下如何判断算法复杂度。

  • 确定问题规模n
  • 如果有循环减半——l o g n logn logn
  • k层关于n的循环—— nk n^k nk
主项定理:

上面的方法是判断一些最基本的简单的情况,下面我们介绍一种处理更复杂情况的判断方法:主项定理
假设 a ≥ 1 , b > 1 , f ( n ) a\geq1,b>1,f(n) a1b>1,f(n)为一个函数, T ( n ) T(n) T(n)由递归式:
T ( n ) = a T (nb ) + f ( n ) T(n) = aT(\frac{n}{b})+f(n) T(n)=aT(bn)+f(n)
T ( n ) T(n) T(n)的渐进有如下情况:

(1)如果 f ( n ) < nlo g b a f(n)<n^{log_ba} f(n)<nlogba, T ( n ) = O ( nlo g b a ) T(n)=O(n^{log_ba}) T(n)=O(nlogba)

(2)如果 f ( n ) = nlo g b a f(n)=n^{log_ba} f(n)=nlogba T ( n ) = O ( nlo g b a f ( n ) ) T(n)=O(n^{log_ba}f(n)) T(n)=O(nlogbaf(n))

(3)如果 f ( n ) > nlo g b a f(n)>n^{log_ba} f(n)>nlogba T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))

下面我们来看一下我们刚刚那个例子

while n>1:    print(n)    n = n//2

上面这个代码每一次都把数据分成两部分,然后打印。那么按主项定理,可以写出一下式子:
T ( n ) = T (n2 ) + 1 T(n) = T(\frac{n}{2})+1 T(n)=T(2n)+1
此时 a = 1 , b = 2 , f ( n ) = 1 = n 0 a=1,b=2,f(n)=1=n^0 a=1,b=2,f(n)=1=n0,计算 l o g b a = l o g 2 1 = 0 log_ba=log_21=0 logba=log21=0,因此 f ( n ) = nlo g b a f(n)=n^{log_ba} f(n)=nlogba,属于第二种情况,其时间复杂度为: O ( n 0 l o g n ) = O ( l o g n ) O(n^0logn)=O(logn) O(n0logn)=O(logn)和我们之前结果一致。

在这里插入图片描述
本章的介绍到此介绍,如果文章对你有帮助,请多多点赞、收藏、评论、关注支持!!

MSDN工具下载