Python中常见的数据结构可以统称为容器。序列(如列表和元组)、映射(如字典)以及集合(set)是三类主要的容器。
线性数据结构分类:栈(stack)--先进后出、 队列(queue)-先进先出、双端队列(deque)、链表(LinkedList),Python内置了数据结构常用模块:collections、heapq、operator、itertools,可以满足使用,但对于最基本的结构还是得熟悉。
一、序列(列表、元组和字符串)
序列中的每个元素都有自己的编号。Python中有6种内建的序列。其中列表和元组是最常见的类型。其他包括字符串、Unicode字符串、buffer对象和xrange对象。下面重点介绍下列表、元组和字符串。
|
列表 | 元祖 | 字符串 |
1、常见的基本操作
| 1.常见的基本操作
| 1.常见的基本操作
|
列表是可变的,这是它区别于字符串和元组的最重要的特点,一句话概括即:列表可以修改,而字符串和元组不能。元组使用小括号,列表使用方括号。元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。
二、映射(字典)
映射中的每个元素都有一个名字,如你所知,这个名字专业的名称叫键。字典(也叫散列表)是Python中唯一内建的映射类型。字典的键可以是数字、字符串或者是元组,键必须唯一。在Python中,数字、字符串和元组都被设计成不可变类型,而常见的列表以及集合(set)都是可变的,所以列表和集合不能作为字典的键。键可以为任何不可变类型,这正是Python中的字典最强大的地方。
函数 | 说明 |
D | 代表字典对象 |
D.clear() | 清空字典 |
D.pop(key) | 移除键,同时返回此键所对应的值 |
D.copy() | 返回字典D的副本,只复制一层(浅拷贝) |
D.update(D2) | 将字典 D2 合并到D中,如果键相同,则此键的值取D2的值作为新值 |
D.get(key, default) | 返回键key所对应的值,如果没有此键,则返回default |
D.keys() | 返回可迭代的 dict_keys 集合对象 |
D.values() | 返回可迭代的 dict_values 值对象 |
D.items() | 返回可迭代的 dict_items 对象 |
三、集合
集合(Set)在Python 2.3引入,通常使用较新版Python可直接创建,如下所示:
strs=set(['jeff','wong','cnblogs'])
nums=set(range(10))
看上去,集合就是由序列(或者其他可迭代的对象)构建的。集合的几个重要特点和方法如下:
(1) 固定集合构造(创建)函数 frozenset
函数 | 说明 |
frozenset() | 创建一个空的固定集合对象 |
frozenset(iterable) | 用可迭代对象创建一个新的固定集合对象 |
(2) 集合构造(创建)函数 set
函数 | 说明 |
set() | 创建一个空的集合对象(不能用{}来创建空集合) |
set(iterable) | 用可迭代对象创建一个新的集合对象 |
(3) Python3 集合中常用的方法
方法 | 意义 |
S.add(e) | 在集合中添加一个新的元素e;如果元素已经存在,则不添加 |
S.remove(e) | 从集合中删除一个元素,如果元素不存在于集合中,则会产生一个KeyError错误 |
S.discard(e) | 从集合S中移除一个元素e,在元素e不存在时什么都不做; |
S.clear() | 清空集合内的所有元素 |
S.copy() | 将集合进行一次浅拷贝 |
S.pop() | 从集合S中删除一个随机元素;如果此集合为空,则引发KeyError异常 |
S.update(s2) | 用 S与s2得到的全集更新变量S |
S.difference(s2) | 用S - s2 运算,返回存在于在S中,但不在s2中的所有元素的集合 |
S.difference_update(s2) | 等同于 S = S - s2 |
S.intersection(s2) | 等同于 S & s2 |
S.intersection_update(s2) | 等同于S = S & s2 |
S.isdisjoint(s2) | 如果S与s2交集为空返回True,非空则返回False |
S.issubset(s2) | 如果S与s2交集为非空返回True,空则返回False |
S.issuperset(...) | 如果S为s2的子集返回True,否则返回False |
S.symmetric_difference(s2) | 返回对称补集,等同于 S ^ s2 |
S.symmetric_difference_update(s2) | 用 S 与 s2 的对称补集更新 S |
S.union(s2) | 生成 S 与 s2的全集 |
四、线性存储(栈、队列、链表)
堆栈:先进后出 队列:先进先出 FIFO
(1)利用提供的栈和队列模块
python自带的list就是以栈为基础构建的,
'''栈stack 先进后出FILO (first in last out) 没有找到import stack这种方法'''
##栈:增(插)、删、改、查
lst=[]
lst.append("zhangsan")
print("增:",lst)
lst.insert(0,"wangwu")
print("插:",lst)
lst.pop() ###pop()默认会弹出栈尾数据,pop(0)会弹出栈首数据
print("删:",lst)
lst[0]="lisi"
print("改:",lst)
print(lst.index("lisi")) ###这是list的查方法,存在则返回索引,否则报错
而队列需要借助第三方包来实现,put压入,get弹出,但是不能直接实现定点插入、修改或删除,因为这样突破了队列先进先出的特性,如果需要操作,需要将队列的元素先取出在压入。
'''普通队列 先进先出 进: put() 出: get(),大小full() empty() qsize(), maxsize'''
import queue ###python3,而python2为import Queue
q = queue.Queue() # 创建队列,初始没有设置则可以无限添加,设置大小后就需要考虑队列满插入和队列空弹出操作
q.put("张一山")
q.put("王大拿")
q.put("王木生")
print(q.get()) ##输出张一山
print(q.get()) ##输出王木拿
print(q.get()) ##输出王木生
qq = queue.Queue(8)
# print(dir(qq))
try:
for i in range(12):
myData = 'A'
qq.put(myData,block=False)
myData = 'B'
except Exception as e:
print(e)
while qq.empty()!=True:
print(qq.get())
'''
双端队列(两边都可以进和出)
进: append()和appendleft()
出: pop()和popleft()
'''
from collections import deque
d = deque()
d.append("牡丹花")
d.appendleft("樱桃花")
d.append("腊梅")
d.append("兰花")
d.appendleft("罂粟花")
print(d.pop()) # "兰花"
print(d.popleft()) # "罂粟花"
print(d.pop()) # "腊梅"
print(d.popleft()) # "樱桃花"
print(d.popleft()) # "牡丹花"
补充一点就是,当设置了队列长度后,Queue.put()默认有 block = True 和 timeout 两个参数。当 block = True 时,写入是阻塞式的,阻塞时间由 timeout 确定。正因为阻塞,才导致了后来的赋值污染了处于阻塞状态的数据。Queue.put()方法加上 block=False 的参数,即可解决这个隐蔽的问题。但要注意,非阻塞方式写队列,当队列满时会python2会抛出 exception Queue.Full 的异常。
(2)用list来分别构建栈和队列
从某种角度来说,栈和队列可以看作操作受限的list,具有操作如下,
###利用列表构建一个栈
class Stack():
def __init__(self):
self.items = [] # 初始化一个列表
def is_empty(self): # 如果为空则返回True 否则返回False
return self.items == []
def pop(self):
try:
return self.items.pop()
except:
raise
def push(self, item):
self.items.append(item)
def peek(self): # 返回栈顶的元素
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
###利用列表构建一个队列
class Queue():
def __init__(self):
self.items = [] ##初始化一个队列
def is_empty(self):
return self.items == []
def enqueue(self, item): # 从队尾插入
self.items.insert(0, item)
def dequeue(self): # 从队首弹出
try:
return self.items.pop()
except:
raise
def peek(self):
return self.items[0]
def size(self):
return len(self.items)
###用两个栈实现队列的操作
'''
因为栈是先进后出的,队列是先进先出的。那么,设两个栈分别为s1和s2。
当进行入队的时候,可以直接在s1上进行压栈的操作;
当进行出队的时候,因为是先进要先出,因此可以将s1的元素倒出到s2中,然后在s2进行弹栈的操作;
'''
class Solution:
def __init__(self):
# 初始化两个栈
self.stack1 = []
self.stack2 = []
def push(self, node):
# 直接在s1进行弹栈
self.stack1.append(node)
def pop(self):
# 如果s2不为空,则直接进行弹栈
if self.stack2:
return self.stack2.pop()
# 如果s2为空,则返回寻找s1中的元素
elif not self.stack1:
# 如果s1为空,则返回空置,没有元素可以弹出
return None
else:
# 否则将s1的元素倒入到s2中,再弹栈
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
链表:给我一个表头,给你整个世界
链表种类:单向链表、单向循环链表、双向链表、双向循环链表
常见的链表形式有带头节点的与无头结点的两种形式,
1. 头结点的数据域可以不存储任何信息,也可以存储线性表的长度等附加信息,头结点的指针域存储第一个元素结点的地址,即首元结点的存储地址。若线性表为空表,则头结点的指针域为NULL;
2. 在链表中加入头结点的好处是:
a) 由于首元结点的地址被存放在头结点的指针域中,因此在链表的首元结点和表中其他结点上的操作一致,无需进行特殊处理,使得各种操作方便统一。
b) 即使是空表,头指针也不为空,使得“空表”和“非空表”的处理一致。
class LNode(object):
#结点初始化函数, p 即模拟所存放的下一个结点的地址
#为了方便传参, 设置 data,p 的默认值为None
def __init__(self, data=None, p=None):
self.data = data
self.next = p
class LinkList(object):
def __init__(self):
self.head = None
#链表初始化函数, 方法类似于尾插
def init_List(self, data):
#创建头结点
self.head = LNode(data[0])
p = self.head
#逐个为 data 内的数据创建结点, 建立链表
for i in data[1:]:
node = LNode(i)
p.next = node
p = p.next
#链表判空
def is_Empty(self):
if self.head == None:
return True
else:
return False
#取链表长度
def get_Length(self):
if self.is_Empty():
return 0
p = self.head
len = 0
while p:
len += 1
p = p.next
return len
#遍历链表
def trave_List(self):
if self.is_Empty():
return
print ('\rlink list traving result: ')
p = self.head
while p:
print (p.data)
p = p.next
#链表插入数据函数
def insert_Elem(self, key, index):
if self.is_Empty():
return
if index<0 or index>self.get_Length()-1:
print ("\rKey Error! Program Exit.")
return
p = self.head
i = 0
while i<=index:
pre = p
p = p.next
i += 1
#遍历找到索引值为 index 的结点后, 在其后面插入结点
node = LNode(key)
pre.next = node
node.next = p
#链表删除数据函数
def delete_Elem(self, index):
if self.is_Empty():
return
if index<0 or index>self.get_Length()-1:
print ("\rValue Error! Program Exit.")
return
i = 0
p = self.head
#遍历找到索引值为 index 的结点
while p.next:
pre = p
p = p.next
i += 1
if i==index:
pre.next = p.next
p = None
return 1
#p的下一个结点为空说明到了最后一个结点, 删除之即可
pre.next = None
#初始化链表与数据
data = [1,2,3,4,5]
l = LinkList()
l.init_List(data)
l.trave_List()
#插入结点到索引值为3之后, 值为666
l.insert_Elem(666, 3)
l.trave_List()
#删除索引值为4的结点
l.delete_Elem(4)
l.trave_List()
五、非线性存储(树、图)
对于常见的非线性存储结构,二叉树的构建,通常会先构建一个节点类,然后再构建树类,具体如下:
class BTNode(object):
def __init__(self, key=None, lchild=None, rchild=None):
self.key = key
self.lchild = lchild
self.rchild = rchild
class BiTree(object):
def __init__(self, data_list):
#初始化即将传入的列表的迭代器
self.it = iter(data_list)
def createBiTree(self, bt=None):
try:
#步进获取下一个元素
next_data = next(self.it)
#如果当前列表元素为'#', 则认为其为 None
if next_data is "#":
bt = None
else:
bt = BTNode(next_data)
bt.lchild = self.createBiTree(bt.lchild)
bt.rchild = self.createBiTree(bt.rchild)
except Exception as e:
print(e)
return bt
#先序遍历函数
def preOrderTrave(self, bt):
if bt is not None:
print(bt.key, end=" ")
self.preOrderTrave(bt.lchild)
self.preOrderTrave(bt.rchild)
#中序遍历函数
def inOrderTrave(self, bt):
if bt is not None:
self.inOrderTrave(bt.lchild)
print(bt.key, end=" ")
self.inOrderTrave(bt.rchild)
#后序遍历函数
def postOrderTrave(self, bt):
if bt is not None:
self.postOrderTrave(bt.lchild)
self.postOrderTrave(bt.rchild)
print(bt.key, end=" ")
#层序遍历
def cengTrave(self,bt):
if bt==None:
return
else:
q=[bt]
while q:
node=q.pop(0)
print(node.key,end=' ')
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
#综合打印
def printTrave(self, bt):
print("先序遍历: ", end="")
self.preOrderTrave(bt)
print('\n')
print("中序遍历: ", end="")
self.inOrderTrave(bt)
print('\n')
print("后序遍历: ", end="")
self.postOrderTrave(bt)
print('\n')
data = input("Please input the node value: ")
data_list = list(data)
btree = BiTree(data_list)
root = btree.createBiTree()
btree.printTrave(root)
或者整合在一起:
class BinaryTree:
def __init__(self,rootObj):
self.key = rootObj
self.leftChild = None
self.rightChild = None
def insertLeft(self,newNode):
if self.leftChild == None:
self.leftChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.leftChild = self.leftChild
self.leftChild = t
def insertRight(self,newNode):
if self.rightChild == None:
self.rightChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.rightChild = self.rightChild
self.rightChild = t
def getRightChild(self):
return self.rightChild
def getLeftChild(self):
return self.leftChild
def setRootVal(self,obj):
self.key = obj
def getRootVal(self):
return self.key
r = BinaryTree('a')
print(r.getRootVal())
print(r.getLeftChild())
r.insertLeft('b')
print(r.getLeftChild())
print(r.getLeftChild().getRootVal())
r.insertRight('c')
print(r.getRightChild())
print(r.getRightChild().getRootVal())
r.getRightChild().setRootVal('hello')
print(r.getRightChild().getRootVal())
至于图结构 (参考链接:)
创建图有3种方式
1,邻接矩阵
2,邻接集合
3,邻接字典
# coding=utf8
# 对5个顶点进行排序
a, b, c, d, e = range(5)
# 顶点个数
N = 5
G = [[0] * N for _ in xrange(N)]
# 邻接矩阵
# 存储数据有大量空的数据,全是0的情况,在查找某一个顶点的邻接顶点就会遍历所有的顶点
def add_edge(G, v1, v2):
G[v1][v2] = G[v2][v1] = 1
add_edge(G, a, b)
add_edge(G, a, e)
add_edge(G, b, e)
add_edge(G, b, d)
add_edge(G, b, c)
add_edge(G, c, d)
add_edge(G, d, e)
print(G)
#更多情况,我们是采用邻接集合和邻接字典来存储
# 邻接集合
# 每一个顶点只记录其邻接顶点
G2 = [{b, e}, # a
{a, e, b, c}, # b
{b, d}, # c
{b, c, e}, # d
{a, b, d} # e
]
print(G2)
# 带权的边,利用邻接字典存储
G3 = [{b: 4, e: 2}, # a
{a: 4, e: 3, d: 6, c: 5}, # b
{b: 5, d: 7}, # c
{b: 6, c: 7, e: 1}, # d
{a: 2, b: 3, d: 1} # e
]
print(G3)