在应用开发中,除了Python序列等基本数据类型之外,还经常需要使用到其他一些数据结构,例如堆、栈、队列、树、图等。其中有些结构Python本身已经提供了,而有些则需要自己利用Python基本序列或其他数据类型来实现。本节内容可以看作是Python序列、元组等基本数据结构的扩展,或者Python基本数据结构的二次开发。
【堆】
堆是一种重要的数据结构,在进行排序时使用较多,优先队列也是堆结构的一个重要应用。堆是一个二叉树,其中每个父节点的值都小于或等于其所有子节点的值。使用数组或列表来实现堆时,对于所有的k(下标,从0开始)都满足heap[k]<=heap[2k+1]和heap[k]<=heap[2k+2],并且整个堆中最小的元素总是位于二叉树的根节点。Python在heapq模块中提供了对堆的支持。下面的代码演示了堆的原理以及heapq模块的用法,同时也请注意random模块的用法。另外,当堆中没有元素时,进行heappop()操作将会抛出异常。
>>>import heapq
>>>import random
>>>data=range(10)
>>>data
[0,1,2,3,4,5,6,7,8,9]
>>>random.choice(data) #从序列中随机选取一个元素
9
>>>random.choice(data)
1
>>>random.choice(data)
3
>>>random.shuffle(data) #随机打乱列表中的元素顺序
>>>data
[6,1,3,4,9,0,5,2,8,7]
>>>heap=[]
>>>for n in data: #建堆
heapq.heappush(heap,n)
>>>heap
[0,2,1,4,7,3,5,6,8,9]
>>>heapq.heappush(heap,0.5) #新数据入堆
>>>heap
[0,0.5,1,4,2,3,5,6,8,9,7]
>>>heapq.heappop(heap) #弹出最小的元素,堆会自动重建
0
>>>heapq.heappop(heap)
0.5
>>>heapq.heappop(heap)
1
>>>myheap=[1,2,3,5,7,8,9,4,10,333] #将列表转换为堆
>>>heapq.heapify(myheap)
>>>myheap
[1,2,3,4,7,8,9,5,10,333]
>>>heapq.heapreplace(myheap,6) #替换堆中最小的元素值,自动重新构建堆
1
>>>myheap
[2,4,3,5,7,8,9,6,10,333]
>>>heap.nlargest(3,myheap) #返回前3个最大元素
[333,10,9]
>>>heap.nsmallest(3,myheap) #返回前3个最小元素
[2,3,4]
【队列】
队列的特点是“先进先出”和“后进后出”,在某些应用中有着重要的作用,例如多线程编程、作业处理等等。Python提供了queue模块和collection.deque模块支持队列的操作,当然也可以使用Python列表进行二次开发来实现自定义的队列结构。
>>>import queue
>>>q=queue.Queue()
>>>q.put(0) #元素入队,添加至队列尾部
>>>q.put(1)
>>>q.put(2)
>>>print(q.queue)
deque([0,1,2])
>>>print(q.get()) #队列头元素出队
0
>>>print(q.queue)
deque([1,2])
>>>print(q.get())
1
>>>print(q.queue)
deque([2])
另外,queue模块还提供了“后进先出”队列和优先级队列
>>>import queue
>>>LiFoQueue=queue.LifoQueue(5) #“后进先出”队列
>>>LiFoQueue.put(1)
>>>LiFoQueue.put(2)
>>>LiFoQueue.put(3)
>>>LiFoQueue.queue
[1,2,3]
>>>LiFoQueue.get()
3
>>>LiFoQueue.get()
2
>>>LiFoQueue.get()
1
>>>import queue
>>>PriQueue=queue.PriorityQueue(5) #优先级队列(按照建堆的思想)
>>>PriQueue.put(3)
>>>PriQueue.queue
[3]
>>>PriQueue.put(5)
>>>PriQueue.queue
[3,5]
>>>PriQueue.put(1)
>>>PriQueue.queue
[1,5,3]
>>>PriQueue.put(8)
>>>PriQueue.queue
[1,5,3,8]
>>>PriQueue.get()
1
>>>PriQueue.get()
3
>>>PriQueue.get()
5
>>>PriQueue.get()
8
下面的代码使用了collections模块中的双端队列
#可以从队列的两端加入和删除元素
>>>from collectioms import deque
>>>queue=deque(["Eric","John","Michael"])
>>>queue.append("Terry")
>>>queue.append("Graham")
>>>queue.popleft()
'Eric'
>>>queue.popleft()
'John'
>>>queue
deque(['Michael','Terry','Graham'])
下面的Python2.7.8代码定义了一个类,利用Python列表实现了自定义的队列结构,并模拟了队列的基本操作
class myQueue:
def __init__(self,size=10):
self._content=[]
self._size=size
def setSize(self,size):
self._size=size
def put(self,v):
if len(self._content)<self._size:
self._content.append(v)
else:
print 'The queue is full'
def get(self):
if self._content:
return self._content.pop(0)
else:
print 'The queue is empty'
def show(self)
if self._content:
print self._content
else:
print 'The queue is empty'
def empty(self):
self._content=[]
def isEmpty(self):
if not self._content:
return True
else:
return False
def isFull(self):
if len(self._content)==self._size:
return True
else:
return False
可以使用该类中的方法来实现队列的基本操作,当然也可以对上面的代码进行适当的扩展以实现其他特殊需求。下面的代码简单演示了这个自定义队列结构的用法
>>>import myQueue
>>>q=myQueue.myQueue()
>>>q.get() #元素出队,队列为空时给出提示
The queue is empty
>>>q.put(5) #元素入队
>>>q.show()
[5]
>>>q.put(7)
>>>q.put('a')
>>>q.show()
[5,7,'a']
>>>q.isEmpty() #测试队列是否为空
False
>>>q.isFull() #测试队列是否为满
False
>>>q.get()
5
>>>q.get()
7
>>>q.get()
'a'
>>>q.get()
The queue is empty
【栈】
栈是一种“后进先出”或“先进后出”的数据结构,Python列表本身就可以实现栈结构的基本操作。例如,列表对象的append()方法是在列表尾部追加元素,类似于入栈操作;pop()方法默认是弹出并返回列表的最后一个元素,类似于出栈操作。但是直接使用Python列表对象模拟栈操作并不是很方便,例如,当列表为空时,若再执行pop()出栈操作,则会抛出一个不很友好的异常;另外,也无法限制栈的大小
下面的Python2.7.8代码使用列表实现了自定义的栈结构来模拟栈结构及其基本操作,除了支持常规的入栈和出栈操作,还有效地控制了栈的大小,并且支持置空和测试栈是否为空,是否为满等状态以及实时查看剩余可用空间
class Stack:
def __init__(self,size=10):
self._content=[]
self._size=size
def empty(self):
self._content=[]
def isEmpty(self):
if not self._content:
return True
else:
return False
def setSize(self,size):
self._size=size
def isFull(self):
if len(self._content)==self._size:
return True
else:
return False
def push(self,v):
if len(self._content)<self._size:
self._content.append(v)
else:
print 'Stack Full!'
def pop(self):
if self._content:
return self._content.pop()
else
print 'Stack is empty!'
def show(self):
print self._content
def showRemaininderSpace(self):
print 'Stack can still PUSH ',self._size-len(self._content),' elements. '
if __name__ == '__main__':
print 'Please ues me as a module.'
将上面代码保存为Stack.py文件之后,可以作为模块进行使用来模拟栈的基本操作。
>>>import Stack
>>>x=Stack.Stack()
>>>x.push(1)
>>>x.push(2)
>>>x.show()
[1,2]
>>>x.pop()
2
>>>x.show()
[1]
>>>x.showRemainderSpace()
Stack can still PUSH 9 elements.
>>>x.isEmpty()
False
>>>x.isFull()
False
【链表】
可以直接使用Python列表及其基本操作来实现链表的功能,可以很方便地实现链表创建以及节点的插入和删除操作,当然也可以对列表进行封装来实现自定义的链表结构实现特殊功能或更加完美的外围检查工作。下面的代码直接使用Python列表模拟了链表及其基本操作。
>>>linkTable=[]
>>>linkTable.append(3) #在尾部追加节点
>>>linkTable.append(5)
>>>linkTable
[3,5]
>>>linkTable.insert(1,4) #在链表中间插入节点
>>>linkTable
[3,4,5]
>>>linkTable.remove(linkTable[1]) #删除节点
>>>linkTable
[3,5]
如前所述,使用列表直接模拟链表结构时,同样存在一些问题,例如,链表为空或删除的元素不存在时会抛出异常,可以对列表进行封装来实现完整的链表操作,可以参考队列与栈的代码。
【二叉树】
如果学过数据结构,大家肯定还记得,用C/C++来实现二叉树要考虑很多语言本身的问题,看到下面使用Python列表实现的二叉树,相信你会眼前一亮。使用代码中的类BinaryTree创建的对象不仅支持二叉树的创建以及前序遍历、中序遍历与后序遍历三种常用的二叉树节点的遍历方式,还支持二叉树中任意“子树”的遍历。
#Filename:BinaryTree.py
#Function description:
#Creation,traverse of binary tree
class BinaryTree:
def __init__(self,value):
self._left=None
self._right=None
self._data=value
def insertLeftChild(self,value): #插入左节点
if self._left:
print '_left child tree already exists.'
else:
self._left=BinaryTree(value)
return self._left
def insertRightChild(self,value): #插入右节点
if self._right:
print 'Right child tree already exists.'
else:
self._right=BinaryTree(value)
return self._right
def removeLeftChild(self): #删除左子树
self._left=None
def removeRightChild(self): #删除右子树
self._right=None
def show(self):
print self._data
def preOrder(self): #前序遍历
print self._data
if self._left:
self._left.preOrder()
if self._right:
self._right.preOrder()
def inOrder(self): #中序遍历
if self._left:
self._left.inOrder()
print self._data
if self._right:
self._right.inOrder()
def postOrder(self): #后序遍历
if self._right:
self._right.postOrder()
if self._left:
self._left.postOrder()
print self._data
if __name__ == '__main__':
print 'Please ues me as a module.'
假设把上面的代码保存为文件BinaryTree.py,然后使用下面的方法来使用上面定义的二叉树。需要把文件放在Python的安装目录中,或者把含有该文件的目录添加到sys.path中
>>>import BinaryTree
>>>root=BinaryTree.BinaryTree('root')
>>>firstRight=root.insertRightChild('B')
>>>firstLeft=root.insertLeftChild('A')
>>>secondLeft=firstLeft.insertLeftChild('C')
>>>thridRight=secondLeft.insertRightChild('D')
>>>root.postOrder() #后序遍历
D
C
A
B
root
>>>root.preOrder() #前序遍历
root
A
C
D
B
>>>root.inOrder() #中序遍历
C
D
A
root
B
>>>firstLeft.inOrder() #遍历“子树”
C
D
A
>>>secondLeft.removeRightChild() #删除二叉树中的节点
>>>root.preOrder()
root
A
C
B
【有向图】
作为本章的最后一个示例,让我们来看一下使用Python语言实现有向图的创建和路径搜索。有向图由节点和边组成,而每条边都是有方向的,若两个节点之间存在有向边,则表示可以从起点到达终点。与二叉树的示例一样,下面给出较为完整的代码
#Filename:DirectedGraph.py
#Function description:path searching of directed graph
def searchPath(graph,start,end):
results=[]
_generatePath(graph,[start],end,results)
results.sort(key=lambda x:len(x))
return results
def _generatePath(graph,path,end,results):
current=path[-1]
if current==end:
results.append(path)
else:
for n in graph[current]:
if n not in path:
#path.append(n)
_generatePath(graph,path+[n],end,results)
def showPath(results):
print 'The Path from',results[0][0],'to',results[0][-1],'is:'
for path in results:
print path
if __name__ =='__main__':
graph={'A':['B','C','D'],'B':['E'],'C':['D','F'],'D':['B','E','G'],'E':['G'],'F':['D','G'],'G':['E','A','B']}
r=searchPath(graph,'A','D')
showPath(r)