python数据结构与算法-基础概念

本文探讨了学习数据结构和算法的重要性,数据结构如列表和字典的性能分析,以及如何通过算法分析优化程序。举例介绍了不同算法的时间复杂度,如变位词检测的多种解决方案,强调在时间和空间复杂度之间找到平衡。同时,文章指出Python中列表和字典的操作复杂度,帮助理解其内在性能差异。
摘要由CSDN通过智能技术生成

写 在 前 面 \color{#FF3030}{写在前面}

- 为 什 么 要 学 数 据 结 构 ? \color{#FF3030}{为什么要学数据结构?}

“数据抽象”使得程序执行的细节被隐藏,而用户在更高层的封装后的交互界面执行程序,只需关注执行结果而不用了解抽象数据执行细节, 而抽象数据的执行,被称为“数据结构”。执行一个抽象数据通常有不同的方法,学习数据结构可使我们在不改变用户的交互界面与数据交互方式的前提下,对执行细节进行调整,使用户更专注与解决问题。

- 为 什 么 要 学 习 算 法 ? \color{#FF3030}{为什么要学习算法?}

在面对各种各样的问题时,会有不同的解决方法,各有优劣,我们可以将解决方法理解为“算法”,而通过评估不同“算法”的优劣性(例如:复杂度高低,运行快慢,占用内存大小),可使得计算机更高效的执行程序解决问题。


1.什么是算法分析

一个问题通常会有不同种解决方案,但可以实现相同的结果,那么评估各种方案的优劣性,我们可以称之为“算法分析”。

举个栗子:
问题:解决累加问题的函数,可以实现用户输入数字n,最终显示1+2+3…+n的结果。
方案1:迭代法求和

def sum1(n):
	res_sum = 0
	for i in range(1,n+1):
		res_sum = res_sum+i
	return (res_sum)
	print (res_sum)

方案2:无迭代求和

def sum2(n):
	res_sum=((1+n)*n)/2
	print(res_sum)

通过调用时间函数其实可以发现,方案1迭代求和时,随着数字增大,程序所需的运行时间会越来越长;方案2无迭代求和时,无论数字多大,程序运行所需时间基本都相同。调用时间函数来计算程序运行时间太麻烦,我们可以用用一种更好更方便的度量指标来计算程序运行时间,那么就引出了下面的“大O表示法”。

1.1大“O”表示法好

当我们以时间作为度量指标去描述一个算法的效率时,我们要清楚一个程序从开始执行到执行结束一共进行了多少步,我们把每一步看作一个基本度量单位,就可以表示出计算机解决某一问题所需的时间。
还是以上面的两个方案为例子:
方案1中以1个赋值语句(res_sum=0)为1步,那么计算sum1(n)的赋值语句(res_sum=res_sum+i)的步数为n+1步,所需的时间我们可以表示为O(n+1),当n趋于无穷大时,1对结果的影响可忽略不计,对结果起主导作用的部分为n,那么方案1执行完毕所需时间为O(n)。
方案2中计算结果只需1步,那么它所需时间为O(1)。

假设一个方案3,它的循环嵌套里有两个相互嵌套的for语句,例如:

for i in range(n):
	for j in range(n):

那么上述这个程序它执行所需时间就为O( n 2 n^{2} n2)


常见函数的大O表示法(时间复杂度依次上升):

f(n)函数名
1常数函数
log n对数函数
n线性函数
nlog n线性对数函数
n 2 n^{2} n2二次函数
n 2 n^{2} n2三次函数
2 n 2^{n} 2n指数函数
n!n的阶乘

1.2变位词检测

经典的字符串变位词检测时比较不同数量级函数的一个典型例子,比如"heart"和"earth"互为变位词。假设两个字符串的长度一样,并且都来自于26个英文字母,我们需要编写一个接受两个字符串,返回真假,来判断是否为变位词。

方案1:检查第一个字符串中的每个字符是否都在第二个字符串中,如果在,则用NULL标记,因为字符串不可变,所以需要将第二个字符串转换为列表。

def anagram_solution1(s1,s2):
	a_list = list(s2)
	pos1 = 0
	still_ok= True
	while pos1<len(s1) and still_ok:
		pos2 = 0
		found = False
		while pos2<len(a_list) and not found:
			if s1[pos1]==a_list[pos2]:
				found = True
			else:
				pos2 = pos2 + 1
		if found:
			a_list[pos2] = None
		else:
			still_ok = False
		pos1=pos1+1
	return still_ok

s1中n个字符串中的每一个都会迭代到s2列表中的n个字符,所以时间复杂度为O( n 2 n^{2} n2)

方案2:排序比较法,可以将s1与s2字符串转换成列表,用sort()函数对他们从a到z进行一个排序,若为变位词,那么他们排序后的结果就相同。

def anagram_solution2(s1,s2):
	a_list=list(s1)
	b_list=list(s2)
	a_list.sort()
	b_list.sort()
	pos=0
	matches=True
	while pos<len(s1) and matches:
		if a_list[pos]==b_list[pos]:
			pos = pos+1
		else:
			matches=False
	return matches
		

一眼看上去时间复杂度好像时O(n),而实际上sort()函数也是有消耗的,所以排序法的复杂度为O( n 2 n^{2} n2)或者O(nlog n)

方案3:暴力匹配算法,我们可以建立一个s1中所有字符组成的所有字符串的列表,并检查s2是否在前面的列表中,当构造s1所有字符串时,第一个字符串有n种可能,第二个有n-1种,以此类推,所有可能的字符串总数时n*(n-1)* (n-2)* …* 3 * 2 *1种,也就是n!个字符串。时间复杂度为O(n!)

方案4:计数比较法,因为变位词都有相同数量的a,相同数量的b…,由于a-z共有26个字符,我们可以用有26个计数器的列表,每个计数器队医一个字符,放我们没看到一个字符,就在相对应的计数器上加1,最终两个计数器的列表相同则互为变位词。
时间复杂度为O(n)。

def anagram_solution4(s1,s2):
	c1=[0]*26
	c2=[0]*26
	for i in range(len(s1)):
		pos1 = ord(s1[i])-ord('a')
		c1[pos1] = c1[pos1]+1
	for j in range(len(s2)):
		pos2 = ord(s2[j]) - ord('a')
		c2[pos2] = c2[pos2]+1
	num = 0
	still_ok = True
	while num<26 and still_ok:
		if c1[num] == c2[num]:
			num = num+1
		else:
			still_ok = False
	return still_ok

由上可见,方案4的时间复杂度是较低的,但是这是使用了额外的空间来达到目的的。所以很多情况下我们需要权衡时间和空间后做出选择,以期得到更优的解决方案。

2.python数据结构的性能

列表和字典是python中的基础构件,了解学习他们的大O复杂度,对于我们去理解python数据结构的性能是非常重要的。

2.1列表list

首先我们先看一下用四种不同的方法生成从0到男的一个列表。

def test1(n):
	l = []
	for i in range(n)
	l = l + [i]

def test2(n):
	l = []
	for i in range(n):
	l.append(i)
	return l

def test3(n):
	l = [i for i in range(n)]

def test4(n):
	l=list(range(n))

大家可尝试通过加入timeit模块,创建一个Timer对象,计算每种方法的运行时间。
这里给出一个list操作的大O复杂度表:
列表操作大O复杂度表
这里需要注意append时间复杂度优于insert,pop()优于pop(i)那是因为数据结构导致了操作前面位置的元素要挪动该元素之后的元素。

2.2字典dict

字典与列表的不同之处在于字典需要一个键(key)来访问对应的值,dict值的访问和赋值的时间复杂度都为O(1),这里给出一个字典的其他操作的时间复杂度表:

操作复杂度
赋值O(1)
访问O(1)
赋值O(1)
删除O(1)
包含(in)O(1)
迭代O(n)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值