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个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小生成树一个连通图的生生成最小生成树的算法:普里姆算法和克鲁斯卡尔算法(两个算法比较难,只给出实现代码)
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条边。我们把构造连通网的最小代价生成树。称为最小生成树一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小生成