python 實現樹結構
簡述:
研究 MCTS 過程中, 需要用到樹結構。 baidu google 了一番, 找不到自己能滿足自己的庫或代碼參考,只好再造個輪子出來
我造的樹用來下五子棋 和 圍棋用的, 有其它不同的應用場合, 那就需要在此基礎上改造了。
本樹的特點:
1. 支持多子節點 ( 網絡上很多代碼都是二叉樹,不符合我的需求 )
2. 支持樹的存儲 和 讀取, 網上很少看到。
正文:
下面按照 應用場景, 數據結構和接口 , 代碼, 三個部分 自上向下說明。
應用場景:
一: 畫一個根節點, 再加三個葉子的樹 (參考 Tree.demo1())
data = [‘{}‘.format( random.random() * 10000 )] self.addSubNodeToCur_Data( data ) self.moveUp(); data = [‘{}‘.format( random.random() * 10000 )] self.addSubNodeToCur_Data( data ) self.moveUp(); data = [‘{}‘.format( random.random() * 10000 )] self.addSubNodeToCur_Data( data ) self.save()
初始化出來的樹,只有一個根節點 tree = Tree()
然後在根下面,加一個節點, 內容是 data ,必須是 list 結構, list 裏的元素只能是 字符,數字, True/False , 不允許 object , list , ()
moveUp 是當前節點往上移動一層, 如果已經是根節點了,會返回False
addSubNodeToCur_Data(self, data, NodeId = 0, isMoveToSub = True ) 在當前節點下,增加一個節點,節點內容是 Data ; isMoveToSub 當前節點移到新增節點
NodeId 一般不需要設置, 如果你的樹結構是清楚的時候, 知道NodeId 的情況下設置。
二: 保存樹,讀取樹
self.load(self, filename = None) , load 之後 ,生成樹
self.save(self, filename = None)
默認文件名 ./data.npy 可指定文件名 , .npy 結尾
三:生成棋譜樹
該棋局, 只有 4個 落子點 , [(0, 0), (0, 1), (1, 0), (1, 1)] , 一個人下
根據數理分析, 會形成這樣一棵樹:
0 root_node 根節點
1 4 × node 第一步 節點數
2 3 x 4 x node 第二步節點數
3 2x 3x4xnode 第三步節點數
4 1x 2x3x4xnode 第四步節點數
下一局,形成的一個棋譜
# 下一局, 隨機選一個點, 下滿棋盤 def WzOne( tree ): tree.reset() #讓樹當前節點回到根節點 avilAction = [(0, 0), (0, 1), (1, 0), (1, 1)] # 4個落子點 while( len(avilAction) >0 ): #無落子點 action = random.choice(avilAction) #隨機一個落子點 avilAction.remove(action) data = list( action ) # 遍歷當前前節點的子節點, 如果已經存在該落子點, 則跳到子節點 # 如果當前節點的子節點, 無該落子點, 則增加一個子節點 isExist = False for node in tree.cur_Node.children : if( node.getData() == data ): isExist = True tree.moveToNode_byNode( node ) break if( isExist == False ): tree.addSubNodeToCur_Data(data) pass
下100局,形成100局的棋譜
# 下100局, 拓展棋譜 def testWzTree(): ‘‘‘ 生成 落子樹 ‘‘‘ tree = Tree() for i in range(100): WzOne( tree ) tree.printTree() tree.save() #讀取樹 print ‘------------------------------------------------‘ tree2 = Tree() tree2.load() tree2.printTree() pass
最多生成 24 個葉子(最低層)的樹; 也就是 24個棋譜
三:生成10x10 的五子 對弈 棋譜樹
理論最大棋譜數 100 x 98 x 96x 94 x ...... (不考慮五子成線) 貌似好幾百億
試驗了一下 7 x7 對弈五子棋, 10000 局, 形成 195719 個節點的樹
github :
https://github.com/rehylas/play_chess/blob/master/standTree/wuzi.py
待補充
數據結構和接口
本代碼輸出兩個類:Node 和 Tree
節點保存的信息有兩部分:
nodeInfo = [ nodeid, level, parentNodeid, [ node1,node2,node3 ] ]
data = [data1, data2,data3,data3 ] 隨應用定義
Node 輸出接口:
create
setData(Data)
setParent(Node)
addSubNode(Node)
getSubNodeList()
Tree 輸出接口:
def __init__(self): def reset(self): def load(self, filename = None ): def save(self, filename = None ): def printTree(self): def getRootNode(self): def getCurNode(self): def addSubNodeToCur_Node(self, subNode, isMoveToSub = True ): def addSubNodeToCur_Data(self, data, NodeId = 0, isMoveToSub = True ): def moveToNode(self,nodeId ): def moveToNode_byNode(self, node ): def serachNodeId(self, thisNode, nodeId): def moveUp(self):
代碼:
gitHub: https://github.com/rehylas/play_chess/blob/master/standTree/Tree.py
代碼:
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: hylas zhang import numpy as np; import random ‘‘‘ Node class data data: [] nodeinfo: id, level, parentId, childrenIdList [ 0, 0, 0, [] ] childrenIdList save to file : [1,2,3,4] ==> 1,2,3,4 export function: create setData(Data) setParent(Node) addSubNode(Node) getSubNodeList() in function: ‘‘‘ class Node(): def __init__(self, info=None, data=None, infodata = None ): if( infodata != None ): info = infodata[0:3] +[[]] data = infodata[3:] self.info = info self.data = data pass self.data = [‘‘] #[ 0,0, ‘‘ ] #times, wins, values if( data != None ): self.data = data self.nodeInfo=[ 0, 0 ,0, [] ] #Nodeid, level , parentId, childrenIdList if( info != None ): self.nodeInfo = info self.parent = None self.children = [] pass def setData(self,data): self.data = data def setParent(self, parentNode): self.parent = parentNode def addChild(self, childNode ): self.children += [ childNode ] def getSubNodeList(self): return self.children def getDataInfo(self): dataList = [self.nodeInfo[0],self.nodeInfo[1],self.nodeInfo[2]] +self.data return dataList def getData(self): return self.data def getInfo(self): return self.nodeInfo def __repr__(self): return "nodeInfo: {}, ".format(self.nodeInfo ) def __eq__(self, other): selfVal = "{}".format(self.nodeInfo ) otherVal = "{}".format(other.nodeInfo) if hash(selfVal) == hash(otherVal): return True return False ‘‘‘ Tree export function: 1.create 2.getRootNode 3.getCurNode 4.reset( 一般 回溯之後 ) 5.loadFile 6.saveFile 7.addNewNodeInCurNode 7.獲取usb最優節點 8.增加新節點 9.移動當前節點 內部: ‘‘‘ class Tree(): ###### 輸出 def __init__(self): self.root_Node = Node() self.cur_Node = self.root_Node self.NodeCount = 1 # 節點+ 葉子數 + 1 pass def reset(self): self.cur_Node = self.root_Node pass def load(self, filename = None ): if( filename == None ): filename = ‘./data.npy‘ npData2 = np.load( filename ) lst2 = npData2.tolist() for node in lst2: info = node[0:3] data = node[3:] if( info[0] == 0 ): continue print ‘want to add:‘ print node if( info[2] == self.cur_Node.nodeInfo[0] ): self.addSubNodeToCur_Data(data, NodeId = info[0] ) continue #self.cur_Node count = 10 while( info[2] != self.cur_Node.nodeInfo[0] ) : ret = self.moveUp() if( ret == False ): print ‘error ......‘ return continue self.addSubNodeToCur_Data(data, NodeId = info[0]) self.printTree() pass def save(self, filename = None ): if (filename == None): filename = ‘./data.npy‘ nodeLst = self.fetchAllNode() dataList =[] for node in nodeLst : print node dataList += [ node.getDataInfo() ] # Node. pass npData = np.array( dataList ) np.save(filename, npData ) ‘‘‘ npData2 = np.load( ‘./data.npy‘ ) lst2 = npData2.tolist() print ‘lst2:‘, lst2 ‘‘‘ def printTree(self): nodeLst = self.fetchAllNode() for node in nodeLst : print node.getDataInfo() pass def getRootNode(self): return self.root_Node; def getCurNode(self): return self.cur_Node; # nodeinfo: id, level, parentId, childrenIdList[0, 0, 0, []] def addSubNodeToCur_Node(self, subNode, isMoveToSub = True ): newNodeId = self.NodeCount self.NodeCount += 1 self.cur_Node.children += [subNode] self.cur_Node.nodeInfo[3] += [ newNodeId ] subNode.parent = self.cur_Node subNode.nodeinfo[0] = newNodeId subNode.nodeinfo[1] = self.cur_Node.nodeInfo[1] +1 subNode.nodeinfo[2] = self.cur_Node.nodeInfo[0] subNode.nodeinfo[3] = [] if( isMoveToSub ): self.cur_Node = subNode pass def addSubNodeToCur_Data(self, data, NodeId = 0, isMoveToSub = True ): subNode = Node(data=data) if(NodeId == 0 ): newNodeId = self.NodeCount else: newNodeId = NodeId self.NodeCount += 1 self.cur_Node.children += [subNode] self.cur_Node.nodeInfo[3] += [ newNodeId ] subNode.parent = self.cur_Node subNode.nodeInfo[0] = newNodeId subNode.nodeInfo[1] = self.cur_Node.nodeInfo[1] +1 subNode.nodeInfo[2] = self.cur_Node.nodeInfo[0] subNode.nodeInfo[3] = [] #print ‘addSubNodeToCur_Data, now in :‘, self.cur_Node.nodeInfo[0] if( isMoveToSub ): self.cur_Node = subNode #print ‘addSubNodeToCur_Data, now in :‘, self.cur_Node.nodeInfo[0] def moveToNode(self,nodeId ): node = self.serachNodeId( self.root_Node, nodeId) if (node!= None): self.cur_Node = node return node else: return None pass def moveToNode_byNode(self, node ): self.cur_Node = node pass def fetchAllNode(self): return self.touchAllNode( self.getRootNode() ) pass # in function def touchAllNode(self, thisNode): allNode = [ thisNode ] for node in thisNode.children : allNode += self.touchAllNode( node ) return allNode def serachNodeId(self, thisNode, nodeId): if( thisNode.nodeInfo[0] == nodeId ): return thisNode else: for node in thisNode.children : ret = self.serachNodeId(node , nodeId ) if( ret != None ): return ret return None ###### 內部函數 def moveUp(self): if( self.cur_Node.nodeInfo[0] == 0 ): print ‘moveUp error‘ return False self.cur_Node = self.cur_Node.parent; return True ######## test def demo1(self): data = [‘{}‘.format( random.random() * 10000 )] self.addSubNodeToCur_Data( data ) self.moveUp(); data = [‘{}‘.format( random.random() * 10000 )] self.addSubNodeToCur_Data( data ) self.save() pass def deom2(self): self.load() # 下100局, 拓展棋譜 def testWzTree(): ‘‘‘ 生成 落子樹 ‘‘‘ tree = Tree() for i in range(100): WzOne( tree ) tree.printTree() tree.save() #讀取樹 print ‘------------------------------------------------‘ tree2 = Tree() tree2.load() tree2.printTree() pass # 下一局, 隨機選一個點, 下滿棋盤 def WzOne( tree ): tree.reset() #讓樹當前節點回到根節點 avilAction = [(0, 0), (0, 1), (1, 0), (1, 1)] # 4個落子點 while( len(avilAction) >0 ): #無落子點 action = random.choice(avilAction) #隨機一個落子點 avilAction.remove(action) data = list( action ) # 遍歷當前前節點的子節點, 如果已經存在該落子點, 則跳到子節點 # 如果當前節點的子節點, 無該落子點, 則增加一個子節點 isExist = False for node in tree.cur_Node.children : if( node.getData() == data ): isExist = True tree.moveToNode_byNode( node ) break if( isExist == False ): tree.addSubNodeToCur_Data(data) pass def test(): testWzTree() return tree = Tree() tree.test() return if __name__ == "__main__": test()
python 實現樹結構