1.概念
图是一种重要的数据结构,是由顶点的有穷非空集合和顶点之间边的集合组成,由G(V,E)表示,其中G表示一个图,V是该图中顶点的集合,E是图G中边的集合。基本概念包括:顶点,边,有向,无向,权,路径回路,连通域,邻接点,度,入边,出边,入度,出度。
2.存储结构
两种存储结构:邻接矩阵和邻接表。
邻接矩阵:
邻接矩阵特征:
1、邻接矩阵是正矩阵,即横纵维数相等。
2、矩阵的每一行或一列代表一个顶点,行与列的交点对应这两个顶点的边。
3、矩阵的点代表边的属性,1代表有边,0代表无边,对角线上对应的横纵轴代表相同的顶点,边没有意义均为0。
4、如果是无向图,那么矩阵是对称矩阵;如果是有向图则不一定。
5、如果是有权图,矩阵点数值可以是权值。
邻接表:
邻接表特性:
1、邻接表示一个有单链表组成的数组 。
2、图中的每一个顶点都有一个链,数组的大小等于图中顶点的个数。
3、无向图的链的第一个元素是本顶点,后继分别连接着和这个顶点相连的顶点;有向图的链第一个顶点是本顶点,后继是以本顶点为起点的边的终点。
4、如果是有权图,可以在节点元素中设置权值属性 。
Java实现图的结构:
package com.binaryTree;
public class Graph {
//顶点数量
private int vertexSize;
//顶点数组
private int[] vertexs;
private int[][] matrix;
//设置不可到达的权值为1000
private static final int MAX_WEIGHT=1000;
public Graph(int vertexSize){
this.vertexSize=vertexSize;
matrix=new int[vertexSize][vertexSize];
vertexs=new int[vertexSize];
for(int i=0;i<vertexSize;i++){
vertexs[i]=i;
}
}
public int[] getVertexs() {
return vertexs;
}
public void setVertexs(int[] vertexs) {
this.vertexs = vertexs;
}
/**
*获取某个顶点的出度
* @return
*/
public int getOutDegree(int index){
int degree=0;
for(int j=0;j<matrix[index].length;j++){
int weight=matrix[index][j];
if(weight!=0&&weight!=MAX_WEIGHT){
degree++;
}
}
return degree;
}
/**
* 获取两个顶点之间的权值
* @param args
*/
public int getWeight(int v1,int v2){
return matrix[v1][v2]==0?0:(matrix[v1][v2]==MAX_WEIGHT?-1:matrix[v1][v2]);
}
public static void main(String[] args) {
Graph graph=new Graph(5);
int[] a1=new int[]{0,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,6};
int[] a2=new int[]{9,0,3,MAX_WEIGHT,MAX_WEIGHT};
int[] a3=new int[]{2,MAX_WEIGHT,0,5,MAX_WEIGHT};
int[] a4=new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,0,1};
int[] a5=new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,0};
graph.matrix[0]=a1;
graph.matrix[1]=a2;
graph.matrix[2]=a3;
graph.matrix[3]=a4;
graph.matrix[4]=a5;
int degree=graph.getOutDegree(1);
System.out.println("出度:"+degree);
int weight=graph.getWeight(0, 4);
System.out.println("权值:"+weight);
}
}
图的遍历:
我们希望从图中某一个顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程称为图的遍历。
两种遍历分别为深度优先算法遍历和广度优先算法遍历:
以该图为例实现两种遍历(注意图有误,v3-v6之间直线应去掉;v0-v8即对应输出0-8)
深度优先算法遍历:
从图中的某个顶点V出发,访问此节点,然后依次从V的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V路径想通的顶点都被访问到。
package com.binaryTree;
public class Graph {
//顶点数量
private int vertexSize;
//顶点数组
private int[] vertexs;
private int[][] matrix;
//设置不可到达的权值为1000
private static final int MAX_WEIGHT=1000;
private boolean[] isVisited;
public Graph(int vertexSize){
this.vertexSize=vertexSize;
matrix=new int[vertexSize][vertexSize];
vertexs=new int[vertexSize];
for(int i=0;i<vertexSize;i++){
vertexs[i]=i;
}
isVisited=new boolean[vertexSize];
}
public int[] getVertexs() {
return vertexs;
}
public void setVertexs(int[] vertexs) {
this.vertexs = vertexs;
}
/**
*获取某个顶点的出度
* @return
*/
public int getOutDegree(int index){
int degree=0;
for(int j=0;j<matrix[index].length;j++){
int weight=matrix[index][j];
if(weight!=0&&weight!=MAX_WEIGHT){
degree++;
}
}
return degree;
}
/**
* 获取两个顶点之间的权值
* @param args
*/
public int getWeight(int v1,int v2){
return matrix[v1][v2]==0?0:(matrix[v1][v2]==MAX_WEIGHT?-1:matrix[v1][v2]);
}
/**
* 获取某个顶点的第一个邻接点
* @param args
*/
public int getFirstNeigbour(int index){
for(int j=0;j<vertexSize;j++){
if(matrix[index][j]>0&&matrix[index][j]<MAX_WEIGHT){
return j;
}
}
return -1;
}
/**
* 根据前一个邻接点的下标来取得下一个邻接点
* v表示要找的顶点
* index表示该顶点相对于哪个邻接点去获取下一个邻接点
* @param args
*/
public int getNextNeighbour(int v,int index){
for(int j=index+1;j<vertexSize;j++){
if(matrix[v][j]>0&&matrix[v][j]<MAX_WEIGHT){
return j;
}
}
return -1;
}
/**
* 图的深度优先遍历算法
* @param args
*/
private void depthFirstSearch(int i){
isVisited[i]=true;
int w=getFirstNeigbour(i);
while(w!=-1){
if(!isVisited[w]){
//需要遍历该顶点
System.out.println("访问到了:"+w+"顶点");
depthFirstSearch(w);
}
w=getNextNeighbour(i, w);
}
}
public void depthFirstSearch(){
isVisited=new boolean[vertexSize];
for(int i=0;i<vertexSize;i++){
if(!isVisited[i]){
System.out.println("访问到了:"+i+"顶点");
depthFirstSearch(i);
}
}
isVisited=new boolean[vertexSize];
}
public static void main(String[] args) {
Graph graph=new Graph(9);
int[] a1=new int[]{0,10,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,11,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT};
int[] a2=new int[]{10,0,18,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,16,MAX_WEIGHT,12};
int[] a3=new int[]{MAX_WEIGHT,MAX_WEIGHT,0,22,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,8};
int[] a4=new int[]{MAX_WEIGHT,MAX_WEIGHT,22,0,20,MAX_WEIGHT,MAX_WEIGHT,16,21};
int[] a5=new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,20,0,26,MAX_WEIGHT,7,MAX_WEIGHT};
int[] a6=new int[]{11,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,26,0,176,MAX_WEIGHT,MAX_WEIGHT};
int[] a7=new int[]{MAX_WEIGHT,16,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,17,0,19,MAX_WEIGHT};
int[] a8=new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,16,7,MAX_WEIGHT,19,0,MAX_WEIGHT};
int[] a9=new int[]{MAX_WEIGHT,12,8,21,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,0};
graph.matrix[0]=a1;
graph.matrix[1]=a2;
graph.matrix[2]=a3;
graph.matrix[3]=a4;
graph.matrix[4]=a5;
graph.matrix[5]=a6;
graph.matrix[6]=a7;
graph.matrix[7]=a8;
graph.matrix[8]=a9;
int degree=graph.getOutDegree(1);
System.out.println("出度:"+degree);
int weight=graph.getWeight(0, 4);
System.out.println("权值:"+weight);
graph.depthFirstSearch();
}
}
输出:
出度:4
权值:-1
访问到了:0顶点
访问到了:1顶点
访问到了:2顶点
访问到了:3顶点
访问到了:4顶点
访问到了:5顶点
访问到了:6顶点
访问到了:7顶点
访问到了:8顶点
广度优先算法遍历:
从图中的某个顶点V出发,并在访问此顶点之后依次访问V的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V有路径相通的顶点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
/**
* 实现广度优先算法遍历
* @param args
*/
public void broadFirstSearch(){
isVisited=new boolean[vertexSize];
for(int i=0;i<vertexSize;i++){
if(!isVisited[i]){
broadFirstSearch(i);
}
}
isVisited=new boolean[vertexSize];
}
private void broadFirstSearch(int i){
int u,w;
LinkedList<Integer> queue=new LinkedList<Integer>();
System.out.println("访问到了"+i+"顶点");
isVisited[i]=true;
queue.add(i);
while(!queue.isEmpty()){
u=(Integer)(queue.removeFirst()).intValue();
w=getFirstNeigbour(u);
while(w!=-1){
if(!isVisited[w]){
System.out.println("访问到了"+w+"顶点");
isVisited[w]=true;
queue.add(w);
}
w=getNextNeighbour(u, w);
}
}
}
输出:
访问到了0顶点
访问到了1顶点
访问到了5顶点
访问到了2顶点
访问到了6顶点
访问到了8顶点
访问到了4顶点
访问到了3顶点
访问到了7顶点
最小生成树:
一个连通图的
如图所示我们引入连通图来解决我们遇到的问题,9个村庄对应图上的9个顶点,边表示两个村庄的通信线路,每条边上的权重就是我们搭建这条线路所需要的成本,所以现在我们有9个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。生成树是一个极小的连通子图,
生成最小生成树的算法:普里姆算法和克鲁斯卡尔算法(两个算法比较难,只给出实现代码)
Java实现普里姆算法:
/**
* prim 普里姆算法
* @param args
*/
public void prim(){
//最小代价顶点权值的数组,为0表示已不需要考虑
int[] lowcost=new int[vertexSize];
//放顶点权值
int[] adjvex=new int[vertexSize];
int min,minId,sum=0;
for(int i=1;i<vertexSize;i++){
lowcost[i]=matrix[0][i];
}
for(int i=1;i<vertexSize;i++){
min=MAX_WEIGHT;
minId=0;
for(int j=1;j<vertexSize;j++){
if(lowcost[j]<min&&lowcost[j]>0){
min=lowcost[j];
minId=j;
}
}
System.out.println("顶点:"+adjvex[minId]+"权值:"+min);
sum+=min;
lowcost[minId]=0;
for(int j=1;j<vertexSize;j++){
if(lowcost[j]!=0&&matrix[minId][j]<lowcost[j]){
lowcost[j]=matrix[minId][j];
adjvex[j]=minId;
}
}
}
System.out.println("最小生成树权值和:"+sum);
}
输出:
顶点:0权值:10
顶点:0权值:11
顶点:1权值:12
顶点:8权值:8
顶点:1权值:16
顶点:6权值:19
顶点:7权值:7
顶点:7权值:16
最小生成树权值和:99
Java实现克鲁斯卡尔算法:中全ava以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小生成树一个连通图的生成树是一个极小的连通有图中全的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小生成树一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生
成树。称为最小生成树
package com.binaryTree;
public class GraphKrusKal {
private Edge[] edges;
private int edgeSize;
public GraphKrusKal( int edgeSize) {
this.edgeSize=edgeSize;
edges = new Edge[edgeSize];
}
class Edge{
private int start;
private int end;
private int edgeWeight;
public Edge(int start, int end, int edgeWeight) {
this.start = start;
this.end = end;
this.edgeWeight = edgeWeight;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getEdgeWeight() {
return edgeWeight;
}
public void setEdgeWeight(int edgeWeight) {
this.edgeWeight = edgeWeight;
}
}
public void createEdge(){
Edge edge0=new Edge(4,7,7);
Edge edge1=new Edge(2,8,8);
Edge edge2=new Edge(0,1,10);
Edge edge3=new Edge(0,5,11);
Edge edge4=new Edge(1,8,12);
Edge edge5=new Edge(3,7,16);
Edge edge6=new Edge(1,6,16);
Edge edge7=new Edge(5,6,17);
Edge edge8=new Edge(1,2,18);
Edge edge9=new Edge(6,7,19);
Edge edge10=new Edge(3,4,20);
Edge edge11=new Edge(3,8,21);
Edge edge12=new Edge(2,3,22);
Edge edge13=new Edge(3,6,24);
Edge edge14=new Edge(4,5,26);
edges[0]=edge0;
edges[1]=edge1;
edges[2]=edge2;
edges[3]=edge3;
edges[4]=edge4;
edges[5]=edge5;
edges[6]=edge6;
edges[7]=edge7;
edges[8]=edge8;
edges[9]=edge9;
edges[10]=edge10;
edges[11]=edge11;
edges[12]=edge12;
edges[13]=edge13;
edges[14]=edge14;
}
/**
* 克鲁斯卡尔算法
* @param args
*/
public void minSpanTreekruskal(){
int n,m,sum=0;
//定义数组,下表为起点,值为终点
int[] parent=new int[edgeSize];
for(int i=0;i<edgeSize;i++){
parent[i]=0;
}
for(int i=0;i<edgeSize;i++){
n=find(parent,edges[i].start);
m=find(parent,edges[i].end);
if(n!=m){
parent[n]=m;
System.out.println("起始顶点:"+edges[i].start+"结束顶点"+edges[i].end);
sum+=edges[i].edgeWeight;
}
}
System.out.println("最小生成树权值:"+sum);
}
private int find(int[] parent, int f) {
while(parent[f]>0){
f=parent[f];
}
return f;
}
public static void main(String[] args) {
GraphKrusKal graphKrusKal=new GraphKrusKal(15);
graphKrusKal.createEdge();
graphKrusKal.minSpanTreekruskal();
}
}
起始顶点:4结束顶点7
起始顶点:2结束顶点8
起始顶点:0结束顶点1
起始顶点:0结束顶点5
起始顶点:1结束顶点8
起始顶点:3结束顶点7
起始顶点:1结束顶点6
起始顶点:6结束顶点7
最小生成树权值:99
一个连通图的
成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的
最短路径的迪杰斯特拉算法:
package com.binaryTree;
public class GraphDijstra {
private static final int MAXVEX=9;
private static final int MAXWEIGHT=65535;
private int shortTablePath[]=new int[MAXVEX];
/**
* 获取一个图的最短路径
*/
public void shortestPathDijstra(Graph graph){
int min = 0;
int k=0;//记录下表
boolean isgetPath[]=new boolean[MAXVEX];
for(int v=0;v<graph.getVertexSize();v++){
//暂时获取v0到其他顶点的路径权值
shortTablePath[v]=graph.getMatrix()[0][v];
}
shortTablePath[0]=0;
isgetPath[0]=true;
for(int v=1;v<graph.getVertexSize();v++){
min=MAXWEIGHT;
for(int w=0;w<graph.getVertexSize();w++){
if(!isgetPath[w]&&shortTablePath[w]<min){
k=w;
min=shortTablePath[w];
}
}
isgetPath[k]=true;
for(int j=0;j<graph.getVertexSize();j++){
if(!isgetPath[j]&&(min+graph.getMatrix()[k][j])<shortTablePath[j]){
shortTablePath[j]=min+graph.getMatrix()[k][j];
}
}
}
for(int w=0;w<graph.getVertexSize();w++){
System.out.println("V0到V"+w+"最小路径权值和为:"+shortTablePath[w]);
}
}
public static void main(String[] args) {
GraphDijstra dijstra=new GraphDijstra();
Graph graph=new Graph(MAXVEX);
graph.createGraph();
dijstra.shortestPathDijstra(graph);
}
}
输出:
V0到V0最小路径权值和为:0
V0到V1最小路径权值和为:1
V0到V2最小路径权值和为:4
V0到V3最小路径权值和为:7
V0到V4最小路径权值和为:5
V0到V5最小路径权值和为:8
V0到V6最小路径权值和为:10
V0到V7最小路径权值和为:12
V0到V8最小路径权值和为:16
t最小
拓扑排序:
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。
在一个表示工程的有向图中,将顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。
Java实现拓扑排序:
package com.binaryTree;
import java.util.Stack;
import com.binaryTree.GraphKrusKal.Edge;
public class GraphTopologic {
VertexNode[] adjList;
//边表顶点
class EdgeNode{
private int adjVet;
private EdgeNode next;
private int weight;
public EdgeNode(int adjVet){
this.adjVet=adjVet;
}
public int getAdjVet() {
return adjVet;
}
public void setAdjVet(int adjVet) {
this.adjVet = adjVet;
}
public EdgeNode getNext() {
return next;
}
public void setNext(EdgeNode next) {
this.next = next;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
//邻接顶点(竖着)
class VertexNode{
private int in;//入度数量
private String data;
private EdgeNode firstEdge;//指向出去的顶点
public VertexNode(int in, String data) {
this.in = in;
this.data = data;
}
public int getIn() {
return in;
}
public void setIn(int in) {
this.in = in;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public EdgeNode getFirstEdge() {
return firstEdge;
}
public void setFirstEdge(EdgeNode firstEdge) {
this.firstEdge = firstEdge;
}
}
private void createGraph(){
VertexNode node0=new VertexNode(0,"v0");
VertexNode node1=new VertexNode(0,"v1");
VertexNode node2=new VertexNode(2,"v2");
VertexNode node3=new VertexNode(0,"v3");
VertexNode node4=new VertexNode(2,"v4");
VertexNode node5=new VertexNode(3,"v5");
VertexNode node6=new VertexNode(1,"v6");
VertexNode node7=new VertexNode(2,"v7");
VertexNode node8=new VertexNode(2,"v8");
VertexNode node9=new VertexNode(2,"v9");
VertexNode node10=new VertexNode(1,"v10");
VertexNode node11=new VertexNode(2,"v11");
VertexNode node12=new VertexNode(1,"v12");
VertexNode node13=new VertexNode(2,"v13");
adjList=new VertexNode[14];
adjList[0]=node0;
adjList[1]=node1;
adjList[2]=node2;
adjList[3]=node3;
adjList[4]=node4;
adjList[5]=node5;
adjList[6]=node6;
adjList[7]=node7;
adjList[8]=node8;
adjList[9]=node9;
adjList[10]=node10;
adjList[11]=node11;
adjList[12]=node12;
adjList[13]=node13;
node0.firstEdge=new EdgeNode(11);node0.firstEdge.next=new EdgeNode(5);node0.firstEdge.next.next=new EdgeNode(4);
node1.firstEdge=new EdgeNode(8);node1.firstEdge.next=new EdgeNode(4);node1.firstEdge.next.next=new EdgeNode(2);
node2.firstEdge=new EdgeNode(9);node2.firstEdge.next=new EdgeNode(6);node2.firstEdge.next.next=new EdgeNode(5);
node3.firstEdge=new EdgeNode(13);node3.firstEdge.next=new EdgeNode(2);
node4.firstEdge=new EdgeNode(7);
node5.firstEdge=new EdgeNode(12);node5.firstEdge.next=new EdgeNode(8);
node6.firstEdge=new EdgeNode(5);
node8.firstEdge=new EdgeNode(7);
node9.firstEdge=new EdgeNode(11);node9.firstEdge.next=new EdgeNode(10);
node10.firstEdge=new EdgeNode(13);
node12.firstEdge=new EdgeNode(9);
}
/**
* 拓扑排序
* @param args
*/
private void topologicSort(){
Stack<Integer> stack=new Stack<Integer>();
int count=0;
int k=0;
for(int i=0;i<14;i++){
if(adjList[i].in==0){
stack.push(i);
}
}
while(!stack.isEmpty()){
int pop=stack.pop();
System.out.println("顶点"+adjList[pop].data);
count++;
for(EdgeNode node=adjList[pop].firstEdge;node!=null;node=node.next){
k=node.adjVet;//下标
if(--adjList[k].in==0){
stack.push(k);
}
}
}
if(count<14){
try {
throw new Exception("拓扑排序错误");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
GraphTopologic topologic=new GraphTopologic();
topologic.createGraph();
topologic.topologicSort();
}
}
输出:
顶点v3
顶点v1
顶点v2
顶点v6
顶点v0
顶点v4
顶点v5
顶点v8
顶点v7
顶点v12
顶点v9
顶点v10
顶点v13
顶点v11
仅供自己学习笔记。
图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小