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

圖結構的python實現

圖是一種非線性的關係型資料結構,關於圖的概念以及性質網路上已有很多資料,我不再多說。本文主要介紹兩種基於python的圖結構的實現方法。

鄰接矩陣實現

鄰接矩陣是表示圖中頂點間鄰接關係的方陣。對於n個頂點的圖G=(V,E),其鄰接矩陣是一個n\times n的方陣,圖中每個頂點(按順序)對應於矩陣裡的一行和一列,矩陣元素表示圖中的鄰接關係。一般對於不帶權值的圖,用1表示節點有邊,用0表示節點無邊。

對於有權圖,矩陣元素值即為邊的權值,可用0或\infty表示兩頂點不相連。顯然,對於無向圖,其鄰接矩陣是一個對稱矩陣。

如上圖所示連個圖的鄰接矩陣分別為 

   A_{G5}=\begin{pmatrix} 0& 0& 1\\ 1& 0& 1\\ 0& 1& 0 \end{pmatrix}  A_{G6}=\begin{pmatrix} 0& 1& 1& 0\\ 1& 0& 1& 0\\ 1& 1& 0& 1\\ 0& 0& 1& 0 \end{pmatrix}

其中a,b,c的行下標分別為0,1,2。

在python中實現的程式碼:

class Graph:
    def __init__(self,mat,unconn=0):
        vnum=len(mat)
        for x in mat:
            if len(x)!=vnum:
                raise ValueError("引數錯誤")
        self._mat=[mat[i][:] for i in range(vnum)]  #做拷貝
        self._unconn=unconn
        self._vnum=vnum

    #頂點個數
    def vertex_num(self):
        return self._vnum

    #頂點是否無效
    def _invalid(self,v):
        return v<0 or v>=self._vnum

    #新增邊
    def add_edge(self,vi,vj,val=1):
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi)+" or "+str(vj)+"不是有效的頂點")
        self._mat[vi][vj]=val

    #獲取邊的值
    def get_edge(self,vi,vj):
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi)+" or "+str(vj)+"不是有效的頂點")
        return self._mat[vi][vj]

    #獲得一個頂點的各條出邊
    def out_edges(self,vi):
        if self._invalid(vi):
            raise ValueError(str(vi)+"不是有效的頂點")
        return self._out_edges(self._mat[vi],self._unconn)

    @staticmethod
    def _out_edges(row,unconn):
        edegs=[]
        for i in range(len(row)):
            if row[i]!=unconn:
                edegs.append((i,row[i]))
        return edegs

    def __str__(self):
        return "[\n"+",\n".join(map(str,self._mat))+"\n]"+"\nUnconnected: "+str(self._unconn)

 鄰接矩陣儲存在mat中,是一個二層巢狀的列表。注意到,該圖類沒有定義新增頂點的操作,主要是因為新增頂點時,要橫向和縱向擴充鄰接矩陣,操作不便。

可見用鄰接矩陣表示圖的方式所需的空間開銷是和圖的頂點數的平方成正比的,如果圖的頂點數比較多,並且圖的每個頂點的度並不多,這樣鄰接矩陣就是一個稀疏矩陣,其中大部分元素都是無用的,反而耗費了大量的儲存空間。另外,也不便新增頂點。

鄰接表實現 

所謂鄰接表,就是為圖中每個頂點關聯一個邊表,其中記錄這個頂點的所有鄰接邊。這樣,一個頂點的表,其中每個頂點又關聯一個邊表,就構成了圖的一種表示。如下圖,右圖是左圖的鄰接表表示,但沒有表示權值。

 

 以下是在python中的實現:

class GraphAL(Graph):
    def __init__(self,mat=[],unconn=0):
        vnum = len(mat)
        for x in mat:
            if len(x) != vnum:
                raise ValueError("引數錯誤")
        self._mat=[Graph._out_edges(mat[i],unconn) for i in range(vnum)]
        self._unconn = unconn
        self._vnum = vnum

    #新增頂點
    #返回該頂點編號
    def add_vertex(self):
        self._mat.append([])
        self._vnum+=1
        return self._vnum-1

    #新增邊
    def add_edge(self,vi,vj,val=1):
        if self._vnum==0:
            raise ValueError("不能向空圖新增邊")
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi)+" or "+str(vj)+"不是有效的頂點")

        row=self._mat[vi]
        i=0
        while i<len(row):
            if row[i][0]==vj:
                self._mat[vi][i]=(vj,val)   #如果原來有到vj的邊,修改mat[vi][vj]的值
                return
            if row[i][0]>vj:    #原來沒有到vj的邊,退出迴圈後加入邊
                break
            i+=1
        self._mat[vi].insert(i,(vj,val))

    #獲取邊的值
    def get_edge(self,vi,vj):
        if self._invalid(vi) or self._invalid(vj):
            raise ValueError(str(vi)+" or "+str(vj)+"不是有效的頂點")
        for i,val in self._mat[vi]:
            if i==vj:
                return val
        return self._unconn

    # 獲得一個頂點的各條出邊
    def out_edges(self,vi):
        if self._invalid(vi):
            raise ValueError(str(vi)+"不是有效的頂點")
        return self._mat[vi]

 可以,這種表示圖的方式更加節省空間,且插入頂點操作比較簡單。但由於每個頂點的邊表的元素是按終點的編號排序的,因此在插入邊時,需要從頭遍歷該列表,在一定程度上增加了演算法的複雜度。