一、基本术语
图:由有穷、非空点集和边集合组成,简写成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; 无向图为对称矩阵
维持一个一维数组,存储顶点信息,比如顶点的名字;
下图为带有权重的图的邻接矩阵表示法:
缺点:邻接矩阵表示法对于稀疏图来说不合理,因为太浪费空间;
2.邻接表
如果图示一般的图,则如下图:
如果是网,即边带有权值,则如下图:
3.十字链表
只针对有向图;,适用于计算出度和入度;
顶点结点:
边结点:
好处:创建的时间复杂度和邻接链表相同,但是能够同时计算入度和出度;
4.邻接多重表
针对无向图; 如果我们只是单纯对节点进行操作,则邻接表是一个很好的选择,但是如果我们要在邻接表中删除一条边,则需要删除四个顶点(因为无向图);
在邻接多重表中,只需要删除一个节点,即可完成边的删除,因此比较方便;
因此邻接多重表适用于对边进行删除的操作;
顶点节点和邻接表没区别,边表节点如下图:
比如:
5.边集数组
适合依次对边进行操作;
存储边的信息,如下图:
三、图的遍历
DFS
思想:往深里遍历,如果不能深入,则回朔;
比如:
[java]
copy
- /**
- * O(v+e)
- */
- @Test
- publicvoidDFS() {
- for(inti = 0; i < g.nodes.length; i++) {
- if(!visited[i]) {
- DFS_Traverse(g, i);
- }
- }
- }
- privatevoidDFS_Traverse(Graph2 g,inti) {
- 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
思想:对所有邻接节点遍历;
[java]
copy
- <span style="white-space:pre"> </span>/**
- * O(v+e)
- */
- @Test
- publicvoidBFS() {
- ArrayList<Integer> list =newArrayList<Integer>();
- for(inti = 0; i < g.nodes.length; i++) {
- if(!visited[i]) {
- visited[i] =true;
- list.add(i);
- System.out.println(i);
- while(!list.isEmpty()) {
- intk = 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
邻接矩阵存储;
[java]
copy
- <span style="white-space:pre"> </span>/**
- * 时间复杂度为O(n^2)
- * 适用于稠密图
- */
- @Test
- publicvoidprim(){
- intcost[] =newint[9];
- intpre[] =newint[9];
- for(inti=0;i<g1.vertex.length;i++){
- cost[i] = g1.adjMatrix[0][i];
- }
- cost[0] = 0;
- for(inti=1;i<g1.vertex.length;i++){
- intmin = 65536;
- intk = 0;
- for(intj=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(intj=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
边集数组存储;
[java]
copy
- <span style="white-space:pre"> </span>/**
- * 时间复杂度:O(eloge)
- * 适用于稀疏图
- */
- @Test
- publicvoidkrustral(){
- Edge[] edges = initEdges();
- intparent[] =newint[9];
- for(inti=0;i<edges.length;i++){
- Edge edge = edges[i];
- intm = find(parent,edge.begin);
- intn = find(parent,edge.end);
- if(m!=n){
- parent[m] = n;
- System.out.println(m+","+n);
- }
- }
- }
- privatestaticintfind(int[] parent,intf) {
- while(parent[f] > 0) {
- f = parent[f];
- }
- returnf;
- }
五、最短路径
dijkstra算法
邻接矩阵存储; [java]
copy
- <span style="white-space:pre"> </span>//O(n^2)
- @Test
- publicvoidDijkstra(){
- intdistance[] =newint[9];
- intpre[] =newint[9];
- booleanfinished[] =newboolean[9];
- finished[0] =true;
- for(inti=0;i<9;i++){
- distance[i] = g1.adjMatrix[0][i];
- }
- intk = 0;
- for(inti=1;i<9;i++){
- intmin = 65536;
- for(intj=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(intj=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
使用: (1)邻接矩阵:存储图;
[java]
copy
- <span style="white-space:pre"> </span>/**
- * O(n^3)
- * 求出任意顶点之间的距离
- */
- @Test
- publicvoidfloyd(Graph1 g) {
- inti, j, k;
- intlength = g.vertex.length;
- intdist[][] =newint[length][length];
- intpre[][] =newint[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)变种邻接列表:作为图的存储结构;此邻接列表的顶点节点还需要存放入度属性;
[java]
copy
- /**
- * O(n+e)
- */
- privatestaticString topologicalSort(Graph2 g2) {
- Stack<Integer> s =newStack<Integer>();
- intcount = 0;
- for(inti=0;i<g2.nodes.length;i++){
- if(g2.nodes[i].indegree==0){
- s.push(i);
- }
- }
- while(!s.isEmpty()){
- intvalue = 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";
- }
七、关键路径
使用数据结构: (1)变种邻接列表:同拓扑排序; (2)变量: ltv表示某个事件的最晚开始时间; etv表示事件最早开始时间; ete表示活动最早开始时间; lte表示活动最晚开始时间;
[java]
copy
- <span style="white-space:pre"> </span>//O(n+e)
- <span style="white-space:pre"> </span>@Test
- publicvoidCriticalPath(){
- Stack<Integer> stack = topological_etv();
- intlength = stack.size();
- if(stack==null){
- return;
- }
- else{
- int[]ltv =newint[length];
- for(inti=0;i<stack.size();i++){
- ltv[i] = etv[stack.size()-1];
- }
- //从拓扑排序的最后开始计算ltv
- while(!stack.isEmpty()){
- inttop = stack.pop();
- EdgeNode current = g.nodes[top].next;
- while(current!=null){
- intidx = current.idx;
- //最晚发生时间要取所有活动中最早的
- if((ltv[idx]-current.weight)<ltv[top]){
- ltv[top] = ltv[idx]-current.weight;
- }
- }
- }
- intete = 0;
- intlte = 0;
- for(intj=0;j<length;j++){
- EdgeNode current = g.nodes[j].next;
- while(current!=null){
- intidx = current.idx;
- ete = etv[j];
- lte = ltv[idx]-current.weight;
- if(ete==lte){
- //是关键路径
- }
- }
- }
- }
- }
- privateStack<Integer> topological_etv(){
- Stack<Integer> stack2 =newStack<Integer>();
- Stack<Integer>stack1 =newStack<Integer>();
- for(inti=0;i<g.nodes.length;i++){
- if(g.nodes[i].indegree==0){
- stack1.add(i);
- }
- }
- etv[] =newint[g.nodes.length];
- intcount = 0;
- while(!stack1.isEmpty()){
- inttop = stack1.pop();
- count++;
- stack2.push(top);
- EdgeNode current = g.nodes[top].next;
- while(current!=null){
- intidx = 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){
- returnnull;
- }
- returnstack2;
- }