python3字典为什么有须的_21.揭秘Python字典类型背后的秘密:为什么有了列表,还需要字典...

百家号不支持代码格式,文章里的代码排版都是乱的。

如果需要拷贝代码,可以去同名微信公众号。

在教小朋友Python字典类型时,她问了一个非常好的问题:既然都是作为“容器”,为什么有列表了,还需要字典?

给定一个列表,包含3万个随机整数, 和一个目标数。求列表里有多少组数字,两数之和等于目标数。

nums = random.sample(range(1,100000),30000)

target = 88888

如果不考虑时间复杂度,用一个简单的嵌套循环就可以了。

cn = 0

result = []

print(datetime.datetime.now())

for i in range(len(nums)):

for j in range(i+1,len(nums)):

cn += 1

if nums[j] == target - nums[i] :

result.append([nums[i],nums[j]])

print(datetime.datetime.now())

print(cn)

但是如果考虑时间复杂度。

程序外层 i 循环 3万次,而内层j分别循环 29999,29998,29997……,1次

所以,一共要循环 (n * (n-1)) /2 = 449985000次。

如下图,我们用了一个计数器变量cn,每次循环,对 cn 加1。

我们发现,程序果然执行了 4亿多次,用了2分多钟。

根据前面学习过的时间复杂度的知识。

这个程序的时间复杂度是 O( n ** 2)。

如果列表里有10万个元素,需要循环近50亿次。

如果列表里有100万个元素,则需要循环近5000亿次。

那有没有办法可以降低程序的时间复杂度呢?只要1分钟,甚至1秒钟就能完成100万个元素的计算呢?

答案是肯定的,不过我们先来介绍Python里的另外两种容器类型:元组 和 字典

元组

元组类型和列表类型非常相似。

不同之处在于列表用中括号,元组用小括号(或者没有括号)

最重要的不同是 元组里的元素不能修改。

tup1 = (1,2,3,4,5)

tup2 = (1,'a',3,[1,23,2])

tup3 = 'abc',1,2,'d'

从元组里获取元素的方法也和列表一致,可以使用下标或者下标切片。

但是不要试图去改变元组里的元素。比如

tup1[2] = 8

思考一下,下面的操作会报错吗?

tup1 = tup1 +tup2

字典

元组用小括号,列表用中括号,而字典用大括号。

并且和元组、列表不同,字典里的每个元素都要写成 key:value的形式。

形如

d1 = {key1:value1,key2:value2}

比如说某班同学的语、数、外考试成绩

d1 = {'Cherry':[90,89,100],'Mark':[100,90,100],'Ruby':[100,100,100]}

上面的字典实例 d1里,有3个元素。分别是

'Cherry':[90,89,100]

'Mark':[100,90,100]

'Ruby':[100,100,100]

而要从字典里获取元素,也不能再使用下标了。而是要用 d1[key] 的方式。

所以下面的程序

d1['Ruby']

是正确的,而

d1[2]

就会报错!

在字典里,key必须是唯一的。

比如说如果字典里还没有'Tom' 这个 key。

d1['Tom'] = [80,90,80]

就是在字典 d1里新增了一个元素,这个元素的key是 'Tom',value是 [80,90,80]。

而在上面的程序执行完之后,字典里已经有'Tom'这个 key了,再执行

d1['Tom'] = [50,50,60]

就是在修改这个元素的value。字典里不会出现2个'Tom'。

因为字典不能通过下标遍历,所以,如果我想在字典里查找某个key是否存在,或者遍历整个字典时,Python提供了下列2种办法。

key in dict

if 'Ruby' in d1:

print('Ruby in d1')

因为d1 里有‘Ruby’这个key,所以程序里的 if 判断是成立的。

items()函数

for k,v in d1.items():

print(k , ' ', v)

上面的程序相当于遍历了字典里的每一个元素,而变量 k, v 依次等于每个元素的key和value。

用字典,从2分钟变成0.2毫秒

接下来,我们用上面刚学的字典类型,看看是否真的能更快。

因为程序并不复杂,所以直接先看程序。

d1 = {}

result = []

print(datetime.datetime.now())

for k in range(len(nums)):

d1[ nums[k] ] = k

for i in range(len(nums)):

minus = target - nums[i]

if minus in d1:

result.append([nums[i],minus])

print(datetime.datetime.now())

同样是3万个随机整数。

使用列表,用了2分多种,而使用字典,只用了0.2毫秒。

奥秘在什么地方呢?

1. 下面这段代码是把列表里的3万个元素存储到字典里。

因为要遍历列表,所以会循环3万次。

如果不理解的话,看下面这个简单的示例。

2. 下面这段代码外层还是遍历列表,每循环一个元素,都用目标数减去这个元素得到差 minus,然后看 minus 是否在 字典 d1里。

显然外层还是需要循环 3 万次。

外层每循环一次,都要执行一次 minus in d1,从d1里查找 minus。

d1也是3 万个元素,看上去,还是要循环 3万 * 3万 = 9 亿。

但是,虽然 d1有3万个元素,但是minus in d1,非常非常非常快!

快到可以忽略不计。

所以,我们可以认为这段程序只循环了3万次,而非9亿次。

为什么字典查找这么快?

为什么在字典里查找key非常快呢?

前面在介绍列表时,我们说过。Python里任何一个实例都住在“小房子”里,每个房子都有自己的地址。

内置函数id(),可以返回这个地址。

如下图,列表里的每个元素,和字典里的每个元素都有不同的地址。

假设下图是 100层,每层300个房间的大楼。也即一共有 100*300=3万个房间。

每个房间都有一个地址,不妨就用 001001表示第1层第1间房,001002表示第1层第2间房。

现在来了一个人要入住。

['Jim',100,90,100]

如果我们用列表 l1 来表示这栋大楼。下面是入住的代码。

l1.append(['Jim',100,90,100])

l1里可能已经住了很多人了,所以‘Jim’被安排在了 888666号房间。

有一天,警察怀疑‘Jim’是坏人,来抓他。

因为不知道‘Jim’的房间号,只能通过for循环。

最好的情况是‘Jim’住在001001,只要敲一个房间的门。

最坏的情况是‘Jim’住在100300,要敲3万个房间的门。

for i in range(len(l1)):

if 'Jim' == l1[i][0] :

抓起来

如果我们用字典d1来表示这栋大楼。入住的代码应该是

d1[ 'Jim' ] = [100,90,100]

这时,警察来抓‘Jim’,需要敲多少个房间的门呢?

答案是永远都只要敲一个房间的门!

因为通过 ‘Jim’这个key,就能计算出房间号。

在‘Jim’办理入住的时候,房间并不是随便分配的,而是通过一种神奇的函数计算出来。

这种神奇的函数就称为哈希(Hash)函数。

当然,这只是最基础的介绍,Python字典在具体实现哈希的时候,还有很多细节需要考虑。

比如,每个人的名字长度不一样,但经过哈希之后得到的房间号都是6位的。

再比如,不同名字的人,不能哈希到同一个房间号

等等等等

总而言之,Python字典因为在存储元素时,将key哈希转化到了存储地址,所以如果要在字典里查找某个元素的key,不需要循环字典,只需要进行同样的哈希计算就可以得到了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值