一、基本术语


图:由有穷、非空点集和边集合组成,简写成G(V,E);

Vertex:图中的顶点;


无向图:图中每条边都没有方向;

有向图:图中每条边都有方向;


无向边:边是没有方向的,写为(a,b)

有向边:边是有方向的,写为<a,b>

有向边也成为弧;开始顶点称为弧尾,结束顶点称为弧头;


简单图:不存在指向自己的边、不存在两条重复的边的图;


无向完全图:每个顶点之间都有一条边的无向图;0<=边<=n(n-1)/2

有向完全图:每个顶点之间都有两条互为相反的边的无向图;0<=边<=n(n-1)


稀疏图:边相对于顶点来说很少的图;边< n*logn

稠密图:边很多的图;


权重:图中的边可能会带有一个权重,为了区分边的长短;

网:带有权重的图;


度:与特定顶点相连接的边数;

出度、入度:对于有向图的概念,出度表示此顶点为起点的边的数目,入度表示此顶点为终点的边的数目;

路径的长度:是路径的边或者弧的数目


环、回路:第一个顶点和最后一个顶点相同的路径;

简单环:除去第一个顶点和最后一个顶点后没有重复顶点的环;


连通图:任意两个顶点都相互连通的图;

极大连通子图:包含竟可能多的顶点(必须是连通的),即找不到另外一个顶点,使得此顶点能够连接到此极大连通子图的任意一个顶点;

连通分量:极大连通子图的数量;


强连通图:此为有向图的概念,表示任意两个顶点a,b,使得a能够连接到b,b也能连接到a 的图;

极大强连通子图:包含竟可能多的顶点(必须是连通的),即找不到另外一个顶点,使得此顶点能够连接到此极大连通子图的任意一个顶点;

强连通分量:有向图中的极大连通子图 称之为;


一个连通图的生成树是 极小连通子图 类似于 树

连通图的生成树:n个顶点,n-1条边,并且保证n个顶点相互连通(不存在环);

                            如果一个图有 n 个顶点 n-1 个边,则一定是非连通图。 如果多于 n-1 个边则一定是个环!


如果一个有向图 有一个顶点入度为0 其余入度为1 则为有向树   

由图我们去掉若干边后生成森林


最小生成树:此生成树的边的权重之和是所有生成树中最小的;


AOV网:结点表示活动的网;

AOE网:边表示活动的持续时间的网;


二、图的存储结构

         由于图顶点之间是多对多关系  都可能存在联系 ,因此 简单用顺序存储不能实现, 而用链式存储 因为 对应关系不知道 因此 会浪费很多next指针。        但是前辈们还是提供了 五种不同的图存储结构


1.邻接矩阵

       图由顶点跟边或者弧构成  顶点不分大小主次  用一维数组表示顶点, 边或弧 用二维数组存储,二维数组就是邻接矩阵

       G(V,E) 如果有N个顶点 则临接矩阵为N*N 方阵


维持一个二维数组,arr[i][j]表示i到j的边,如果两顶点之间存在边,则为1,否则为0;  无向图为对称矩阵

维持一个一维数组,存储顶点信息,比如顶点的名字;

 下图为带有权重的图的邻接矩阵表示法:

 图Graph_java

缺点:邻接矩阵表示法对于稀疏图来说不合理,因为太浪费空间; 


2.邻接表


如果图示一般的图,则如下图:

图Graph_i++_02

 如果是网,即边带有权值,则如下图:

图Graph_i++_03


3.十字链表

只针对有向图;,适用于计算出度和入度;

顶点结点:


边结点:

 图Graph_java_04

好处:创建的时间复杂度和邻接链表相同,但是能够同时计算入度和出度;

4.邻接多重表

针对无向图; 如果我们只是单纯对节点进行操作,则邻接表是一个很好的选择,但是如果我们要在邻接表中删除一条边,则需要删除四个顶点(因为无向图);

在邻接多重表中,只需要删除一个节点,即可完成边的删除,因此比较方便;

因此邻接多重表适用于对边进行删除的操作;

顶点节点和邻接表没区别,边表节点如下图:


比如:

 图Graph_i++_05


5.边集数组


适合依次对边进行操作;

存储边的信息,如下图:

图Graph_i++_06


三、图的遍历



DFS


思想:往深里遍历,如果不能深入,则回朔;

比如:

图Graph_java_07



[java]​ 

​view plain​​ 

​copy​


  1. /**
  2. * O(v+e)
  3. */
  4. @Test
  5. publicvoid​DFS() {
  6. for​(​int​i = 0; i < g.nodes.length; i++) {
  7. if​(!visited[i]) {
  8. DFS_Traverse(g, i);
  9. }
  10. }
  11. }
  12. privatevoid​DFS_Traverse(Graph2 g,​int​i) {
  13. visited[i] =​true​;
  14. System.out.println(i);
  15. EdgeNode node = g.nodes[i].next;
  16. while​(node !=​null​) {
  17. if​(!visited[node.idx]) {
  18. DFS_Traverse(g, node.idx);
  19. }
  20. node = node.next;
  21. }
  22. }





BFS


思想:对所有邻接节点遍历;



[java]​ 

​view plain​​ 

​copy​


  1. <span style="white-space:pre">  </span>/**
  2. * O(v+e)
  3. */
  4. @Test
  5. publicvoid​BFS() {
  6. ArrayList<Integer> list =​new​ArrayList<Integer>();
  7. for​(​int​i = 0; i < g.nodes.length; i++) {
  8. if​(!visited[i]) {
  9. visited[i] =​true​;
  10. list.add(i);
  11. System.out.println(i);
  12. while​(!list.isEmpty()) {
  13. int​k = list.remove(0);
  14. EdgeNode current = g.nodes[k].next;
  15. while​(current !=​null​) {
  16. if​(!visited[current.idx]) {
  17. visited[current.idx] =​true​;
  18. System.out.println(current.idx);
  19. list.add(current.idx);
  20. }
  21. current = current.next;
  22. }
  23. }
  24. }
  25. }
  26. }







四、最小生成树


prim

  邻接矩阵存储;



[java]​ 

​view plain​​ 

​copy​


  1. <span style="white-space:pre">  </span>/**
  2. * 时间复杂度为O(n^2)
  3. * 适用于稠密图
  4. */
  5. @Test
  6. publicvoid​prim(){
  7. int​cost[] =​newint​[9];
  8. int​pre[] =​newint​[9];
  9. for​(​int​i=0;i<g1.vertex.length;i++){
  10. cost[i] = g1.adjMatrix[0][i];
  11. }
  12. cost[0] = 0;
  13. for​(​int​i=1;i<g1.vertex.length;i++){
  14. int​min = 65536;
  15. int​k = 0;
  16. for​(​int​j=1;j<g1.vertex.length;j++){
  17. if​(cost[j]!=0&&cost[j]<min){
  18. min = cost[j];
  19. k = j;
  20. }
  21. }
  22. cost[k] = 0;
  23. System.out.println(pre[k]+","+k);
  24. for​(​int​j=1;j<g1.vertex.length;j++){
  25. if​(cost[j]!=0&&g1.adjMatrix[k][j]<cost[j]){
  26. pre[j] = k;
  27. cost[j] = g1.adjMatrix[k][j];
  28. }
  29. }
  30. }
  31. }





krustral

边集数组存储;




[java]​ 

​view plain​​ 

​copy​


  1. <span style="white-space:pre">  </span>/**
  2. * 时间复杂度:O(eloge)
  3. * 适用于稀疏图
  4. */
  5. @Test
  6. publicvoid​krustral(){
  7. Edge[] edges = initEdges();
  8. int​parent[] =​newint​[9];
  9. for​(​int​i=0;i<edges.length;i++){
  10. Edge edge = edges[i];
  11. int​m = find(parent,edge.begin);
  12. int​n = find(parent,edge.end);
  13. if​(m!=n){
  14. parent[m] = n;
  15. System.out.println(m+","+n);
  16. }
  17. }
  18. }
  19. privatestaticint​find(​int​[] parent,​int​f) {
  20. while​(parent[f] > 0) {
  21. f = parent[f];
  22. }
  23. return​f;
  24. }





五、最短路径



dijkstra算法


  邻接矩阵存储; ​[java]​ 

​view plain​​ 

​copy​


  1. <span style="white-space:pre">  </span>//O(n^2)
  2. @Test
  3. publicvoid​Dijkstra(){
  4. int​distance[] =​newint​[9];
  5. int​pre[] =​newint​[9];
  6. boolean​finished[] =​newboolean​[9];
  7. finished[0] =​true​;
  8. for​(​int​i=0;i<9;i++){
  9. distance[i] = g1.adjMatrix[0][i];
  10. }
  11. int​k = 0;
  12. for​(​int​i=1;i<9;i++){
  13. int​min = 65536;
  14. for​(​int​j=0;j<9;j++){
  15. if​(!finished[j]&&distance[j]<min){
  16. min = distance[j];
  17. k = j;
  18. }
  19. }
  20. finished[k] =​true​;
  21. System.out.println(pre[k]+","+k);
  22. for​(​int​j=1;j<9;j++){
  23. if​(!finished[j]&&(min+g1.adjMatrix[k][j])<distance[j]){
  24. distance[j] = min+g1.adjMatrix[k][j];
  25. pre[j] = k;
  26. }
  27. }
  28. }
  29. }


Floyd

使用: (1)邻接矩阵:存储图;



[java]​ 

​view plain​​ 

​copy​


  1. <span style="white-space:pre">  </span>/**
  2. * O(n^3)
  3. * 求出任意顶点之间的距离
  4. */
  5. @Test
  6. publicvoid​floyd(Graph1 g) {
  7. int​i, j, k;
  8. int​length = g.vertex.length;
  9. int​dist[][] =​newint​[length][length];
  10. int​pre[][] =​newint​[length][length];
  11. for​(i = 0; i < g.vertex.length; i++) {
  12. for​(j = 0; j < g.vertex.length; j++) {
  13. pre[i][j] = j;
  14. dist[i][j] = g.adjMatrix[i][j];
  15. }
  16. }
  17. for​(i = 0; i < length; i++) {
  18. for​(j = 0; j < g.vertex.length; j++) {
  19. for​(k = 0; k < g.vertex.length; k++) {
  20. if​(dist[i][j] > dist[i][k] + dist[k][j]) {
  21. dist[i][j] = dist[i][k] + dist[k][j];
  22. pre[i][j] = pre[i][k];
  23. }
  24. }
  25. }
  26. }
  27. System.out.println();
  28. }



六、拓扑排序



使用​​数据结构​​:

(1)栈:用来存放入度为0的节点;

(2)变种邻接列表:作为图的存储结构;此邻接列表的顶点节点还需要存放入度属性;



[java]​ 

​view plain​​ 

​copy​


  1. /**
  2. * O(n+e)
  3. */
  4. privatestatic​String topologicalSort(Graph2 g2) {
  5. Stack<Integer> s =​new​Stack<Integer>();
  6. int​count = 0;
  7. for​(​int​i=0;i<g2.nodes.length;i++){
  8. if​(g2.nodes[i].indegree==0){
  9. s.push(i);
  10. }
  11. }
  12. while​(!s.isEmpty()){
  13. int​value = s.pop();
  14. System.out.println(value+"、");
  15. count++;
  16. EdgeNode node = g2.nodes[value].next;
  17. while​(node!=​null​){
  18. g2.nodes[node.idx].indegree--;
  19. if​(g2.nodes[node.idx].indegree==0){
  20. s.push(node.idx);
  21. }
  22. node = node.next;
  23. }
  24. }
  25. if​(count<g2.nodes.length){
  26. return​"error";
  27. }
  28. return​"ok";
  29. }







七、关键路径

使用数据结构: (1)变种邻接列表:同拓扑排序; (2)变量: ltv表示某个事件的最晚开始时间; etv表示事件最早开始时间; ete表示活动最早开始时间; lte表示活动最晚开始时间;



[java]​ 

​view plain​​ 

​copy​


  1. <span style="white-space:pre">  </span>//O(n+e)
  2. <span style="white-space:pre">  </span>@Test
  3. publicvoid​CriticalPath(){
  4. Stack<Integer> stack = topological_etv();
  5. int​length = stack.size();
  6. if​(stack==​null​){
  7. return​;
  8. }
  9. else​{
  10. int​[]ltv =​newint​[length];
  11. for​(​int​i=0;i<stack.size();i++){
  12. ltv[i] = etv[stack.size()-1];
  13. }
  14. //从拓扑排序的最后开始计算ltv
  15. while​(!stack.isEmpty()){
  16. int​top = stack.pop();
  17. EdgeNode current = g.nodes[top].next;
  18. while​(current!=​null​){
  19. int​idx = current.idx;
  20. //最晚发生时间要取所有活动中最早的
  21. if​((ltv[idx]-current.weight)<ltv[top]){
  22. ltv[top] = ltv[idx]-current.weight;
  23. }
  24. }
  25. }
  26. int​ete = 0;
  27. int​lte = 0;
  28. for​(​int​j=0;j<length;j++){
  29. EdgeNode current = g.nodes[j].next;
  30. while​(current!=​null​){
  31. int​idx = current.idx;
  32. ete = etv[j];
  33. lte = ltv[idx]-current.weight;
  34. if​(ete==lte){
  35. //是关键路径
  36. }
  37. }
  38. }
  39. }
  40. }
  41. private​Stack<Integer> topological_etv(){
  42. Stack<Integer> stack2 =​new​Stack<Integer>();
  43. Stack<Integer>stack1 =​new​Stack<Integer>();
  44. for​(​int​i=0;i<g.nodes.length;i++){
  45. if​(g.nodes[i].indegree==0){
  46. stack1.add(i);
  47. }
  48. }
  49. etv[] =​newint​[g.nodes.length];
  50. int​count = 0;
  51. while​(!stack1.isEmpty()){
  52. int​top = stack1.pop();
  53. count++;
  54. stack2.push(top);
  55. EdgeNode current = g.nodes[top].next;
  56. while​(current!=​null​){
  57. int​idx = current.idx;
  58. if​((--g.nodes[idx].indegree)==0){
  59. stack1.push(idx);
  60. }
  61. if​((etv[top]+current.weight)>etv[idx]){
  62. etv[idx] = etv[top]+current.weight;
  63. }
  64. current = current.next;
  65. }
  66. }
  67. if​(count<g.nodes.length){
  68. returnnull​;
  69. }
  70. return​stack2;
  71. }