1. 程式人生 > >【資料結構】圖(深度優先遍歷、廣度優先遍歷)的JAVA程式碼實現

【資料結構】圖(深度優先遍歷、廣度優先遍歷)的JAVA程式碼實現

圖的遍歷是指從圖中的任一頂點出發,對圖中的所有頂點訪問一次並且只訪問一次。圖的遍歷是圖的一種基本操作,圖中的許多其他操作也都是建立在遍歷的基礎之上。在圖中,沒有特殊的頂點被指定為起始頂點,圖的遍歷可以從任何頂點開始。圖的遍歷主要有深度優先搜尋和廣度優先搜尋兩種方式。

深度優先搜尋演算法

演算法的思想

從圖中的某一個頂點x出發,訪問x,然後遍歷任何一個與x相鄰的未被訪問的頂點y,再遍歷任何一個與y相鄰的未被訪問的頂點z……依次類推,直到到達一個所有鄰接點都被訪問的頂點為止;然後,依次回退到尚有鄰接點未被訪問過的頂點,重複上述過程,直到圖中的全部頂點都被訪問過為止。

演算法實現的思想

深度優先遍歷背後基於堆疊,有兩種方式:第一種是在程式中顯示構造堆疊,利用壓棧出棧操作實現;第二種是利用遞迴函式呼叫,基於遞迴程式棧實現。

本文介紹第一種方式:

  1. 訪問起始頂點,並將其壓入棧中;
  2. 從棧中彈出最上面的頂點,將與其相鄰的未被訪問的頂點壓入棧中;
  3. 重複第二步,直至棧為空棧。

未被訪問的頂點怎麼識別呢?利用visited陣列來進行標記。

演算法的實現

基於鄰接矩陣的演算法實現:

	public String depthFirstSearch(int v) {
		if (v < 0 || v >= numOfVexs)
			throw new ArrayIndexOutOfBoundsException();
		visited = new boolean[numOfVexs];
		StringBuilder sb = new StringBuilder();
		Stack<Integer> stack = new Stack<Integer>();
		stack.push(v);
		visited[v] = true;
		while (!stack.isEmpty()) {
			v = stack.pop();
			sb.append(vexs[v] + ",");
			for (int i = numOfVexs - 1; i >= 0; i--) {
				if ((edges[v][i] != 0 && edges[v][i] != Integer.MAX_VALUE)
						&& !visited[i]) {
					stack.push(i);
					visited[i] = true;
				}
			}
		}
		return sb.length() > 0 ? sb.substring(0, sb.length() - 1) : null;
	}

基於鄰接表的演算法實現:

	public String depthFirstSearch(int v) {
		if (v < 0 || v >= numOfVexs)
			throw new ArrayIndexOutOfBoundsException();
		visited = new boolean[numOfVexs];
		StringBuilder sb = new StringBuilder();
		Stack<Integer> stack = new Stack<Integer>();
		stack.push(v);
		visited[v] = true;
		ENode current;
		while (!stack.isEmpty()) {
			v = stack.pop();
			sb.append(vexs[v].data + ",");
			current = vexs[v].firstadj;
			while (current != null) {
				if (!visited[current.adjvex]) {
					stack.push(current.adjvex);
					visited[current.adjvex] = true;
				}
				current = current.nextadj;
			}
		}
		return sb.length() > 0 ? sb.substring(0, sb.length() - 1) : null;
	}

廣度優先搜尋演算法

演算法的思想

從圖中的某一個頂點x出發,訪問x,然後訪問與x所相鄰的所有未被訪問的頂點x1、x2……xn,接著再依次訪問與x1、x2……xn相鄰的未被訪問的所有頂點。依次類推,直至圖中的每個頂點都被訪問。

演算法實現的思想

廣度優先遍歷背後基於佇列,下面介紹一下具體實現的方法:

  1. 訪問起始頂點,並將插入佇列;
  2. 從佇列中刪除隊頭頂點,將與其相鄰的未被訪問的頂點插入佇列中;
  3. 重複第二步,直至佇列為空。

未被訪問的頂點怎麼識別呢?利用visited陣列來進行標記。

演算法的實現

基於鄰接矩陣的演算法實現:

	public String breadFirstSearch(int v) {
		if (v < 0 || v >= numOfVexs)
			throw new ArrayIndexOutOfBoundsException();
		visited = new boolean[numOfVexs];
		StringBuilder sb = new StringBuilder();
		Queue<Integer> queue = new LinkedList<Integer>();
		queue.offer(v);
		visited[v] = true;
		while (!queue.isEmpty()) {
			v = queue.poll();
			sb.append(vexs[v] + ",");
			for (int i = 0; i < numOfVexs; i++) {
				if ((edges[v][i] != 0 && edges[v][i] != Integer.MAX_VALUE)
						&& !visited[i]) {
					queue.offer(i);
					visited[i] = true;
				}
			}
		}
		return sb.length() > 0 ? sb.substring(0, sb.length() - 1) : null;
	}

基於鄰接表的演算法實現:

	public String breadFirstSearch(int v) {
		if (v < 0 || v >= numOfVexs)
			throw new ArrayIndexOutOfBoundsException();
		visited = new boolean[numOfVexs];
		StringBuilder sb = new StringBuilder();
		Queue<Integer> queue = new LinkedList<Integer>();
		queue.offer(v);
		visited[v] = true;
		ENode current;
		while (!queue.isEmpty()) {
			v = queue.poll();
			sb.append(vexs[v].data + ",");
			current = vexs[v].firstadj;
			while (current != null) {
				if (!visited[current.adjvex]) {
					queue.offer(current.adjvex);
					visited[current.adjvex] = true;
				}
				current = current.nextadj;
			}
		}
		return sb.length() > 0 ? sb.substring(0, sb.length() - 1) : null;
	}
這邊的其他的主要部分(如成員變數的定義等),參考【資料結構】圖(鄰接矩陣、鄰接表)的JAVA程式碼實現