图的存储(领接矩阵,领接表法)
领接表是在优化表的存储方法,当采用领接矩阵的时候,当要查看“表有多上条边“,“图是否是连通”的时候,必须要对对角线以外的n^2-n个元素逐一排查,时间开销很高,当n个顶点中边的个数很少时,就会变成稀疏矩阵,很浪费空间。
#if 1
#pragma once
#include<iostream>
using namespace std;
#include<queue>
#define DEFAULT_VERTICES_SIZE 10
template<class Type>
class GraphLink;
class Edge
{
public:
Edge(int num):dest(num),link(NULL)
{}
~Edge()
{}
public:
int dest;
Edge *link;
};
template<class Type>
class VerticeList
{
friend class GraphLink<Type>;
public:
VerticeList():data(Type()),adj(NULL)
{
}
~VerticeList()
{}
private:
Type data;
Edge *adj;
};
template<class Type>
class GraphLink
{
public:
GraphLink(int sz = DEFAULT_VERTICES_SIZE)
{
MaxVertices = sz > DEFAULT_VERTICES_SIZE?sz:DEFAULT_VERTICES_SIZE;
//Nodetable 是头结点类型的指针;
Nodetable = new VerticeList<Type>[MaxVertices];
for(int i = 0;i<MaxVertices;++i)
{
Nodetable[i].adj = NULL;
}
numVertices = numEdges = 0;
}
public: // 插入的头结点是 type 类型
bool InsertVertices(const Type &v)
{
if(numVertices >= MaxVertices)
return false;
Nodetable[numVertices++].data = v;
return true;
}
bool InsertEdge(Type vertex1,Type vertex2)
{
int v1 = GetPosVertex(vertex1);//首先要得到在那俩个顶点之间插入边;
int v2 = GetPosVertex(vertex2);
if(v1== -1 || v2 == -1)//如果这俩个顶点不存在,那就不进行插入边了;
{
return false;
}
else
{
//因为这是无向图,所以俩边都要插入边;
// v1-->v2
Edge *e = new Edge(v2);//首先要明白这条边要连接哪一个索引;这里是V2;
e->link = Nodetable[v1].adj;//这里头插,一定要把后一个给记住;否则就找不到后一个结点;
Nodetable[v1].adj = e;
// v2-->v1;
e = new Edge(v1);
e->link = Nodetable[v2].adj;
Nodetable[v2].adj = e;
numEdges++;
return true;
}
}
int GetPosVertex(Type vertex)
{
for(int i =0;i<numVertices;++i )
{
if(Nodetable[i].data == vertex)
return i;
}
return -1;
}
int NumberOfVertice()const
{
return numVertices;
}
int GetFirstNeigbor(const Type vertex)
{
int v = GetPosVertex(vertex);
Edge *e = Nodetable[v].adj;
while(e != NULL)
{
if(e->dest >0)
return e->dest ;
e = e->link;
}
return -1;
}
int GetNextNeighbor(const Type vertex1,const Type vertex2)
{
int v1 = GetPosVertex(vertex1);
int v2 = GetPosVertex(vertex2);
if(v1 == -1 || v2 == -1)
return -1;
Edge * e = Nodetable[v1].adj;
while( e!= NULL)
{
if(e->dest == v2)//就是先找到v2,然后她的下一个就是所需要的结点;
break;
e = e->link;
}
if(e == NULL)
{
return -1;
}//这个条件预防虽然找到了e,但是e->link == NULL;
if( e->link != NULL)
return e->link->dest;
else
return -1;
return true;
}
bool RemoveEdge(const Type &vertex1, const Type &vertex2)
{
int v1 = GetPosVertex(vertex1);
int v2 = GetPosVertex(vertex2);
if(v1==-1 || v2==-1)
return -1;
//v1 --> v2
Edge *pe = Nodetable[v1].adj;
Edge *q = NULL;
while(pe!=NULL && pe->dest!=v2)
{
q = pe;
pe = pe->link;
}
if(pe == NULL)
return false;
if(pe == Nodetable[v1].adj)
Nodetable[v1].adj = pe->link;
else
q->link = pe->link;
delete pe;
pe = NULL;
//v2--v1
pe = Nodetable[v2].adj;
q = NULL;
while(pe!=NULL &&pe->dest!=v1)
{
q = pe;
pe = pe->link;
}
if(pe == Nodetable[v2].adj)
Nodetable[v2].adj = pe->link;
else
q->link = pe->link;
delete pe;
pe = NULL;
numEdges--;
return true;
}
//删除头稍微有点麻烦,首先你要将自己连接的头都释放掉,然后在其他头上删除该头;
bool RemoveVertice(const Type &vertex)
{
int v1 = GetPosVertex(vertex);
if(v1 == -1)
return false;
/* ///0并不代表'A';
Edge *e1 = Nodetable[v1].adj;
while(e1 != NULL)
{
int v2 = e1->dest;
switch(v2)
{
case 0:
e1 = e1->link ;
RemoveEdge(vertex,'A');
break;
case 1:
e1 = e1->link ;
RemoveEdge(vertex,'B');
break;
case 2:
e1 = e1->link ;
RemoveEdge(vertex,'C');
break;
case 3:
e1 = e1->link;
RemoveEdge(vertex,'D');
break;
}
}*/
Edge * pe = Nodetable[v1].adj;
Edge *q = NULL;
Edge *p = NULL;
while(pe != NULL)
{
int dest;
dest = pe -> dest;
// v1-->v2;
Nodetable[v1].adj = pe->link;
delete pe;
// v2->v1;
p = Nodetable[dest].adj;
while( p!= NULL && p->dest != v1)
{
q = p;
p = p->link;
}
if(q == NULL)
Nodetable[dest].adj = p ->link ;
else
q->link = p ->link ;
pe = Nodetable[v1].adj;
}
numVertices--;
p = NULL;Edge *s = NULL;
Nodetable[v1].data =Nodetable[numVertices].data;
p = Nodetable[v1].adj = Nodetable[numVertices].adj;
while(p != NULL)
{
s = Nodetable[p->dest].adj;
while( s!= NULL)
{
if(s->dest == numVertices)
{
s->dest = v1;
break;
}
else
s = s->link;
}
p = p->link;
}
return true;
}
void ShownGrap()
{
int i;
for( i = 0;i<numVertices;++i)
{
cout << Nodetable[i].data;
cout <<"-->";
Edge *e = Nodetable[i].adj;
while(e != NULL)
{
cout << e->dest ;
cout <<"-->";
e = e->link;
}
cout <<"Nll"<<endl;
}
}
public:
void DFS()
{
bool *visited = new bool[numVertices];
for(int i = 0;i < numVertices;++i)
{
visited[i] = false;
}
DFS(0,visited);
delete []visited;
}
void DFS(int v,bool visited[])
{
cout << Nodetable[v].data;//首先访问顶点
visited[v] = true; //将顶点设置为已经访问过的标志
int w = GetFirstNeigbor(Nodetable[v].data);//接着访问他的第一个临接顶点;
while( w != -1 )
{
if( !visited[w])DFS(w,visited);//若w未访问过,递归访问w,
w = GetNextNeighbor(Nodetable[v].data,Nodetable[w].data);//取v排在w后的下一个邻接顶点;
}
}
void BFS()
{
bool *visited = new bool[numVertices];
for(int i = 0;i < numVertices;++i)
{
visited[i] = false;
}
BFS(0,visited);
delete []visited;
}
void BFS(int v,bool visited[])
{
cout << Nodetable[v].data<<" ";
visited[v] = true;//访问顶点,标志为已经访问过的;
queue<int>Q;//借助队列,因为在BFS搜索过程中,当搜索完当前结点的所有领结顶点后,在顺序访问w1,w2,w3......
Q.push(v); //他们各自的领结顶点;顶点进入队列实现分层访问;
while(!Q.empty())//循环访问所有顶点(w1,w2,w3......)
{
int v = Q.front();
Q.pop();
int w = GetFirstNeigbor(Nodetable[v].data);//顶点v的第一个领结顶点
while(w != -1)//若临接顶点存在,
{ //就访问他;
if(visited[w] == false)
{
cout << Nodetable[w].data <<" ";
visited[w] = true;
Q.push(w);//访问完之后就让他如队列;为了后面搜索他的领结顶点;
}
w = GetNextNeighbor(Nodetable[v].data,Nodetable[w].data);//找顶点loc的下一个领结顶点
//重复检测v的所有邻接顶点
}
}
}
private:
VerticeList<Type> *Nodetable;
int numVertices;
int numEdges;
int MaxVertices;
int MaxEdges;
};
#endif
测试代码:
/*
*/
#if 1
#include"GRPHMTX.h"
void main()
{
GraphLink<char> gm;
gm.InsertVertices('A');
gm.InsertVertices('B');
gm.InsertVertices('C');
gm.InsertVertices('D');
gm.InsertVertices('E');
gm.InsertVertices('F');
gm.InsertVertices('G');
gm.InsertVertices('H');
gm.InsertVertices('I');
gm.InsertEdge('A','B');
gm.InsertEdge('A','D');
gm.InsertEdge('A','C');
gm.InsertEdge('B','C');
gm.InsertEdge('B','E');
gm.InsertEdge('E','G');
gm.InsertEdge('C','F');
gm.InsertEdge('D','F');
gm.InsertEdge('F','H');
gm.InsertEdge('E','H');
gm.InsertEdge('H','I');
gm.ShownGrap();
cout << "--------------------"<<endl;
//gm.BFS();
//cout <<gm.GetFirstNeigbor('A')<<endl;
//cout <<gm.GetNextNeighbor('A','D');
//gm.RemoveEdge('A','B');
gm.RemoveVertice('C');
//gm.ShownGrap();
}
#endif
/*
容器,算法,迭代器是STL的三大组件;
*/
最小生成树:
为什么要有最小生成树?
图的最小生成树是一个最极大无环的子通,在实际生产生活中,列如:在规划n个城市之间的网络通信,至少要搭建n-1线路,那么如何使这n-1条线路的造价最低呢?我们在保证每个城市之间能够通信的基础上,造价最低。一个图的最小生成有很多种,我们就要找出权值之和最小的那颗最小生成树,有Kruskal算法和Prim算法;
首先来讲Kruskal算法;
他是这样的,首先构造出一个只有n个头,无边的一个图T,然后在边结构中找出最小的一条边,并且边得俩个头来自不同的连通分量,就将这条边加入到T,(或者说新边的加入不会让原来的T里面产生环路),否则将其舍弃掉,然后一直重复找边直到所有的顶点都在一个连通分量上。每次迭代时,选出一条最小的边,且俩个端点不在同一个连通图上。
代码如下所示;在找最小边的时候我用的是先把所有的边都构造出来,然后对他进行排序,那么排完序每次取出来的就是最小的边;
public:
typedef struct Edges
{
int x;
int y;
E cost;
}Edges;
int cmp(const void *a,const void *b)
{
return (*(Edge*)a).cost - (*(Edge*)b).cost;
}
bool Is_same( int *father,int i,int j)//判断边的俩个端点是否来自同一个连接图;
//因为我把father数组最终初始化为:father[i] =i;
//意思是自己的头是自己;
{
while(father[i] != i) //他的头不是他自己
{
i = father[i]; //查看他的头的头是哪个;//最终会以father[i] = i;结束
}
while(father[j] != j)
{
j = father[j];
}
if( i== j)
return true;
else
return false;
}
void Mask_same(int *father,int i,int j) //将俩个刚建立连接的头标记为是连通的;
{
while(father[i] != i)
{
i = father[i];
}
while(father[j] != j)
{
j = father[j];
}
father[j] = i;
}
void MinTreeKruskal()
{
int n = numVertices;
Edges *edge = (Edges *)malloc(sizeof(Edges)*(n*(n-1)/2));
assert(edge != NULL);
int k = 0;//初始化edge[k].x 和 edge[k].j
for(int i = 0;i < numVertices ;++i)
{
for(int j = i;j<numVertices;j++)
{
if(Edge[i][j] != 0 && Edge[i][j] != Max_cost)
{ //从这里就可以看出,小数是大数的父结点
edge[k].x = i;
edge[k].y = j;
edge[k].cost = Edge[i][j];
k++;
}
}
}
for(int j = 0;j<k;++j)
{
cout << VerticesList[edge[j].x ]<< "-->"<< VerticesList[edge[j].y ]<<"-->"<<edge[j].cost<<endl;
}//排好之后第一条边就是权值最小的边;
//qsort(edge,k,sizeof(Edges),cmp);
printf("----------------------------\n");
for(int i =0; i<k ; ++i)
{
Edges tmp;
for(int j = i+1; j<k ; ++j)
{
if(edge[i].cost>edge[j].cost)
{
tmp = edge[j];
edge[j] = edge[i];
edge[i] = tmp;
}
}
}
int *father = (int *)malloc(sizeof(int)*numVertices);
for(int i = 0;i<numVertices;i++)
{
father[i] = i;
}
for(int i = 0;i< k;++i)
{ //起点 ,//终点
if(!Is_same(father,edge[i].x,edge[i].y))
{
int v1 = edge[i].x;
int v2 = edge[i].y;
cout << VerticesList[v1] << "-->"<<VerticesList[v2] <<"-->"<<edge[i].cost<<endl;
Mask_same(father,edge[i].x,edge[i].y);
}
}
}
(二)最小生成树的第二种方法Prim算法,他和Kruskal算法完全不一样;
Prim算法借助来个数组lowcost[i] = value,mst[i] = begin;lowcost[i] = value,是指i这个头的最小代价value,mst[i] = begin,使得i得到最小代价的起点是begin这个头;也就是说begin->i的边的权值是value;
Prim算法的思想是:先从图结构中任意取一点v1作为开始的头,然后计算剩余的头结点vi到这个v1的权值,计算出来以后找出权值最小的边,记下他的头结点vi',并且使lowcast[vi'] = 0;(这是他被并入集合的标志),然后先计算剩余的头结点vi到vi'的权值,如果这个新计算的权值比原来的权值小,那么修改这个头的权值lowcost[i] = value',并且修改他的起点不在是原来的begin = v1,而是现在的头vi';然后进入循环继续重复刚才的步骤,直到把n-1条边都查找完毕;
代码后后面补上;