1. 程式人生 > >圖的儲存結構與搜尋

圖的儲存結構與搜尋

  • 圖是什麼

    圖,顧名思義,就是一張大大的網,網中的每個節點都與另外一個節點直接或者間接的聯絡。網際網路就是一個大大的圖,從A到B到C經過的路由,就是圖的搜尋演算法。

    要給圖下一個定義,那就是在眾多離散節點中可以產生迴路的資料結構。比之線性資料結構的單線特性,樹形結構的多路分叉特性,圖的最大特點就是有迴路的樹形結構。圖可以表示很多具體的事物,它是現實的一種抽象模型。比如地圖定址、路由定址、狀態機等。圖是用來當做計算模型使用的,在某些高階場合十分適用。

  • 圖的定義

    圖的定義主要通過兩種方式:1.鄰接矩陣;2.鄰接表

    還有一種非常複雜的定義:十字連結串列

    鄰接矩陣的定義十分的直觀,它通過二維矩陣來表示節點之間的消耗。比如a[i][j]表示從i節點到j節點的消耗,可以定義為0則沒有通路。 在無向圖中,通常這個矩陣是對稱陣,即a[i][j] = a[j][i];在有向圖中,它是反對稱陣,即a[i][j] = -a[j][i]。可以看到使用鄰接矩陣來儲存消耗了額外的一半空間,但是查詢非常的方便。

    鄰接表,使用鄰接表來儲存圖,則儲存一個連結串列陣列,每個頭結點就是圖的節點,它指向與它有聯絡的節點並存儲消耗。這種方式節省了空間,但是查詢節點是否有聯絡的時候必須遍歷連結串列,這樣增加了時間的消耗。

    圖的種類大概會有:無向圖,有向圖,雙向圖

    Java的鄰接矩陣實現,用一個類儲存矩陣,在建構函式時候傳入大小,新建的是n*n的矩陣,然後就是寫API了,無外乎CRUD。

複製程式碼
package Graph;
import java.util.ArrayList;
import java.util.List;

/**
 * 鄰接矩陣
 * @author ctk
 *
 */
public
class AdjacencyMatrixGraph { private int edges ; private int[][] weight; private List<String> nodes; private int vertex; public AdjacencyMatrixGraph(int vertex){ this.vertex = vertex; weight = new int[vertex][vertex]; nodes = new ArrayList<>(vertex); edges
= 0; } //獲得節點數 public int getNodes(){ return nodes.size(); } //獲得邊數 public int getEdges(){ return edges; } //插入節點 public boolean insertNode(String name){ if(nodes.size() == vertex) return false; nodes.add(name); return true; } //設定權重 public boolean setWeight(int i,int j,int weight){ if(i >= vertex || j >= vertex || i < 0 || j < 0) return false; if(this.weight[i][j] == 0) edges++; this.weight[i][j] = weight; return true; } //獲得i節點 public String getNode(int i){ if(i > nodes.size()) return null; else return nodes.get(i); } //獲得邊權重 public int getWeight(int i,int j){ if(i >= vertex || j >= vertex || i < 0 || j < 0) return -1; else return weight[i][j]; } //刪除邊 public void deleteEdge(int i,int j){ if(i >= vertex || j >= vertex || i < 0 || j < 0){ System.out.println("越界"); return ; } weight[i][j] = 0; } //列印矩陣 public void printMatrix(){ for(int i=0;i<weight.length;i++) { for(int j=0;j<weight[i].length;j++){ System.out.print(weight[i][j]+" "); } System.out.println(); } } public static void main(String[] args) { AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); String node1 = "n1"; String node2 = "n2"; String node3 = "n3"; String node4 = "n4"; graph.insertNode(node1); graph.insertNode(node2); graph.insertNode(node3); graph.insertNode(node4); graph.setWeight(0, 1, 2); graph.setWeight(0, 2, 5); graph.setWeight(2, 3, 8); graph.setWeight(3, 0, 7); System.out.println("邊數:"+graph.getEdges()); System.out.println("節點數:"+graph.getNodes()); graph.printMatrix(); } }
複製程式碼

    鄰接表的實現,寫一個節點類,然後初始化的時候新建這麼大的陣列,把每個空格放入新建的節點。

複製程式碼
package Graph;

import java.util.ArrayList;
import java.util.List;

/**
 * 鄰接表實現的圖
 * @author ctk
 *
 */
public class ListGraph {
    private List<GraphNode> gNodes ;
    private int vertex ;
    private int edges ;
    public ListGraph(int vertex){
        this.vertex = vertex ;
        gNodes = new ArrayList<>(vertex);
        for(int i=0;i<vertex;i++)
        {
            GraphNode gnode = new GraphNode();
            gnode.setNodeIndex(i);
            gnode.setNext(null);
            gNodes.add(gnode);
        }
    }
    //新增邊
    public void addEdge(int i,int j,int weight){
        if(i >= vertex || j >= vertex || i < 0 || j < 0)
        {
            System.out.println("輸入的i和j超過範圍");
            return;
        }
        GraphNode gnode = gNodes.get(i);
        boolean isAlter = false;
        while(gnode.getNext() != null)
        {
            if(gnode.getNodeIndex() == j)
            {
                gnode.setData(weight);
                isAlter = true;
                break;
            }
            gnode = gnode.getNext();
        }
        if(i == j){
            gnode.setData(weight);
            isAlter = true;
        }
        if(!isAlter){
            GraphNode edgeNode = new GraphNode();
            edgeNode.setData(weight);
            edgeNode.setNodeIndex(j);
            edgeNode.setNext(null);
            gnode.setNext(edgeNode);
        }
    }
    //生成鄰接矩陣
    public int[][] getMartix(){
        int[][] martix = new int[vertex][vertex];
        GraphNode temp = null;
        for(int i=0;i<gNodes.size();i++){
            temp = gNodes.get(i);
            while(temp != null){
                martix[i][temp.getNodeIndex()] = temp.getData();
                temp = temp.getNext();
            }
        }
        return martix;
    }
    //獲得某邊
    public int getEdge(int i,int j){
        int weight = 0;
        if(i >= vertex || j >= vertex || i < 0 || j < 0)
        {
            System.out.println("輸入的i和j超過範圍");
            return weight;
        }
        GraphNode temp = gNodes.get(i);
        while(temp != null){
            if(temp.getNodeIndex() == j){
                weight = temp.getData();
                break;
            }
            temp = temp.getNext();
        }
        return weight;
    }
    public int getVertex() {
        return vertex;
    }
    public int getEdges() {
        return edges;
    }
    
    public static void main(String[] args) {
        ListGraph graph = new ListGraph(5);
        graph.addEdge(0, 1, 2);
        graph.addEdge(0, 2, 3);
        graph.addEdge(1, 1, 4);
        graph.addEdge(2, 3, 6);
        int[][] martix = graph.getMartix();
        for(int i =0;i<martix.length;i++){
            for(int j=0;j<martix[i].length;j++)
                System.out.print(martix[i][j]+" ");
            System.out.println();
        }
        System.out.println("獲取邊<1,1> :"+graph.getEdge(1, 1));
    }
}
//節點類
class GraphNode{
    private int nodeIndex;
    private int data;
    private GraphNode next;
    
    public int getNodeIndex() {
        return nodeIndex;
    }
    public void setNodeIndex(int nodeIndex) {
        this.nodeIndex = nodeIndex;
    }
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    public GraphNode getNext() {
        return next;
    }
    public void setNext(GraphNode next) {
        this.next = next;
    }
}
複製程式碼

                    

    上圖使用一種工具Graphviz來實現畫圖的,有興趣的同學可以百度一下,有一個建模語言dot。

  • 圖遍歷演算法

    對線性或者樹形進行遍歷,通常都十分簡單,因為到null就停止了,而圖是有迴路的資料結構,如果使用之前的遍歷策略,則很容易就死迴圈了。

    對於圖來說,遍歷的策略通常是:深度優先(DFS),廣度優先(BFS)

    深度優先(DFS)

    對於一個節點來說,儘可能的往下走,走到盡頭再去選擇這個節點的另外一條路。

    以上圖來說,從根節點觸發,先一個一個遍歷完橙色,再遍歷完綠色,最後遍歷完藍色。當然選擇節點的時候有個能先綠色,也可能先藍色,不過一旦選擇一種顏色之後就會遍歷到底。樹形結構明白之後,圖的DFS更加清楚了。

                

    以上圖為例,從a節點出發的DFS,假設裝入都是按順序的,首先遍歷的是a-b-c-d-g,然後遍歷e-f-h。總之順序就是a-b-c-d-g-e-f-h。

    要實現深度優先的搜尋,觀察這個遍歷順序,知道每次把節點拿到之後子節點優先遍歷,那是一個先進後出的順序,使用棧來儲存。比如a拿到之後他的子節點b,e入棧,假設b在棧頂,則彈出遍歷b後把b的子節點c,g壓入棧中。如此往復,直到所有的節點都Visited。

    使用C++定義一個集合專門存放節點的。

複製程式碼
const int MAX = 100;

//圖節點
typedef struct {
    int edges[MAX][MAX];
    int n;
    int e;
    int visited[MAX];
}MGraph;
複製程式碼

    

    這是鄰接矩陣的儲存方式。接著是深度優先遍歷方法。並採用上述例子模型。

複製程式碼
void DFS2(MGraph &G,int v){
    stack<int> temp;
    cout<<"節點:"<<v<<" ";
    G.visited[v] = 1;
    temp.push(v);
    while(!temp.empty()){
        int i,j;
        i=temp.top();
        for(j=0;j<G.n;j++){
            if(G.edges[i][j] != 0 && G.visited[j] == 0)
            {
                cout<<"節點:"<<j<<" ";
                G.visited[j] = 1;
                temp.push(j);
                break;
            }
        }
        if(j==G.n)
            temp.pop();
    }
    cout<<endl;
    
}
複製程式碼

    由於深度優先是一個棧模型,所以遞迴很符合它的運算模型,所以可以使用遞迴來計算。

複製程式碼
void DFS1(MGraph &G,int v){
    int i;
    cout<<"節點:"<<v<<" ";
    G.visited[v] = 1;
    for(i=0;i<G.n;i++)
    {
        if(G.edges[v][i] !=0 && G.visited[i] == 0)
        {
            DFS1(G, i);
        }
    }

}
複製程式碼

    程式的執行結果是。

    廣度優先(BFS)

    廣度優先顧名思義,類似於樹的層序遍歷。把這個節點的子節點全部遍歷之後,再把子節點的孩子遍歷。在戰爭中類似於把周圍的領土先佔領完畢再進行擴張。

    由於廣度優先這個特性,使得它的實現是使用佇列這種先進先出的計算模式。把父節點進入佇列,然後出佇列的時候把父節點的孩子節點依次加入佇列尾部。

    廣度優先的C++程式碼。

複製程式碼
void BFS(MGraph &G,int v){
    queue<int> Q;
    cout<<"節點:"<<v<<" ";
    Q.push(v);
    while(!Q.empty()){
        int i,j;
        i=Q.front();
        Q.pop();
        for(j=0;j<G.n;j++){
            if(G.edges[i][j] != 0 && G.visited[j] == 0){
                cout<<"節點:"<<j<<" ";
                G.visited[j] = 1;
                Q.push(j);
            }
        }
    }
    cout<<endl;
}
複製程式碼

    同上例,BFS的結果是。

    從效能上來講,深度優先優於廣度優先,因為廣度優先的佇列會比棧容量要大,採用哪種搜尋主要看需求。

相關推薦

儲存結構搜尋

圖是什麼    圖,顧名思義,就是一張大大的網,網中的每個節點都與另外一個節點直接或者間接的聯絡。網際網路就是一個大大的圖,從A到B到C經過的路由,就是圖的搜尋演算法。     要給圖下一個定義,那就是在眾多離散節點中可以產生迴路的資料結構。比之線性資料結構的單線特性,樹形結構的多路分叉特性,圖的最大特點

演算法資料結構(四) 的物理儲存結構深搜、廣搜(Swift版)

開門見山,本篇部落格就介紹圖相關的東西。圖其實就是樹結構的升級版。上篇部落格我們聊了樹的一種,在後邊的部落格中我們還會介紹其他型別的樹,比如紅黑樹,B樹等等,以及這些樹結構的應用。本篇部落格我們就講圖的儲存結構以及圖的搜尋,這兩者算是圖結構的基礎。下篇部落格會在此基礎上聊一下最小生成樹的Prim演算法以及克魯

順序儲存結構鏈式儲存結構的比較(也可以說的順序表連結串列的比較)

1、鏈式儲存結構的儲存空間在邏輯上是連續的,但是在物理上是離散的;而順序儲存結構的儲存空間在邏輯上是連續的,在物理上也是連續的。 2、鏈式儲存儲存密度小,但空間利用率較高;順序儲存儲存密度大,但空間利用率較低。 3、順序結構優點是可以隨機讀取元素,缺點是插入和刪除元素要移動大量元素,

資料結構 筆記:樹的儲存結構實現

樹的結點的儲存結構 GTreeNode的設計與實現 template<typename T> class GTreeNode : public TreeNode<T> { public: LinkList<GTreeeNode<T>*&

第八節課:第六章儲存結構磁碟劃分

 筆記 (借鑑請修改) 6.3、檔案系統與資料資料         目前linux最常見的檔案系統:         ext3:日誌檔案系統。宕機時可自動恢復資料資料,容量越大恢復時間越長,且不能保證百分百不

資料結構專題——二叉樹的儲存結構基本操作

一般來說,二叉樹使用連結串列來定義。 與普通連結串列的差別在於,二叉樹每個節點有兩條出邊,因此指標域變成了兩個,分別指向左子樹根節點地址和右子樹的根節點地址,如果某個子樹不存在,則指向NULL,其他地方與普通連結串列完全相同,這樣的連結串列又被叫作二叉連結串列。 二叉樹資

棧的順序儲存結構基本運算

// // main.cpp // 棧的基本運算 // // Created by 柯木超 on 2018/12/3. // Copyright © 2018年 柯木超. All rights reserved. // #include <iostream> #define m

C++學習之deque底層儲存結構迭代器失效情況

C++ STL容器deque和vector很類似,也是採用動態陣列來管理元素。 使用deque之前需包含標頭檔案: #include <deque> 它是定義在名稱空間std內的一個class template: template<class _

| 儲存結構:鄰接表、鄰接多重表、十字連結串列及C語言實現

上一節介紹瞭如何使用順序儲存結構儲存圖,而在實際應用中最常用的是本節所介紹的鏈式儲存結構:圖中每個頂點作為連結串列中的結點,結點的構成分為資料域和指標域,資料域儲存圖中各頂點中儲存的資料,而指標域負責表示頂點之間的關聯。 使用鏈式儲存結構表示圖的常用方法有 3 種:鄰接表、

| 儲存結構:鄰接矩陣及C語言實現

使用圖結構表示的資料元素之間雖然具有“多對多”的關係,但是同樣可以採用順序儲存,也就是使用陣列有效地儲存圖。 鄰接矩陣 鄰接矩陣(Adjacency Matrix),又稱 陣列表示法,儲存方式是用兩個陣列來表示圖: 一個一維陣列儲存圖中頂點本身資訊

Caffe原始碼理解1:Blob儲存結構設計

Blob作用 A Blob is a wrapper over the actual data being processed and passed along by Caffe, and also under the hood provides synchronization capability b

Oracle邏輯儲存結構物理儲存結構

Oracle邏輯儲存結構包括表空間,各種段,區間,資料塊等幾種基本結構。 1)表空間是資料庫的邏輯劃分,每個資料庫至少有一個表空間,USER表空間供一般使用者使用,RBS表空間供回滾使用,一個表空間只能屬於一個數據庫。每一個表空間由同一磁碟上的一個或多個數據檔案組成。表空

結構操作

圖的結構 1、鄰接矩陣結構 #define MAXSIZE 100 #define INFINITY 65535 //無窮大,表示兩點直接沒有路徑連線 typedef struct Mgraph{ char vex[MAXSIZE]; //定義頂點陣列 int a

InnoDB引擎--儲存結構檔案

資料庫是資料的集合,資料庫管理系統(DBMS)是操作和管理資料庫的應用程式。資料庫應用主要有兩類:OLAP(聯機分析處理)和OLTP(聯機事務處理)。 OLAP的主要特點是: 實時性要求不高 資料量大 併發量小 OLTP的主要特點是: 實時性要求高

資料的儲存結構邏輯結構

資料儲存結構比較 順序結構:一段連續的記憶體空間。     優點:隨機訪問     缺點:插入刪除效率低,大小固定 鏈式結構:不連續的記憶體空間     優點:大小動態擴充套件,插入刪除效率高     缺點:不能隨機訪問。 索引結構:為了方便查詢,整體無序,但索引塊之間有序

資料結構知識點--儲存結構邏輯結構

1. 資料的邏輯結構 邏輯結構是指資料元素之間的邏輯關係,即從邏輯關係上描述資料。它與資料的儲存無關,是獨立於計算機的。資料的邏輯結構分為線性結構和非線性結構,線性表是典型的線性結構;集合、樹和圖是典型的非線性結構。資料的邏輯結構分類見圖1-1。 集合結構中的資料元素之間除了 “同屬於一個集合”的關係外,別

17-看理解資料結構算法系列(NoSQL儲存-LSM樹)

關於LSM樹 LSM樹,即日誌結構合併樹(Log-Structured Merge-Tree)。其實它並不屬於一個具體的資料結構,它更多是一種資料結構的設計思想。大多NoSQL資料庫核心思想都是基於LSM來做的,只是具體的實現不同。所以本來不打算列入該系列,但是有朋友留言了好幾次讓我講LS

儲存結構(鄰接矩陣鄰接表)及其C++實現

一、圖的定義 圖是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:                    G=(V,E) 其中:G表示一個圖,V是圖G中頂點的集合,E是圖G中頂點之間邊的集合。 注: 線上性表中,元素個數可以為零,稱為空表; 在樹中,結

儲存結構:鄰接矩陣鄰接表(稠密稀疏

稠密圖用 鄰接矩陣儲存 稀疏圖用 鄰接表儲存 原因: 鄰接表只儲存非零節點,而鄰接矩陣則要把所有的節點資訊(非零節點與零節點)都儲存下來。 稀疏圖的非零節點不多,所以選用鄰接表效率高,如果選用鄰接矩陣則效率很低,矩陣中大多數都會是零節點! 稠密圖的非零界點多,零節點少,選

資料結構演算法16-儲存結構

圖的儲存結構 圖的儲存結構相較線性表與樹來說就更加複雜了。首先,我們口頭上說的“頂點的位置”或“鄰接點的位置”只是一個相對的概念。其實從圖的邏輯結構定義來看,圖上任何一個頂點都可被看成是第一個頂點,任一頂點的鄰接點之間也不存在次序關係。如下圖   用之前學過的資料結構來表