Bootstrap

Python数据结构

Python数据结构

1.1线性数据结构

这些储存大量数据结构的组织形式,在Python中成为容器,包括列表、字典等。

线性结构是最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。它们之间的组织顺序由添加或删除的顺序决定。一旦一个数据元素被添加进容器,它相对于前后一直保持该位置不变,此类数据结构称为线性数据结构。

线性数据结构有两个端点,有时候称左和右,有时候称前和后,甚至称顶部和底部。线性数据结构中数据元素的添加和移除方式,特别是添加和移除的位置。

1.1.1 链表

链表是由许多相同数据类型的元素项按照特定顺序排列而成的线性表。链表的特性是其各个数据项在计算机内存中的位置是不连续且随机存放的。这样做的优点是数据的插入和删除相当方便,有新数据加入就向系统申请一块内存,而数据被删除后,就可以把这块内存空间还给系统,加入和删除都不需要移动大量数据。其缺点是设计数据结构时比较麻烦,而且查找数据时,无法像静态数据那样随机读取,必须按照顺序查找到该数据为止。

单向链表也叫单链表,是链表中最简单的一种组织形式。单向链表中每个节点包含两个域,一个信息域也叫元素域和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。

元素域就是存储数据的区域,指针就是保持存指向下一个链表的地址。最后一个节点的链接域指向空,为NULL。

1.1.2 栈

栈是一个有序的集合,其中添加和移除新项总发生在同一端,这一端通常称为顶部。与顶部对应的是底部。栈的底部很重要,因为在栈中靠近底部的元素是存储时间最长的,而最新添加的元素反而是最先被移除的,这种排序原则就是后进先出原则。如果根据在集合内的存储时间长度这个因素来做排序,较新的元素靠近顶部,较旧的元素靠近底部。

先创建一个空的列表,然后连续插入两个数据元素,这个时候列表stack中存储了两个元素1和2。此时,用pop命令挤出来一个元素,先挤出来的是2,stack列表中只剩下数字1,再执行一次pop操作,把数字1也挤出来了。这个例子充分说明数字2后进先出的过程。

1.1.3 队列

队列也是数据元素的有序结合,其中新添加新项的一端称为队尾,移除项的一端称为队首。当一个元素从队尾进入队列时,一直向队首移动,直到它成为下一个需要移除的元素为止。也就是说,最新添加的元素必须在队尾等待。集合中存活时间最长的元素在队伍首部,这种排序称为先进先出。

1.2 非线性数据结构

简单地说,非线性结构就是表中各个结点之间具有多个对应关系。其中各个数据元素不再保持在一个线性序列中,数据元素之间是一对多,或者是多对一的关系。根据关系的不同,可分为树形结构和图结构。

树是由一个或者一个以上的节点组成,存在一个特殊的节点,称为树根,或者根节点。每个节点都是一些数据和指针组合而成的记录,除了树根外,其余节点都是可分为n个互斥的集合。其中每一个子集合本身也是一种树形结构,即此根节点的子树。为了引入更多的符号,根始终位于树的顶部,后面的其他节点称为分支,每个分支中的最终节点称为叶。可以将每个分支想象成一颗较小的树本身。根通常称为父节点,它下面引用的节点称为子节点。具有相同父节点的节点称为兄弟节点。

另外一种非线性数据组织形式就是图。图结构中的点称为顶点,顶点与顶点之前的连线称为边,所有的顶点构成一个顶点集合,所有的边构成边的集合,一个完整的图结构就是由顶点集合和边集合组成。图结构中顶点集合不能为空,必须包含一个顶点,但构边集合可以为空,表示没有边。非线性数据结构的模型要比线性数据结构更加复杂。

1.3 元组

元组是Python中常用的线性数据结构。元组由不同的元素组成,每个元素可以存储不同类型的数据,如字符串、数字甚至元组。元组是写保护的,即元组创建后不能再做任何修改操作,元组通常代表一行数据。它和列表类似,都是线性数据结构的成员,只不过元组中的元素是不可修改的,而列表中的元素是可以修改的。元组的圆括号中的元素通过逗号来分割。

1.3.1 定义

元组和列表类似,都是有序的线性结构,用括号来表示,可以指定索引来获取元素。创建元组其实很简单,只要在某个值后面加上一个逗号。

注意,当元组只包含一个元素的时候,不能写成(elem)这样的形式,而是要写成"elem,"或者(elem,)如果元组为空,可以只写()。

1.3.2 元组的访问

元组非常适合用于存储在程序运行期间不会变化的数据集,有时希望某个函数不要修改传入的数据,就可以将数据放入元组中传入,因为元组无法修改,万一函数的使用者试图修改数据,那么执行就会抛错。

可以将元素拆解,单独访问元组的某个元素。

可以与for循环语句配合,遍历元组中的所有元素。

元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。

1.3.3 修改tuple变量

虽然元组tuple无法修改,但是可以给存储元组的变量重新赋值。

dimensions=(200,50)
for dimension in dimensions:
    print(dimension)
    //200 50
dimensions=(400,100)
for dimension in dimensions:
    print(dimension)
    //400 100

此外,还有一种变相的修改方法,那就是当两个元组相加的时候。

data=1,'python',True
dimensions=(200,50)
tuple3=data+dimensions
print(tuple3)
//(1,'python',True,200,50)

元组的元素是不可以删除的,但是我们可以使用del语句来删除整个元组。

dimensions=(200,50)
print(dimensions)
//(400,100)
del dimensions
print(dimensions)
//NameError:name 'dimensions' is not defined

元组被删除后,再次打印变量会有异常信息出现。不过在实际开发中,del语句并不常用,因为python自带的垃圾回收机制会自动销毁不用的元组。注意,并非所有闲置元组都会被垃圾回收机制自动销毁,Python会进行判断和区别对待。

1.4 集合

集合set在python中算是比较年轻的数据结构,同时使用率也偏低。

1.4.1 定义

集合是一组无序排列的值,其内容无序且元素不重复。它支持用in和not in操作符检查成员,用len内建函数得到集合的元素个数,用for循环遍历集合的成员,但因为集合本身是无序的,所以不可以为集合创建索引或执行切片操作。

创建集合,可以使用{}来包括元素,元素之间使用逗号分隔。在创建集合实例时,若有重复的元素则会被自动剔除。

s=set()
s={1,1,2,2,3,3}
print(s)
//{1,2,3}

1.4.2 集合的基本操作

如果想创建空集合,千万别用{},因为这样创建的是空字典而非集合。若想创建空集合,则必须使用set()。添加元素使用add方法,删除元素使用remove方法,测试元素是否存在于集合中使用in。

s=set()
s.add('python')
s.add('java')
print(s)
//{'python','java'}
print('python' in s)
//True
s.remove('python')
print('python' in s)
//False
s.update('ruby')
print(s)
//{'java','r','y','b','u'}

因为集合元素不能重复的特性,导致并非任何元素都可以放到集合中去,比如列表就不行。

1.5 字典

1.5.1 定义

简单来说,字典是一系列键和值的对应关系。可以把键理解为数据的编号或索引,每个键都与一个数值相关联,通过键就能访问与之相关联的数值。与键相关联的值可以是数字、字符串、列表、字典。事实上,可以将任何Python对象用作字典的值。字典中的数据是准许修改的,它属于无序的可变序列。

键值对是两个相关联的值。有了特定数据的键时,Python就能找到与之相对应的数值。键和数值之间用冒号分隔,而每个键值对用逗号分隔。在字典中,键值对的数量没有限制。

字典的特征如下:

  1. 字典中数据必须是以键值对形式出现。
  2. 逻辑上讲,键是不能重复的,而值是可以重复的。
  3. 字典中的键是不可以变的,也就是无法修改,而数值是可以修改的。
  4. 字典中的键和值不能脱离对方而存在。

1.5.2 字典的基本操作

字典类型是Python中唯一的映射类型,基本操作包括增删改查。

1.添加键值对

Student={'name':'小明'}
Student['age']=15
Student['class']=2
Student['score']=80
print(Student)
//{'name':'小明','age':15,'class':2,'score':80}

2.创建空字典

定义空字典很方便,只需要一个括号{}就可以了,再逐步添加各个键值对。

member={}
member['name']='小张'
member['age']='18'
print(member)
//{'name':'小张','age':'18'}

3.修改字典中的值

修改字典中的数值其实是一种键和数值对应关系的重组。

member['age']='19'
print(member)
//{'name':'小张''age':'19'}

4.删除字典中的数值

对于字典中不再需要的信息,可使用del语句将对应的键值对彻底删除,使用del语句时,必须指定字典名和要删除的键。

del member['age']
print(member)
//{'name':'小张'}

这样键值对age永远的消失了。另外,还有一种clear()方法可以清除所有的元素。

member.clear()
print(member)
//{}
python={'a':'b','c':'d','e':'f'}
print('a' in python)
//True
print('f' in python)
//False
list(python.items())
//[('a','b'),('c','d'),('e','f')]
list(python.keys())
//['a','c','e']
list(python.values())
//['b','d','f']

2.1 生成器

迭代是访问集合元素的一种方式,是指通过重复执行的代码处理相似的数据集。它有个特点:本次迭代的处理数据要依赖上一次迭代的结果才能继续执行,上一次产生的结果为下一次产生结果的初始状态,如果中途有任何停顿,都不能算是迭代,迭代不一定是循环。

infor=[0,1,2,3,4,5,6,7,8,9]
a=[i+1 for i in range(10)]
print(a)
//[1,2,3,4,5,6,7,8,9,10]

以上是列表生成式。其实直接创建一个列表,由于受到内存限制,列表容量肯定是有限的。如果要创建一个包含100万个数值元素的列表,常规方法无疑会占用很大内存,如果仅仅只需要访问列表中的几个元素,那么绝大多数的元素占用空间都白白浪费了。如果列表元素可以按照某种算法推算出来,那就可以在循环的过程中不断推算出后续的元素。这样就不必创建完整的列表,从而节省大量空间。

类似这种一边循环一边计算的机制,称为生成器。注意,生成器与列表生成式运行机制相同,但是并非同一个东西。

generator_ex=(x+1 for x in range(10))
print(generator_ex)
//<generator object <genexpr> at 0x034F4C30>

创建列表生成式和生成器的区别,表面看就是[]和()的区别,列表生成式的返回值是列表,而生成器产生的却是生成器。

generator_ex=(x+1 for x in range(10))
for i in generator_ex:
    print(i)

用for循环遍历生成器的所有元素,除了不用重复输入next函数之外,还有一个好处就是不用担心StopIteration的错误。

2.2 迭代器

前面介绍了直接作用于for循环的数据类型,一类是集合类型,比如列表、元组、字典、集合、字符串。还有一类就是生成器。以上这些可以直接作用于for循环的对象统称为可迭代对象。

Python有可以判断某个对象是否为可迭代类型的方法isinstance()

from collections import Iterable
print(isinstance('abc',Iterable))
//True

这里介绍一个新的可迭代对象——迭代器。实现next()方法并且是可迭代对象就是迭代器,更简便的方法就是用isinstance()来判断某个对象是否是生成器Iterator。

x for x in range(10)既实现了迭代,又实现了next方法,所以它就是迭代器Iterator。列表、字典、字符串虽然是可迭代对象,却不是迭代器。迭代器通常表示的是一个数据流,可以被next函数调用并不断返回下一个数据,直到没有数据时抛出 StopIteration的错误。我们无法提前知道这个数据流的长度,只能通过不断用next函数按需求计算下一个数据。

虽然列表、字典、字符串都是可迭代对象不是迭代器,却可以通过iter函数强制转换成迭代器。

使用迭代器不要求事先准备好整个迭代过程中的所有元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后元素可以不存在或被销毁。因此迭代器适合遍历一些数量巨大甚至无限的序列。

;