赞
踩
最近在看Python数据结构,对字典有了新的认识,mark一下。
Python的数据类型,以列表和字典的使用最为广泛,其中列表以其强大的增删改查,备受人们的青睐,我个人也特别喜欢列表。但当列表数据过多时,需要查询第n个数据,其性能则为O(n),此时字典就登场了,以其强大的底层结构,可以做到查询为O(1),即常量查询,那原因是什么呢?
【表1-1】
回到字典,散列表的底层是稀疏数组,其内部元素单元称为表元,而表元则分为value和key,value可以是任意值,但key必须是散列值(相当于数组中元素的内存地址),那散列值的存在需要满足两个条件,其一是必须有__hash__魔法方法,该方法的作用是将每个存入的键值对的key转为hash值,也称为散列值,其二是必须有__eq__魔法方法,其作用是保证每个key都不能相同(表2-1/2-2是我截取的字符串关于这两个魔法方法的描述)。而有这两个方法的基本就是不可变对象:如str/int等等,为什么说基本呢?因为不可变对象中元组不一定满足,当元组内嵌套列表时,此时则不是一个散列值,如果当作key,则会报警unhashable type(如表3-1)
【表2-1】
【表2-2】
【表3-1】
讲解完了散列表的构成,此时来说明下散列表的算法,当我们在查询key时(即dict[key]),此时Python会先将key转换为对应的hash值,把这个值最低的几位数字作为偏移量来查找表元,找不到则报KeyError,找到了比较表元中的key和所查找的是否一致,一致则返回对应的value。但有个问题,此时可能会出现表元找到了,但key对不上的情况,这称之为散列冲突。散列冲突的解决方法很简单,就是Python将查询的hash值再重新取部分来查找,如果找不到表元,则报错,找到了,再判断key是否相等,相等则返回;或者又发现了散列冲突,则重复以上步骤,相关流程如表4-1。
【表4-1】
在说明这两个原因之前,先说个概论,字典的快是建立在用空间换时间,切记!还记得之前说的,数组缺点是在创建时规定了大小,当其空白占位达到其大小的1/3时,Python会重新引入一个更大的空间,此时会将原有空间的元素一一拷贝,再将新增的元素一一拷贝进去,所以这会造成巨大的空间浪费,同时在产生新空间的同时,会同步规划元素的hash值(hash值会变得更大),此时由于hash值的不同,则可能导致键的次序不同。另一个原因则是散列冲突,当添加新建的时候发现散列冲突,则新键可能会被安排到另一个位置,于是添加的元素可能就会跑到前方去了。以上就是关于无序的字典解释。
python里字典的底层实现是散列表。
散列表听上去好像很玄乎,但是实际上在我们的生活中却非常的常见。举个很简单的例子,我们读大学的时候,辅导员老师那里的excel表里有每个学生的信息,如果我们想要查找某一个学生的信息,使用什么方法最迅速呢?
一种很容易想到的方法是打开excel,然后从第一行开始一行一行的看,直接看到我们想要找的那个学生的那一行为止,可想而知,如果有5000个学生,很不巧这名学生刚好在excel表的最后一行,是不是要耗很长时间才能找到。这种方法有点类似于列表,查找的时间复杂度为O(n)。
还有另外的方法,因为每个学生都有学号,并且学号是连续的,如果我们存储的时候,直接按学号从小到大的顺序,从第一行开始存,查找的时候,只要知道学生的学号,是不是就能很快速的定位到该名学生在excel表格里的第几行,然后就可以直接拿到学生的信息。使用这种方式,只需要经过一次计算,就能确定学生信息的位置,而这个计算的时间是相对固定的。这就是典型的一种散列表的思想,它查找的时间复杂度是O(1)。这个计算偏移的方法,就相当于散列函数。这里学生的学号就是散列表中的键(key)。
了解了上面所说的散列表的基本原理之后,我们知道,散列表中元素的实际存储位置是由所设计的散列函数对键(key)进行运算后得出的。上面所举的学号的例子比较特殊,使用的散列函数相当于只是对学号数字取了一个偏移来得到学生信息存储位置。而实际上大多数的散列函数在对键进行计算后,得到的存储位置是随机的,并不连续,所以元素的存储位置也就不一定和输入的顺序相同。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。