Java數據結構和算法——圖

2021-01-09 Java0那點事兒0

1.1圖的概念

圖(Graph),是一種複雜的非線性表結構,圖的元素我們就叫做頂點(vertex),一個頂點可以與任意其他頂點建立連接關係,這種建立的關係叫做邊(edge),頂點相連接的邊的條數叫做度(degree) 。邊有方向的圖叫做有向圖 ,邊無方向的圖叫無向圖 。每條邊都有一個權重(weight),可以通過這個權重來表示 一些可度量的值 ,這樣的圖叫做帶權圖(weighted graph) 。

1.2 圖的存儲原理

1.2.1 鄰接矩陣存儲

圖最直觀的一種存儲方法就是,鄰接矩陣(Adjacency Matrix),鄰接矩陣的底層是一個二維數組。

如果是無向圖,如果頂點 i 與頂點 j 之間有邊,我們就將A[i][j]和 A[j][i]標記為 1。

如果是有向圖,如果頂點 i 到頂點 j 之間,有一條箭頭從頂點 i 指向頂點 j 的邊,那我們就將A[i][j]標記為 1。同理,如果有一條箭頭從頂點 j 指向頂點 i 的邊,我們就將 A[j][i]標記為 1 .

如果是帶權圖,數組中就存儲相應的權重。

1.2.2 鄰接矩陣的代碼實現

public class Graph {private List vertexList;//存儲點的鍊表 private int[][] edges;//鄰接矩陣,用來存儲邊,值是權值 private int numOfEdges;//邊的數目 public Graph(int n) { //初始化矩陣,一維數組,和邊的數目 edges=new int[n][n]; vertexList=new ArrayList(n); numOfEdges=0; } //得到結點的個數 public int getNumOfVertex() { return vertexList.size(); } //得到邊的數目 public int getNumOfEdges() { return numOfEdges; } //返回結點i的數據 public Object getValueByIndex(int i) { return vertexList.get(i); } //返回v1,v2的權值 public int getWeight(int v1,int v2) { return edges[v1][v2]; } //插入結點 public void insertVertex(Object vertex) { vertexList.add(vertex); } //插入邊 public void insertEdge(int v1,int v2,int weight) { edges[v1][v2]=weight; numOfEdges++; }}

1.2.3 鄰接表存儲

用鄰接矩陣來表示一個圖,雖然簡單、直觀,但是比較浪費存儲空間,對於無向圖來說,如果 A[i][j]等於 1,那 A[j][i]也肯定等於 1。實際上,我們只需要存儲一個就可以了。 如果我們存儲的是稀疏圖(Sparse Matrix),也就是說,頂點很多,但每個頂點的邊並不多,那鄰接矩陣的存儲方法就更加浪費空間了。針對上面鄰接矩陣比較浪費內存空間的問題,有另外一種圖的存儲方法,鄰接表(Adjacency List)。

存儲原理

每個頂點對應一條鍊表,鍊表中存儲的是與這個頂點相連接的其他頂點。圖中畫的是一個有向圖的鄰接表存儲方式,每個頂點對應的鍊表裡面,存儲的是指向的頂點。 前面的數組存儲的是所有的頂點,每一個頂點後面連接的塊代表前面頂點所指向的頂點和路線的權值。如果該點還指向其他頂點,則繼續在塊後面添加。例如A指向了B權值是4,那麼A後面就加上一塊,之後發現A還指向D權值是5,那麼就在塊尾繼續添加一塊。其實也就是數組+鍊表的結構.

1.2.4鄰接表代碼實現

根據鄰接表的結構和圖,圖其實是由頂點和邊組成的。抽象出兩種類,一個是Vertex頂點類,一個是Edge邊類

頂點

class Vertex{String name;//頂點名稱 Edge next;//從該定點出發的邊 public Vertex(String name, Edge edge) { this.name = name; this.next = edge; }}

邊:

class Edge{String name;//指向下一個頂點的名字 int weight;//權重 Edge next;//被指向的下一個邊 public Edge(String name, int weight, Edge next) { this.name = name; this.weight = weight; this.next = next; }}

添加和遍歷的實現

public class Graph2 {Map<String, Vertex> vertexsMap; Graph2(){ this.vertexsMap = new HashMap<>(); } /** * 添加頂點 * @param name 頂點名稱 */ public void insertVertex(String name) { Vertex vertex = new Vertex(name,null); vertexsMap.put(name,vertex); } /** * 插入變 * @param start 開始頂點名稱 * @param end 結束頂點名稱 * @param weight 權重 */ public void insertEdge(String start,String end,int weight){ //現獲取開始頂點 Vertex startVertex = vertexsMap.get(start); //開始頂點為null,直接返回,也可以新建一個頂點// if (startVertex==null){// return;// } if (startVertex==null){ startVertex = new Vertex(start,null); vertexsMap.put(start,startVertex); } //新建邊 Edge edge = new Edge(end, weight, null); if (startVertex.next==null){ startVertex.next=edge; }else{ //如果不為null 尋找節點的next==null的位置,掛上這個邊 Edge lastEdge = startVertex.next; while(lastEdge.next!=null){ lastEdge=lastEdge.next; } lastEdge.next=edge; } } public void print(){ Set<Map.Entry<String, Vertex>> set = vertexsMap.entrySet(); Iterator<Map.Entry<String, Vertex>> iterator = set.iterator(); while (iterator.hasNext()){ Map.Entry<String, Vertex> entry = iterator.next(); Vertex vertex = entry.getValue(); Edge edge = vertex.next; while(edge!=null){ System.out.println(vertex.name+"指向:"+edge.name+"權重是:"+edge.weight); edge= edge.next; } } } public static void main(String[] args) { Graph2 graph = new Graph2(); graph.insertVertex("A"); graph.insertVertex("B"); graph.insertVertex("C"); graph.insertVertex("D"); graph.insertVertex("E"); graph.insertVertex("F"); graph.insertEdge("C", "A", 1); graph.insertEdge("F", "C", 2); graph.insertEdge("A", "B", 4); graph.insertEdge("E", "B", 2); graph.insertEdge("A", "D", 5); graph.insertEdge("D", "F", 4); graph.insertEdge("D", "E", 3); graph.print(); }}

測試驗證:

按照此圖來存儲

先存儲A-F6個頂點,然後存7個邊:

1.3 圖的遍歷

遍歷是指從某個節點出發,按照一定的的搜索路線,依次訪問對數據結構中的全部節點,且每個節點僅訪問一次.

從給定圖中任意指定的頂點(稱為初始點)出發,按照某種搜索方法沿著圖的邊訪問圖中的所有頂點,使每個頂點僅被訪問一次,這個過程稱為圖的遍歷。遍歷過程中得到的頂點序列稱為圖遍歷序列

圖的遍歷可以分為2中策略:深度優先搜索 (DFS,Depth First Search )、廣度優先搜索(BFS,Breadth First Search )

1.3.1 深度優先搜索DFS

深度優先搜索,從起點出發,從規定的方向中選擇其中一個不斷地向前走,直到無法繼續為止,然後嘗試另外一種方向,直到最後走到終點。就像走迷宮一樣,儘量往深處走.

DFS 解決的是連通性的問題,即,給定兩個點,一個是起始點,一個是終點,判斷是不是有一條路徑能從起點連接到終點。起點和終點,也可以指的是某種起始狀態和最終的狀態。問題的要求並不在乎路徑是長還是短,只在乎有還是沒有。

遍歷過程

以下圖為例,需要依賴棧(Stack),特點是後進先出(LIFO)。

第一步選擇一個起始頂點,頂點A,把A壓棧標記它為訪問過(用紅色標記),並輸出到結果中。

第二步 尋找與 A 相連並且還沒有被訪問過的頂點,頂點 A 與 B、D、G 相連,而且它們都還沒有被訪問過,按照字母順序處理,所以將 B 壓入棧,標記它為訪問過,並輸出到結果中。

第三步,現在在頂點 B 上,重複上面的操作,由於 B 與 A、E、F 相連,如果按照字母順序處理的話,A 應該是要被訪問的,但是 A 已經被訪問了,所以我們訪問頂點 E,將 E 壓入棧,標記它為訪問過,並輸出到結果中

第四步,從 E 開始,E 與 B、G 相連,但是B剛剛被訪問過了,所以下一個被訪問的將是G,把G壓入棧,標記它為訪問過,並輸出到結果中

第五步,現在在頂點 G 的位置,由於與 G 相連的頂點都被訪問過了,類似於走到了一個死胡同,必須嘗試其他的路口了。所以這裡要做的就是簡單地將 G 從棧裡彈出,表示從 G 這裡已經無法繼續走下去了,看看能不能從前一個路口找到出路。

第六步,現在棧的頂部記錄的是頂點 E,來看看與 E 相連的頂點中有沒有還沒被訪問到的,發現它們都被訪問了,所以把 E 也彈出去。

第七步,當前棧的頂點是 B,看看它周圍有沒有還沒被訪問的頂點,有,是頂點 F,於是把 F 壓入棧,標記它為訪問過,並輸出到結果中。

第八步,當前頂點是 F,與 F 相連並且還未被訪問到的點是 C 和 D,按照字母順序來,下一個被訪問的 點是 C,將 C 壓入棧,標記為訪問過,輸出到結果中。

第九步,當前頂點為 C,與 C 相連並尚未被訪問到的頂點是 H,將 H 壓入棧,標記為訪問過,輸出到結 果中。

第十步,當前頂點是 H,由於和它相連的點都被訪問過了,將它彈出棧。

第十一步,當前頂點是 C,與 C 相連的點都被訪問過了,將 C 彈出棧

第十二步,當前頂點是 F,與 F 相連的並且尚未訪問的點是 D,將 D 壓入棧,輸出到結果中,並標記為 訪問過。

第十三步,當前頂點是 D,與它相連的點都被訪問過了,將它彈出棧。以此類推,頂點 F,B,A 的鄰居 都被訪問過了,將它們依次彈出棧就好了。最後,當棧裡已經沒有頂點需要處理了,我們的整個遍歷結 束。

時間複雜度

鄰接表:訪問所有頂點的時間為 O(V),而查找所有頂點的鄰居一共需要 O(E) 的時間,所以總的時間複雜度是O(V + E)。

鄰接矩陣:查找每個頂點的鄰居需要 O(V) 的時間,所以查找整個矩陣的時候需要 O(V^2) 時間

1.3.2 廣度優先搜索 BFS

就是一種「地毯式」層層推進的搜索策略,即先查找離起始頂點最近的,然後是次近的,依次往外搜索.

遍歷過程

還是以下圖為例,需要依賴隊列(Queue),先進先出(FIFO),一層一層地把與某個點相連的點放入隊列中,處理節點的時候正好按照它們進入隊列的順序進行

第一步,選擇一個起始頂點,頂點 A 開始,把 A 壓入隊列,標記它為訪問過(用紅色標記)

第二步,從隊列的頭取出頂點 A,列印輸出到結果中,同時將與它相連的尚未被訪問過的點按照字母大小順序壓入隊列,同時把它們都標記為訪問過,防止它們被重複地添加到隊列中。

第三步,從隊列的頭取出頂點 B,列印輸出它,同時將與它相連的尚未被訪問過的點(也就是 E 和 F)壓入隊列,同時把它們都標記為訪問過。

第四步,繼續從隊列的頭取出頂點 D,列印輸出它,此時我們發現,與 D 相連的頂點 A 和 F 都被標記訪問過了,所以就不要把它們壓入隊列裡

第五步,接下來,隊列的頭是頂點 G,列印輸出它,同樣的,G 周圍的點都被標記訪問過了,不做任何處理。

第六步,隊列的頭是 E,列印輸出它,它周圍的點也都被標記為訪問過了,不做任何處理。

第七步,接下來輪到頂點 F,列印輸出它,將 C 壓入隊列,並標記 C 為訪問過。

第八步,將 C 從隊列中移出,列印輸出它,與它相連的 H 還沒被訪問到,將 H 壓入隊列,將它標記為訪問過。

第九步,隊列裡只剩下 H 了,將它移出,列印輸出它,發現它的鄰居都被訪問過了,不做任何事情。

第十步,隊列為空,表示所有的點都被處理完畢了,程序結束

廣度優先一般是用於查找最短路徑問題。

時間複雜度

鄰接表:每個頂點都需要被訪問一次,時間複雜度是 O(V);相連的頂點(也就是每條邊)也都要被訪問一次,加起來就是 O(E)。因此整體時間複雜度就是 O(V+E)

鄰接矩陣:V 個頂點,每次都要檢查每個頂點與其他頂點是否有聯繫,因此時間複雜度是O(v^2)

廣度優先的搜索可以同時從起始點和終點開始進行,稱之為雙端 BFS。這種算法往往可以大大地提高搜索的效率。

相關焦點

  • java 數據結構與算法 之遞歸算法
    從事java開發多年,越來越發現要學的很多,但是有什麼辦法呢,誰叫我們已經走上了這條道路呢。我們也只有一點一點的積累,趁現在有時間,今天討論一下java 的數據結構與算法:遞歸算法,希望能達到溫故而知新的效果。一。
  • JAVA必須掌握的數據結構和算法
    常見的數據結構鍊表LinkedHashSet LinkedList 底層數據結構由鍊表和哈希表組成。數據的添加和刪除都較為方便,就是訪問比較耗費時間。數組ArrayList 訪問數據十分簡單,而添加和刪除數據比較耗工夫堆堆是一種圖的樹形結構,被用於實現「優先隊列",優先隊列是一種數據結構,可以自由添加數據,但取出數據時要從最小值開始按順序取出
  • Java數據結構和算法(三)圖的深度優先遍歷算法,附代碼(DFS)
    所謂圖的遍歷,即是對結點的訪問,一個圖會有很多的結點,那麼如何遍歷這些結點,需要特定的策略,一般有兩種訪問策略深度遍歷優先和廣度遍歷優先。
  • 詳解JAVA數據結構與算法:排序算法
    排序算法排序也稱排序算法(Sort Algorithm),排序是將一組數據,依指定的順序進行排列的過程。排序的分類:1) 內部排序:指將需要處理的所有數據都加載到內部存儲器中進行排序。低從圖中可見,我們應該盡可 能 避 免使用 指數階的算法1)常數階O(1)無論代碼執行了多少行,只要是沒有循環等複雜結構,那這個代碼的時間複雜度就都是O(1)
  • 我們到底該如何學習《數據結構與算法》
    第二:工作現在的大廠api框架基本上背後的邏輯就是基於算法實現的。其實算法的種類有很多,比如說機器學習、神經網絡算法,還有java中的排序算法,網際網路的商品推薦、股票預測其背後的邏輯都是算法。就算是熟悉的那些框架,背後的邏輯也是數據結構與算法。我們敲代碼解決問題的過程當中也是算法的集中體現。
  • Java實現冒泡排序算法
    1.引子1.1.為什麼要學習數據結構與算法?有人說,數據結構與算法,計算機網絡,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀!有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中間件,寫的代碼不也能「飛」起來嗎?於是問題來了:為什麼還要學習數據結構與算法呢?
  • 尚矽谷Java數據結構與算法、設計模式教程雙雙發布
    算法是程序的靈魂,優秀的程序在對海量數據處理時,依然保持高速計算,就需要高效的數據結構和算法支撐。網上數據結構和算法的教程不少,但存在兩個問題:(1) 授課方式單一,大多是照著代碼念一遍,數據結構和算法本身就比較難理解,對基礎好的學員來說,還好一點,對基礎不好的學生來說,基本上就是聽天書了。
  • 數據結構和算法學習指南
    首先,這裡講的都是普通的數據結構和算法,咱不是搞競賽的,野路子出生,只解決常規的問題,以面試為最終目標。另外,以下是我個人的經驗的總結,沒有哪本算法書會寫這些東西,所以請讀者試著理解我的角度,別糾結於細節問題,因為這篇文章就是對數據結構和算法建立一個框架性的認識。
  • 算法工程師思維導圖 | 數據結構與算法
    算法工程師思維導圖,求職/自我提升/查漏補缺神器。該手冊一共分為數據結構與算法、數學基礎、統計機器學習和深度學習四個部分。點擊這裡查看具體使用指南。該手冊有兩種獲取方式:下面是數據結構與算法部分~數據結構二維矩陣/直方圖最大距形面積矩陣最大面積:https://leetcode-cn.com/problems/maximal-rectangle/solution/zui-da-ju-xing-by-leetcode/
  • 算法和數據結構最全最易懂總結,再也不怕面試了~
    亦即總結常見的的數據結構,以及在Java中相應的實現方法,務求理論與實踐一步總結到位。好好梳理一下數據結構與算法,畢竟這些基礎知識是很重要的嘛首先給出Java集合框架的基本接口/類層次結構:java.util.Collection [I]    +--java.util.List [I]       +--java.util.ArrayList [C]           +--java.util.LinkedList
  • Java版數據結構與算法
    如果一個算法有缺陷,或不適合於某個問題,執行這個算法將不會解決這個問題。不同的算法可能用不同的時間、空間或效率來完成同樣的任務。一個算法的優劣可以用空間複雜度與時間複雜度來衡量。算法中的指令描述的是一個計算,當其運行時能從一個初始狀態和(可能為空的)初始輸入開始,經過一系列有限而清晰定義的狀態,最終產生輸出並停止於一個終態。一個狀態到另一個狀態的轉移不一定是確定的。
  • 快速入門數據結構和算法
    本文簡要分享算法基礎、常見的數據結構以及排序算法,給同學們帶來一堂數據結構和算法的基礎課。文末福利:阿里雲開發者訓練營來了。一  前言1  為什麼要學習算法和數據結構?2  業務開發要掌握到程度? 二  數據結構基礎1  什麼是數據結構?數據結構是數據的組織、管理和存儲格式,其使用目的是為了高效的訪問和修改數據。數據結構是算法的基石。如果把算法比喻成美麗靈動的舞者,那麼數據結構就是舞者腳下廣闊而堅實的舞臺。2  物理結構和邏輯結構的區別?
  • 從數據結構到算法:圖網絡方法初探
    因此也催化出一系列在圖上進行數據挖掘的任務,如為用戶推薦感興趣的好友、判斷蛋白質結構、預測交通流量、檢測異常帳戶等等。但是真實圖的數據量龐大,動輒上億節點、而且內部拓撲結構複雜,很難將傳統的圖分析方法如最短路徑、DFS、BFS、PageRank 等算法應用到這些任務上。鑑於機器學習在圖像、文本領域的廣泛應用,一部分研究者嘗試將機器學習方法和圖數據結合起來,逐漸成為機器學習領域的一股熱潮。
  • 概述Java中的數據結構是什麼及其內部實現原理
    本文主講介紹幾種常用的數據結構,數據結構是一種容器的一個分支,容器時用來裝東西的,那麼數據結構就是專門用來存儲數據的容器。首先我們從數組來給大家漸漸引入數據結構,容器的概念。那麼數組是什麼呢,數組是一個對象,準確的說我們將對象進行分類,具有特定功能的對象就被成為數據結構或者容器,集合等,數組就是其中之一。
  • Java數據結構與算法——字符串匹配相關算法
    Java裡用的是indexOf函數,其底層就是字符串匹配算法.其中字符串匹配算法主要包括:1. BF(暴力匹配)算法1.1概念和原理Brute Force叫作暴力匹配算法,也叫樸素匹配算法。其主要實現原理就是 在主串中,檢查起始位置分別是 0、1、2…n-m 且長度為 m 的 n-m+1 個子串,看有沒有跟模式串匹配的。
  • 數據結構與算法大總結
    數據結構知識綜述一、前言經過一個學期對數據結構與算法的學習,對這門課程有了更深入的了解,了解到了數據結構的相關知識,明白了,數據結構的重要性及應用,回顧一個學期,通過這個小論文,來總結一學期的學習成果,數據結構這門課程,具有一定的深度和廣度,有其複雜性,我們一起來分析一下
  • 資料| 數據結構與算法 JavaScript 描述
    內容簡介 · · · · · ·通過本書的學習,讀者將能自如地選擇最合適的數據結構與算法,並在JavaScript開發中懂得權衡使用。此外,本書也概述了與數據結構與算法相關的JavaScript特性。數組和列表:最常用的數據結構。棧和隊列:與列表類似但更複雜的數據結構。鍊表:如何通過它們克服數組的不足。字典:將數據以鍵-值對的形式存儲。散列:適用於快速查找和檢索。集合:適用於存儲只出現一次的元素。二叉樹:以層級的形式存儲數據。圖和圖算法:網絡建模的理想選擇。
  • 前端學習數據結構與算法系列(一):初識數據結構與算法
    前言作為一個對算法沒有任何認知,非科班出身的前端程式設計師,如果想提高自己的能力,不再只寫業務代碼當一個應用工程師,算法是必須掌握的一門本領。算法也是一種思想,當你去讀一些優秀框架的源碼,如果對算法和數據結構一無所知,讀起來很困難,你無法理解人家為什麼要那樣寫,那樣寫的好處是什麼,接下來就跟大家分享下作為一個前端程式設計師,如何學習數據結構與算法。
  • 面試經驗分享之數據結構、算法題
    前言面試 IT 企業的研發崗位,數據結構和算法顯然是必考的項目。俺只學過普通的數據結構課程,沒讀過 STL,也沒有過 ACM 的訓練和比賽經歷,在一開始面對這樣類型題目的時候,心裡還是十分忐忑的。大大小小几十場面試下來,自己在這方面總算有了一定的心得積累,在此拋磚引玉, 以饗讀者。
  • 數據結構與算法?看這篇就夠了!!!
    真正讓程式設計師有區分度,企業招聘萬年不變的重點 —— 算法與數據結構但無論半路出家還是科班出身,除學生時代搞算法競賽的同學外真正用心學習過算法與數據結構太少太少對於後期想要學習算法與數據結構卻不得不面對以下問題:於是我們推出了「數據結構與算法365天特訓營」實時視頻直播