位元詩人 [Lua] 程式設計教學:實作圖 (Graph)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

註:此處的圖,是指數學上的圖論 (graph theory),而非電腦圖像 (computer graphics)。

圖 (graph) 是一種非線性、非階層的資料結構,由不重覆的點 (vertex or node) 和邊 (edge) 組成。理論上,樹 (tree) 是圖的特例,但圖的實作較複雜,通常不會直接拿圖取代樹。

圖本身是數學上的抽象理論,內部常見的實作方式如下:

  • Edge list
  • Adjacency matrix
  • Adjacency list

Edge list 就是直接以陣列儲存成對的點,實作起來很簡單,但存取資料時會其效率會退化為線性,沒有充分利用到圖本身非線性所帶來的優點,實際上較少以 edge list 實作圖。

如同其名稱,adjacency matrix 以矩陣儲存圖;因矩陣內部通常是陣列,此種實作存取資料快速,但大量增減資料時效率較差,在圖較稀疏時,也比較耗空間。

Adjacency list 內部則是以串列儲存圖,此種實作在點較稀疏時,較 adjacency matrix 省空間,增減資料也比較有效率,但有時存取資料的效率會退化成線性。有一種變體是使用雜湊表來存圖,利用雜湊表非線性存取的特性避免存取效率退化。

圖有許多種變體,此處以單純的有向性圖 (simple digraph) 來展示如何實作圖,內部則利用 table 來做 adjacency list:

local Graph = {}
package.loaded["Graph"] = Graph

Graph.__index = Graph

function Graph:new()
    self = {}
    self._graph = {}
    setmetatable(self, Graph)
    return self
end

function Graph:hasNode(n)
    return self._graph[n] ~= nil
end

function Graph:hasEdge(u, v)
    return self._graph[u] ~= nil and self._graph[u][v] == true
end

function Graph:addNode(n)
    if self._graph[n] == nil then
        self._graph[n] = {}
    end
end

function Graph:addEdge(u, v)
    self:addNode(u)
    self:addNode(v)

    self._graph[u][v] = true
end

function Graph:removeEdge(u, v)
    if self._graph[u][v] == true then
        self._graph[u][v] = nil
    end
end

function Graph:removeNode(n)
    if self._graph[n] ~= nil then
        for p, _ in pairs(self._graph) do
            local isContinue = false

            if p == n then
                isContinue = true
            end

            if not isContinue then
                for q, _ in pairs(self._graph[p]) do
                    if q == n then
                        self._graph[p][q] = nil
                    end
                end
            end
        end

        self._graph[n] = nil
    end
end

return Graph

由於這裡僅是展示圖的實作方式,我們只實作基本的點和邊的增刪和存取,圖論有許多的運算,這裡就不一一展示。使用範例如下:

local graph = require("digraph")

do
    local g = graph:new()

    g:addEdge("a", "b")

    assert(g:hasNode("a"))
    assert(g:hasNode("b"))
    assert(g:hasEdge("a", "b"))
end

do
    local g = graph:new()

    g:addEdge("a", "b")
    g:addEdge("a", "c")

    assert(g:hasNode("a"))
    assert(g:hasNode("b"))
    assert(g:hasNode("c"))
    assert(g:hasEdge("a", "b"))
    assert(g:hasEdge("a", "c"))

    g:removeEdge("a", "c")

    assert(g:hasNode("a"))
    assert(g:hasNode("b"))
    assert(g:hasNode("c"))
    assert(g:hasEdge("a", "b"))
    assert(g:hasEdge("a", "c") == false)
end

do
    local g = graph:new()

    g:addEdge("a", "b")
    g:addEdge("a", "c")

    assert(g:hasNode("a"))
    assert(g:hasNode("b"))
    assert(g:hasNode("c"))
    assert(g:hasEdge("a", "b"))
    assert(g:hasEdge("a", "c"))

    g:removeNode("a")

    assert(g:hasNode("a") == false)
    assert(g:hasNode("b"))
    assert(g:hasNode("c"))
    assert(g:hasEdge("a", "b") == false)
    assert(g:hasEdge("a", "c") == false)
end
關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。