1. 程式人生 > >python 實現樹結構

python 實現樹結構

elf ... () UNC reat 分析 ali info 一個人

簡述:


研究 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 實現樹結構