一、基本術語
圖:由有窮、非空點集和邊集合組成,簡寫成G(V,E);
Vertex:圖中的頂點;
無向圖:圖中每條邊都沒有方向;
有向圖:圖中每條邊都有方向;
無向邊:邊是沒有方向的,寫爲(a,b)
有向邊:邊是有方向的,寫爲<a,b>
有向邊也成爲弧;開始頂點稱爲弧尾,結束頂點稱爲弧頭;
簡單圖:不存在指向自己的邊、不存在兩條重複的邊的圖;
無向完全圖:每個頂點之間都有一條邊的無向圖;
有向完全圖:每個頂點之間都有兩條互爲相反的邊的無向圖;
稀疏圖:邊相對於頂點來說很少的圖;
稠密圖:邊很多的圖;
權重:圖中的邊可能會帶有一個權重,爲了區分邊的長短;
網:帶有權重的圖;
度:與特定頂點相連接的邊數;
出度、入度:對於有向圖的概念,出度表示此頂點爲起點的邊的數目,入度表示此頂點爲終點的邊的數目;
環:第一個頂點和最後一個頂點相同的路徑;
簡單環:除去第一個頂點和最後一個頂點後沒有重複頂點的環;
連通圖:任意兩個頂點都相互連通的圖;
極大連通子圖:包含竟可能多的頂點(必須是連通的),即找不到另外一個頂點,使得此頂點能夠連接到此極大連通子圖的任意一個頂點;
連通分量:極大連通子圖的數量;
強連通圖:此爲有向圖的概念,表示任意兩個頂點a,b,使得a能夠連接到b,b也能連接到a 的圖;
生成樹:n個頂點,n-1條邊,並且保證n個頂點相互連通(不存在環);
最小生成樹:此生成樹的邊的權重之和是所有生成樹中最小的;
AOV網:結點表示活動的網;
AOE網:邊表示活動的持續時間的網;
二、圖的存儲結構
1.鄰接矩陣
維持一個二維數組,arr[i][j]表示i到j的邊,如果兩頂點之間存在邊,則爲1,否則爲0;
維持一個一維數組,存儲頂點信息,比如頂點的名字;
下圖爲一般的有向圖:
注意:如果我們要看vi節點鄰接的點,則只需要遍歷arr[i]即可;
下圖爲帶有權重的圖的鄰接矩陣表示法:
缺點:鄰接矩陣表示法對於稀疏圖來說不合理,因爲太浪費空間;
2.鄰接表
如果圖示一般的圖,則如下圖:
如果是網,即邊帶有權值,則如下圖:
3.十字鏈表
只針對有向圖;,適用於計算出度和入度;
頂點結點:
邊結點:
好處:創建的時間複雜度和鄰接鏈表相同,但是能夠同時計算入度和出度;
4.鄰接多重表
針對無向圖; 如果我們只是單純對節點進行 操作,則鄰接表是一個很好的選擇,但是如果我們要在鄰接表中刪除一條邊,則需要刪除四個頂點(因爲無向圖);
在鄰接多重表中,只需要刪除一個節點,即可完成邊的刪除,因此比較方便;
因此鄰接多重表適用於對邊進行刪除的操作;
頂點節點和鄰接表沒區別,邊表節點如下圖:
比如:
5.邊集數組
適合依次對邊進行操作;
存儲邊的信息,如下圖:
是有向圖的一種鏈式存儲結構,可看成鄰接表和逆鄰接表結合而成。
三、圖的遍歷
DFS
思想:往深裏遍歷,如果不能深入,則回朔;
比如:
- /**
- * O(v+e)
- */
- @Test
- public void DFS() {
- for (int i = 0; i < g.nodes.length; i++) {
- if (!visited[i]) {
- DFS_Traverse(g, i);
- }
- }
- }
- private void DFS_Traverse(Graph2 g, int i) {
- visited[i] = true;
- System.out.println(i);
- EdgeNode node = g.nodes[i].next;
- while (node != null) {
- if (!visited[node.idx]) {
- DFS_Traverse(g, node.idx);
- }
- node = node.next;
- }
- }
BFS
思想:對所有鄰接節點遍歷;
- <span style="white-space:pre"> </span>/**
- * O(v+e)
- */
- @Test
- public void BFS() {
- ArrayList<Integer> list = new ArrayList<Integer>();
- for (int i = 0; i < g.nodes.length; i++) {
- if (!visited[i]) {
- visited[i] = true;
- list.add(i);
- System.out.println(i);
- while (!list.isEmpty()) {
- int k = list.remove(0);
- EdgeNode current = g.nodes[k].next;
- while (current != null) {
- if (!visited[current.idx]) {
- visited[current.idx] = true;
- System.out.println(current.idx);
- list.add(current.idx);
- }
- current = current.next;
- }
- }
- }
- }
- }
四、最小生成樹
prim
- <span style="white-space:pre"> </span>/**
- * 時間複雜度爲O(n^2)
- * 適用於稠密圖
- */
- @Test
- public void prim(){
- int cost[] = new int[9];
- int pre[] = new int[9];
- for(int i=0;i<g1.vertex.length;i++){
- cost[i] = g1.adjMatrix[0][i];
- }
- cost[0] = 0;
- for(int i=1;i<g1.vertex.length;i++){
- int min = 65536;
- int k = 0;
- for(int j=1;j<g1.vertex.length;j++){
- if(cost[j]!=0&&cost[j]<min){
- min = cost[j];
- k = j;
- }
- }
- cost[k] = 0;
- System.out.println(pre[k]+","+k);
- for(int j=1;j<g1.vertex.length;j++){
- if(cost[j]!=0&&g1.adjMatrix[k][j]<cost[j]){
- pre[j] = k;
- cost[j] = g1.adjMatrix[k][j];
- }
- }
- }
- }
krustral
- <span style="white-space:pre"> </span>/**
- * 時間複雜度:O(eloge)
- * 適用於稀疏圖
- */
- @Test
- public void krustral(){
- Edge[] edges = initEdges();
- int parent[] = new int[9];
- for(int i=0;i<edges.length;i++){
- Edge edge = edges[i];
- int m = find(parent,edge.begin);
- int n = find(parent,edge.end);
- if(m!=n){
- parent[m] = n;
- System.out.println(m+","+n);
- }
- }
- }
- private static int find(int[] parent, int f) {
- while (parent[f] > 0) {
- f = parent[f];
- }
- return f;
- }
五、最短路徑
dijkstra算法
- <span style="white-space:pre"> </span>//O(n^2)
- @Test
- public void Dijkstra(){
- int distance[] = new int[9];
- int pre[] = new int[9];
- boolean finished[] = new boolean[9];
- finished[0] = true;
- for(int i=0;i<9;i++){
- distance[i] = g1.adjMatrix[0][i];
- }
- int k = 0;
- for(int i=1;i<9;i++){
- int min = 65536;
- for(int j=0;j<9;j++){
- if(!finished[j]&&distance[j]<min){
- min = distance[j];
- k = j;
- }
- }
- finished[k] = true;
- System.out.println(pre[k]+","+k);
- for(int j=1;j<9;j++){
- if(!finished[j]&&(min+g1.adjMatrix[k][j])<distance[j]){
- distance[j] = min+g1.adjMatrix[k][j];
- pre[j] = k;
- }
- }
- }
- }
Floyd
- <span style="white-space:pre"> </span>/**
- * O(n^3)
- * 求出任意頂點之間的距離
- */
- @Test
- public void floyd(Graph1 g) {
- int i, j, k;
- int length = g.vertex.length;
- int dist[][] = new int[length][length];
- int pre[][] = new int[length][length];
- for (i = 0; i < g.vertex.length; i++) {
- for (j = 0; j < g.vertex.length; j++) {
- pre[i][j] = j;
- dist[i][j] = g.adjMatrix[i][j];
- }
- }
- for (i = 0; i < length; i++) {
- for (j = 0; j < g.vertex.length; j++) {
- for (k = 0; k < g.vertex.length; k++) {
- if (dist[i][j] > dist[i][k] + dist[k][j]) {
- dist[i][j] = dist[i][k] + dist[k][j];
- pre[i][j] = pre[i][k];
- }
- }
- }
- }
- System.out.println();
- }
六、拓撲排序
使用數據結構:
(1)棧:用來存放入度爲0的節點;
(2)變種鄰接列表:作爲圖的存儲結構;此鄰接列表的頂點節點還需要存放入度屬性;
- /**
- * O(n+e)
- */
- private static String topologicalSort(Graph2 g2) {
- Stack<Integer> s = new Stack<Integer>();
- int count = 0;
- for(int i=0;i<g2.nodes.length;i++){
- if(g2.nodes[i].indegree==0){
- s.push(i);
- }
- }
- while(!s.isEmpty()){
- int value = s.pop();
- System.out.println(value+"、");
- count++;
- EdgeNode node = g2.nodes[value].next;
- while(node!=null){
- g2.nodes[node.idx].indegree--;
- if(g2.nodes[node.idx].indegree==0){
- s.push(node.idx);
- }
- node = node.next;
- }
- }
- if(count<g2.nodes.length){
- return "error";
- }
- return "ok";
- }
七、關鍵路徑
- <span style="white-space:pre"> </span>//O(n+e)
- <span style="white-space:pre"> </span>@Test
- public void CriticalPath(){
- Stack<Integer> stack = topological_etv();
- int length = stack.size();
- if(stack==null){
- return ;
- }
- else{
- int[]ltv = new int[length];
- for(int i=0;i<stack.size();i++){
- ltv[i] = etv[stack.size()-1];
- }
- //從拓撲排序的最後開始計算ltv
- while(!stack.isEmpty()){
- int top = stack.pop();
- EdgeNode current = g.nodes[top].next;
- while(current!=null){
- int idx = current.idx;
- //最晚發生時間要取所有活動中最早的
- if((ltv[idx]-current.weight)<ltv[top]){
- ltv[top] = ltv[idx]-current.weight;
- }
- }
- }
- int ete = 0;
- int lte = 0;
- for(int j=0;j<length;j++){
- EdgeNode current = g.nodes[j].next;
- while(current!=null){
- int idx = current.idx;
- ete = etv[j];
- lte = ltv[idx]-current.weight;
- if(ete==lte){
- //是關鍵路徑
- }
- }
- }
- }
- }
- private Stack<Integer> topological_etv(){
- Stack<Integer> stack2 = new Stack<Integer>();
- Stack<Integer>stack1 = new Stack<Integer>();
- for(int i=0;i<g.nodes.length;i++){
- if(g.nodes[i].indegree==0){
- stack1.add(i);
- }
- }
- etv[] = new int[g.nodes.length];
- int count = 0;
- while(!stack1.isEmpty()){
- int top = stack1.pop();
- count++;
- stack2.push(top);
- EdgeNode current = g.nodes[top].next;
- while(current!=null){
- int idx = current.idx;
- if((--g.nodes[idx].indegree)==0){
- stack1.push(idx);
- }
- if((etv[top]+current.weight)>etv[idx]){
- etv[idx] = etv[top]+current.weight;
- }
- current = current.next;
- }
- }
- if(count<g.nodes.length){
- return null;
- }
- return stack2;
- }
三、圖的遍歷
DFS
思想:往深裏遍歷,如果不能深入,則回朔;
比如:
- /**
- * O(v+e)
- */
- @Test
- public void DFS() {
- for (int i = 0; i < g.nodes.length; i++) {
- if (!visited[i]) {
- DFS_Traverse(g, i);
- }
- }
- }
- private void DFS_Traverse(Graph2 g, int i) {
- visited[i] = true;
- System.out.println(i);
- EdgeNode node = g.nodes[i].next;
- while (node != null) {
- if (!visited[node.idx]) {
- DFS_Traverse(g, node.idx);
- }
- node = node.next;
- }
- }
BFS
思想:對所有鄰接節點遍歷;
- <span style="white-space:pre"> </span>/**
- * O(v+e)
- */
- @Test
- public void BFS() {
- ArrayList<Integer> list = new ArrayList<Integer>();
- for (int i = 0; i < g.nodes.length; i++) {
- if (!visited[i]) {
- visited[i] = true;
- list.add(i);
- System.out.println(i);
- while (!list.isEmpty()) {
- int k = list.remove(0);
- EdgeNode current = g.nodes[k].next;
- while (current != null) {
- if (!visited[current.idx]) {
- visited[current.idx] = true;
- System.out.println(current.idx);
- list.add(current.idx);
- }
- current = current.next;
- }
- }
- }
- }
- }
四、最小生成樹
prim
- <span style="white-space:pre"> </span>/**
- * 時間複雜度爲O(n^2)
- * 適用於稠密圖
- */
- @Test
- public void prim(){
- int cost[] = new int[9];
- int pre[] = new int[9];
- for(int i=0;i<g1.vertex.length;i++){
- cost[i] = g1.adjMatrix[0][i];
- }
- cost[0] = 0;
- for(int i=1;i<g1.vertex.length;i++){
- int min = 65536;
- int k = 0;
- for(int j=1;j<g1.vertex.length;j++){
- if(cost[j]!=0&&cost[j]<min){
- min = cost[j];
- k = j;
- }
- }
- cost[k] = 0;
- System.out.println(pre[k]+","+k);
- for(int j=1;j<g1.vertex.length;j++){
- if(cost[j]!=0&&g1.adjMatrix[k][j]<cost[j]){
- pre[j] = k;
- cost[j] = g1.adjMatrix[k][j];
- }
- }
- }
- }
krustral
- <span style="white-space:pre"> </span>/**
- * 時間複雜度:O(eloge)
- * 適用於稀疏圖
- */
- @Test
- public void krustral(){
- Edge[] edges = initEdges();
- int parent[] = new int[9];
- for(int i=0;i<edges.length;i++){
- Edge edge = edges[i];
- int m = find(parent,edge.begin);
- int n = find(parent,edge.end);
- if(m!=n){
- parent[m] = n;
- System.out.println(m+","+n);
- }
- }
- }
- private static int find(int[] parent, int f) {
- while (parent[f] > 0) {
- f = parent[f];
- }
- return f;
- }
五、最短路徑
dijkstra算法
- <span style="white-space:pre"> </span>//O(n^2)
- @Test
- public void Dijkstra(){
- int distance[] = new int[9];
- int pre[] = new int[9];
- boolean finished[] = new boolean[9];
- finished[0] = true;
- for(int i=0;i<9;i++){
- distance[i] = g1.adjMatrix[0][i];
- }
- int k = 0;
- for(int i=1;i<9;i++){
- int min = 65536;
- for(int j=0;j<9;j++){
- if(!finished[j]&&distance[j]<min){
- min = distance[j];
- k = j;
- }
- }
- finished[k] = true;
- System.out.println(pre[k]+","+k);
- for(int j=1;j<9;j++){
- if(!finished[j]&&(min+g1.adjMatrix[k][j])<distance[j]){
- distance[j] = min+g1.adjMatrix[k][j];
- pre[j] = k;
- }
- }
- }
- }
Floyd
- <span style="white-space:pre"> </span>/**
- * O(n^3)
- * 求出任意頂點之間的距離
- */
- @Test
- public void floyd(Graph1 g) {
- int i, j, k;
- int length = g.vertex.length;
- int dist[][] = new int[length][length];
- int pre[][] = new int[length][length];
- for (i = 0; i < g.vertex.length; i++) {
- for (j = 0; j < g.vertex.length; j++) {
- pre[i][j] = j;
- dist[i][j] = g.adjMatrix[i][j];
- }
- }
- for (i = 0; i < length; i++) {
- for (j = 0; j < g.vertex.length; j++) {
- for (k = 0; k < g.vertex.length; k++) {
- if (dist[i][j] > dist[i][k] + dist[k][j]) {
- dist[i][j] = dist[i][k] + dist[k][j];
- pre[i][j] = pre[i][k];
- }
- }
- }
- }
- System.out.println();
- }
六、拓撲排序
使用數據結構:
(1)棧:用來存放入度爲0的節點;
(2)變種鄰接列表:作爲圖的存儲結構;此鄰接列表的頂點節點還需要存放入度屬性;
- /**
- * O(n+e)
- */
- private static String topologicalSort(Graph2 g2) {
- Stack<Integer> s = new Stack<Integer>();
- int count = 0;
- for(int i=0;i<g2.nodes.length;i++){
- if(g2.nodes[i].indegree==0){
- s.push(i);
- }
- }
- while(!s.isEmpty()){
- int value = s.pop();
- System.out.println(value+"、");
- count++;
- EdgeNode node = g2.nodes[value].next;
- while(node!=null){
- g2.nodes[node.idx].indegree--;
- if(g2.nodes[node.idx].indegree==0){
- s.push(node.idx);
- }
- node = node.next;
- }
- }
- if(count<g2.nodes.length){
- return "error";
- }
- return "ok";
- }
七、關鍵路徑
- <span style="white-space:pre"> </span>//O(n+e)
- <span style="white-space:pre"> </span>@Test
- public void CriticalPath(){
- Stack<Integer> stack = topological_etv();
- int length = stack.size();
- if(stack==null){
- return ;
- }
- else{
- int[]ltv = new int[length];
- for(int i=0;i<stack.size();i++){
- ltv[i] = etv[stack.size()-1];
- }
- //從拓撲排序的最後開始計算ltv
- while(!stack.isEmpty()){
- int top = stack.pop();
- EdgeNode current = g.nodes[top].next;
- while(current!=null){
- int idx = current.idx;
- //最晚發生時間要取所有活動中最早的
- if((ltv[idx]-current.weight)<ltv[top]){
- ltv[top] = ltv[idx]-current.weight;
- }
- }
- }
- int ete = 0;
- int lte = 0;
- for(int j=0;j<length;j++){
- EdgeNode current = g.nodes[j].next;
- while(current!=null){
- int idx = current.idx;
- ete = etv[j];
- lte = ltv[idx]-current.weight;
- if(ete==lte){
- //是關鍵路徑
- }
- }
- }
- }
- }
- private Stack<Integer> topological_etv(){
- Stack<Integer> stack2 = new Stack<Integer>();
- Stack<Integer>stack1 = new Stack<Integer>();
- for(int i=0;i<g.nodes.length;i++){
- if(g.nodes[i].indegree==0){
- stack1.add(i);
- }
- }
- etv[] = new int[g.nodes.length];
- int count = 0;
- while(!stack1.isEmpty()){
- int top = stack1.pop();
- count++;
- stack2.push(top);
- EdgeNode current = g.nodes[top].next;
- while(current!=null){
- int idx = current.idx;
- if((--g.nodes[idx].indegree)==0){
- stack1.push(idx);
- }
- if((etv[top]+current.weight)>etv[idx]){
- etv[idx] = etv[top]+current.weight;
- }
- current = current.next;
- }
- }
- if(count<g.nodes.length){
- return null;
- }
- return stack2;
- }