實踐多種搜索算法求解八數碼問題python實現

    哎,好久沒寫博文了,其實仔細想來,時間還是蠻多的,以後還是多寫寫吧!

    之前看過經典的搜索路徑方法,印象較深的也就BFS(廣度優先),DFS(深度優先)以及A*搜索,但沒實踐過,就借八數碼問題,來通通實現遍,觀察下現象唄~~~

        首先,怎麼說也得把數碼這玩意基本操作實現了唄!上代碼~

class puzzled:
    def __init__(self,puzzled):
        self.puzzled=puzzled
        self.__getPuzzledInfo()
        
    def __getPuzzledInfo(self):
        self.puzzledWid=len(self.puzzled[0])
        self.puzzledHei=len(self.puzzled)
        self.__f1=False
        for i in range(0,self.puzzledHei):
            for j in range(0,self.puzzledWid):
                if(self.puzzled[i][j]==0):
                    self.zeroX=j
                    self.zeroY=i
                    self.__f1=True
                    break
            if(self.__f1):
                break
    def printPuzzled(self):
        for i in range(0,len(self.puzzled)):
            print self.puzzled[i]
        print ""
            
    def isRight(self):
        if(self.puzzled[self.puzzledHei-1][self.puzzledWid-1]!=0):
            return False
        for i in range(0,self.puzzledHei):
            for j in range(0,self.puzzledWid):
                if(i*self.puzzledWid+j+1!=self.puzzled[i][j]):
                    if(i!=self.puzzledHei-1 or j!=self.puzzledWid-1):
                        return False
        return True
    def move(self,dere):#0 up,1 down,2 left,3 right
        if(dere==0 and self.zeroY!=0):
            self.puzzled[self.zeroY-1][self.zeroX],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY-1][self.zeroX]
            self.zeroY-=1
            return True
        
        
        elif(dere==1 and self.zeroY!=self.puzzledHei-1):
            self.puzzled[self.zeroY+1][self.zeroX],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY+1][self.zeroX]
            self.zeroY+=1            
            return True
        
        
        elif(dere==2 and self.zeroX!=0):
            self.puzzled[self.zeroY][self.zeroX-1],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY][self.zeroX-1]
            self.zeroX-=1
            return True
        
        elif(dere==3 and self.zeroX!=self.puzzledWid-1):
            self.puzzled[self.zeroY][self.zeroX+1],self.puzzled[self.zeroY][self.zeroX] = self.puzzled[self.zeroY][self.zeroX],self.puzzled[self.zeroY][self.zeroX+1]
            self.zeroX+=1
            return True
        return False
    def getAbleMove(self):
        a=[]
        if(self.zeroY!=0):
            a.append(0)
        if(self.zeroY!=self.puzzledHei-1):
            a.append(1)
        if(self.zeroX!=0):
            a.append(2)
        if(self.zeroX!=self.puzzledWid-1):
            a.append(3)             
        return a
    def clone(self):
        a=copy.deepcopy(self.puzzled)
        return puzzled(a)
    def toString(self):
        a=""
        for i in range(0,self.puzzledHei):
            for j in range(0,self.puzzledWid):
                a+=str(self.puzzled[i][j])
        return a
    def isEqual(self,p):
        if(self.puzzled==p.puzzled):
            return True
        return False
    def toOneDimen(self):
        a=[]
        for i in range(0,self.puzzledHei):
            for j in range(0,self.puzzledWid):
                a.append(self.puzzled[i][j])
        return a
    
    
    def getNotInPosNum(self):
        t=0
        for i in range(0,self.puzzledHei):
            for j in range(0,self.puzzledWid):
                if(self.puzzled[i][j]!=i*self.puzzledWid+j+1):
                    if(i==self.puzzledHei-1 and j==self.puzzledWid-1 and self.puzzled[i][j]==0):
                        continue
                    t+=1
        return t
    def getNotInPosDis(self):
        t=0
        it=0
        jt=0
        for i in range(0,self.puzzledHei):
            for j in range(0,self.puzzledWid):
                if(self.puzzled[i][j]!=0):
                    it=(self.puzzled[i][j]-1)/self.puzzledWid
                    jt=(self.puzzled[i][j]-1)%self.puzzledWid
                else:
                    it=self.puzzledHei-1
                    jt=self.puzzledWid-1
                t+=abs(it-i)+abs(jt-j)
        return t    
    @staticmethod
    def generateRandomPuzzle(m,n,ran):
        
        tt=[]
        for i in range(0,m):
            t=[]
            for j in range(0,n):
                t.append(j+1+i*n)
            tt.append(t)
        tt[m-1][n-1]=0
        a=puzzled(tt)
        i=0
        while(i<ran):
            i+=1
            a.move(random.randint(0,4))
        return a

稍微註解一下,puzzled類表示一個數碼類,初始化利用

a=puzzled(  [1,2,3],
            [4,5,6],
            [7,8,0])

其中呢,0表示空格位置,上面初始化的便是一個正確的,未被打亂的位置~

其他的成員函數,看名稱就很好理解了唄~

ok,基礎打好了,接下來就該上節點類了:

class node:
    def __init__(self,p):
        self.puzzled=p
        self.childList=[]
        self.father=None
    def addChild(self,child):
        self.childList.append(child)
        child.setFather(self)
    def getChildList(self):
        return self.childList
    def setFather(self,fa):
        self.father=fa
    def displayToRootNode(self):
        t=self
        tt=0
        while(True):
            tt+=1
            t.puzzled.printPuzzled()
            t=t.father
            if(t==None):
                break
        print "it need "+str(tt)+ " steps!"
    def getFn(self):
        fn=self.getGn()+self.getHn() #A*
        #fn=self.getHn() #貪婪
        return fn
        
    def getHn(self):
        Hn=self.puzzled.getNotInPosDis()
        return Hn
        
    def getGn(self):
        gn=0
        t=self.father
        while(t!=None):
            gn+=1
            t=t.father
        return gn

對於節點類吧,也還是很好理解的,初始化方法

    a=node(
    puzzled([1,2,3],
            [4,5,6],
            [7,8,0])
            )

基礎都搭好了,重點人物該閃亮登場了唄~

class seartchTree:
    def __init__(self,root):
        self.root=root
        
    def __search2(self,hlist,m):  #二分查找,經典算法,從大到小,返回位置
                                  #若未查找到,則返回應該插入的位置
        low = 0   
        high = len(hlist) - 1   
        mid=-1
        while(low <= high):  
            mid = (low + high)/2  
            midval = hlist[mid]  
    
            if midval > m:  
                low = mid + 1   
            elif midval < m:  
                high = mid - 1   
            else:  
                return (True,mid)   
        return (False,mid)   
        
    def __sortInsert(self,hlist,m):#對於一個從大到小的序列,
                                    #插入一個數,仍保持從大到小
        t=self.__search2(hlist,m)
        if(t[1]==-1):
            hlist.append(m)
            return 0
        if(m<hlist[t[1]]):
            hlist.insert(t[1]+1, m)
            return t[1]+1
        else:
            hlist.insert(t[1], m)
            return t[1]    
    
    def breadthFirstSearch(self):#廣度優先搜索
        numTree=NumTree.NumTree()
        numTree.insert(self.root.puzzled.toOneDimen())
        t=[self.root]
        flag=True
        generation=0
        while(flag):
            print "it's the "+str(generation)+" genneration now,the total num of items is "+str(len(t))
            tb=[]
            for i in t:
                if(i.puzzled.isRight()==True):
                    i.displayToRootNode()
                    flag=False
                    break
                else:
                    for j in i.puzzled.getAbleMove():
                        tt=i.puzzled.clone()
                        tt.move(j)
                        a=node(tt)
                        if(numTree.searchAndInsert(a.puzzled.toOneDimen())==False):
                            i.addChild(a)
                            tb.append(a)
            t=tb
            generation+=1
            
    def depthFirstSearch(self):#深度優先搜索
        numTree=NumTree.NumTree()
        numTree.insert(self.root.puzzled.toOneDimen())        
        t=self.root
        flag=True
        gen=0
        while(flag):
            bran=0
            print "genneration: "+str(gen)
            if(t.puzzled.isRight()==True):
                t.displayToRootNode()
                flag=False
                break
            else:
                f1=True
                for j in t.puzzled.getAbleMove():
                    tt=t.puzzled.clone()
                    tt.move(j)
                    a=node(tt)
                    if(numTree.searchAndInsert(a.puzzled.toOneDimen())==False):
                        t.addChild(a)
                        t=a
                        f1=False
                        gen+=1
                        break
                if(f1==True):
                    t=t.father
                    gen-=1
                    
                    
    def AStarSearch(self):#A*
        numTree=NumTree.NumTree()
        numTree.insert(self.root.puzzled.toOneDimen())        
        leaves=[self.root]
        leavesFn=[0]
        while True:
            t=leaves.pop()  #open表
            print leavesFn.pop()
            if(t.puzzled.isRight()==True):
                t.displayToRootNode()
                break
            for i in t.puzzled.getAbleMove():
                tt=t.puzzled.clone()
                tt.move(i)
                a=node(tt)
                if(numTree.searchAndInsert(a.puzzled.toOneDimen())==False):#close表
                    t.addChild(a)
                    fnS=self.__sortInsert(leavesFn,a.getFn())
                    leaves.insert(fnS, a)

    注意到裏面存在一個 NumTree,這個是個啥玩意了,這個吧,其實是一個closed表,啥是Closed表呢?

    就是呀,在搜索的時候,得記錄已經擴展的節點,不然的話很有可能成爲不完備的搜索了(對於深度搜索),而且已經擴展的狀態,也對於數碼問題沒必要在擴展一次,那麼怎麼來實現記錄已經擴展的節點呢?

    一個最簡單的方法就是建立一個鏈表,將擴展的節點加進去,但這樣存在一個問題,就是由於每次在擴展節點時都得遍歷closed表,查找是否存在同樣的節點已經被擴展,這樣,複雜度就簡直太高了,完全不行唄~

    那麼咋辦呢?從上面代碼注意到,node對象裏面有一個Puzzled成員,實際上puzzled對象主要就由一個二維數組構成唄~這時候呀,我就把這個二維數組變爲一維唄

[1,2,3],                  
[4,5,6],         ->   [1,2,3,4,5,6,7,8,0]    
[7,8,0]

然後呢,利用trie樹存儲該數組,這樣一來,查找添加一步到位!

也就是上面代碼中的這個啦~

numTree.searchAndInsert(a.puzzled.toOneDimen())

貼上numtree代碼:

class NumTree:
    def __init__(self):
        self.root = Node()

    def insert(self, key):      # key is of type string                                                                                                                
        # key should be a low-case string, this must be checked here!                                                                                                  
        node = self.root
        for char in key:
            if char not in node.children:
                child = Node()
                node.children[char] = child
                node = child
            else:
                node = node.children[char]
        node.value = key

    def search(self, key):
        node = self.root
        for char in key:
            if char not in node.children:
                return None
            else:
                node = node.children[char]
        return node.value

    def display_node(self, node):
        if (node.value != None):
            print node.value
        for char in node.children.keys():
            if char in node.children:
                self.display_node(node.children[char])
        return

    def display(self):
        self.display_node(self.root)
        
    def searchAndInsert(self,m):
        if(self.search(m)==None):
            self.insert(m)
            return False
        return True
        
            
"""test

trie = NumTree()
print trie.searchAndInsert([1,2,3,4,5,6,7,8])
print trie.searchAndInsert([1,2,3,4,5,6,7,8,9])

trie.display()
"""

好了,這樣再看searchTree的代碼就比較清晰了唄,

哎,好吧,我承認這等低劣之作也只有我等渣渣才能寫出,其實再看時,可以發現很多地方都可以優化的,比如二維轉一維,這個其實花費的不少時間,其實可以在內部以一維形式存在的~


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章