文章目录
- 用list来表示图,判断是否存在环
- 邻接表实现拓扑排序
- 用DFS(邻接矩阵) 来实现拓扑排序。
- 判断无向图顶点是否全部连通
- 判断图G中从顶点u到v是否存在简单路径
- 输出图G中从顶点u到v的所有简单路径
- 判断无向图是否存在经过顶点v的环
- 输出所有无向图中经过顶点v的环
用list来表示图,判断是否存在环
定义class Graphic,成员变量和方法如下:
class Graphic{
public:
Graphic(int vertex){
this->vertexNum = vertex;
adjacents = new list<int>[this->vertexNum]; //构造图
}
int getVertexNum(){
return this->vertexNum;//获得顶点数
}
void setVertexNum(int number)
{
//修改顶点数
this->vertexNum = number;
}
void addEdge(int start,int end)
{
this->adjacents[start].push_back(end);
}
void countOutdegree(Graphic*g, int *outdegree);//计算所有顶点的出度
bool isDAG(Graphic *g);//判断是否为有向无环图
bool deleteVertex(int v)
{
list<int>*p = &adjacents[v];//取出该list的顶点v的地址,获取指定点的出度链表
p->clear();
delete p;//删除指定的顶点及其出度边
//需对原来的图结构进行修改
for(int i = v;i < vertexNum -1;i++)
adjacents[v] = adjacents[v+1];//相当于全部元素向前移,因为删除了一个
、this->vertexNum--;//顶点数也要修改
list<int>* newadj = new list[this->vertexNum];
for(int i = 0;i < this->vertexNum;i++)
newadj[i] = adjacents[i];//将所有的原list中的元素赋值到一个临时new的list中
list<int>*q = newadj;
this->adjacents = newadj;
delete q;//删除q
}
private:
int vertexNum; //顶点数
list<int> *adjacents; //邻接表存储边关系 注意它的类型,是一个list类型中存储了很多个指针
};
void Graphic::countOutdegree(Graphic *g, int *indegree){
int total = g->getVertexNum();
int v;
list<int>::iterator it;
for(v = 0;v < total;v++)
{
int number = 0;
for(it =adjacents[v].begin(); it != adjacents[v].end() ;it++)
{
number++;
}
indegree[v] = number;
}
}
bool Graphic::isDAG(Graphic *graphic){
int total = graphic->getVertexNum();
int* outdegree = new int[total];
for(int i = 0;i < total;i++)
outdegree[i] = 0;//初始化数组
Graphic::countOutdegree(graphic,outdegree);
bool ifHas = false;//标志,判断是否存在出度为0的点
list<int>zeroOutDegree;//存储所有出度为0的点
for(int i = 0;i < total;i++)
{
if(outdegree[i] == 0)
{
zeroOutDegree.push_back(outdegree[i]);
ifHas = true;
}
}
if(ifHas)//如果有出度为0的边
{
graphic->deleteVertex(zeroOutDegree.front());
if(graphic->getVertexNum() > 0)
{
isDAG(graphic);//递归入口
}
else
return true;//递归判断该图 每次都删除出度为0的点和出度边
}
else return false;//如果ifHas为False表示没有出度为0的点
}
邻接表实现拓扑排序
判断有向无环图是进行拓扑排序的第一步,注意只有确定一个图是一个有向无环图,我们才有可能对它进行拓扑排序成功。拓扑排序说到底就是我们要将所有的点排列得满足每一条边的规则,边的发出点必须在边的接受点的前面,所以根据这个原则我们可以像下面这样来思考拓扑排序的问题:
找出排在第一或是最后的点,如果是排在第一的点就说明没有边指向它,也就是它的入度为0,如果是最后一个点,说明它没有边指向任何一个其他的点,那么它的出度就应该为0;这两种方式在本质上是一样的,为了方便,我们这里以找最后
一个点为例,
如果找到了最后一个点,我们就将它存储在一个栈中,栈的大小为图的顶点数。
找到最后一个点之后,我们将这个点删除,因为在后面的拓扑排序中这个点已经不起作用,其他所有的顶点都可以在它的前面(左边),所以我们删除这个顶点及所有指向它的边,不仅可以缩小图的规模还不影响排序的结果。
重复执行步骤2,直到图中没有顶点。
将栈中的点依次出栈,完成拓扑排序。
从上面的算法描述可以看出,拓扑排序具有很强的递归性质,其具体执行的方法与我们前面所描述的判定有向无环图的算法类似。这是理所当然的,因为有向无环图的判定就是拓扑排序的基础和前提。所以我们可以试着对前面判定有向无环图的算法进行修改,就可以得到拓扑排序的算法(这里我们的图使用邻接链表表示的,用邻接矩阵结构的情况类似)
void Graphic::topologySort(Graphic *graphic){
int flag = 1; //图中无环的标志
stack<int> s; //设置存储拓扑排序结果的栈
queue<int> q; //存储出度为0 的顶点的队列
int vertexes = graphic->getVertexNum(); //获取图的顶点数
int *outDegree = new int[vertexes]; //存储各顶点的出度
for (int i = 0;i< vertexes;i++)
{
outDegree[i] = 0;
}
list<int>::iterator iters;
for (int j = 0;j<vertexes;j++)
{
int outEdgeNumber = 0;
for (iters = graphic->adjacents[j].begin();iters != graphic->
adjacents[j].end();iters++)
{
outEdgeNumber++; //计算图中各顶点的出度
}
outDegree[j] = outEdgeNumber;
}
for (int i = 0;i < vertexes; i++)
{
if (outDegree[i] == 0)
{
q.push(i); //将出度为0 的顶点入队
}
}
for (int v = 0;v < vertexes;v++)
{
if (q.empty()) //如果队列为空则图中有环
{
flag = 0;
break;
}
int vertex = q.front(); //队列中首元素出队
s.push(vertex); //将最后元素入栈
q.pop();
outDegree[vertex] = -1; //标志该顶点出度为-1,表示已访问过
for (int i = 0;i<vertexes;i++)
{
for (iters = graphic->adjacents[i].begin();iters != graphic->
adjacents[i].end();iters++)
{
if (*iters == vertex)
{
outDegree[*iters]--;//将所有有边指向该结点的顶点的出度减1
if (outDegree[*iters]==0)
{
q.push(*iters);//如果有新的出度为0 的点,则将该点入队
}
}
}
}
}
if (flag)
{
while(!s.empty())
{
cout<<s.top()<<" "; //输出栈内元素,得到拓扑排序结果
s.pop();
}
}else{
cout<<"该图有环不能进行拓扑排序!"<<endl;
}
}
【(注:这是原答案的代码,但我觉得有问题,outDegree[*iters]–;//将所有有边指向该结点的顶点的出度减1
if (outDegree[*iters]==0)
{
q.push(*iters);//如果有新的出度为0 的点,则将该点入队
}这句话中我觉得并不是q.push(*iters);而是q.push(i)
)】
从上面的程序可以看出,拓扑排序的时间复杂度其实就是一次图的遍历的时间复杂度,如果图是邻接矩阵表示的话,复杂度为O(v2),如果图使用邻接表结构的话,其时间复杂度则为O(V+E),V 表示定点数,E 表示邻接链表的最大长度
用DFS(邻接矩阵) 来实现拓扑排序。
问:图的拓扑排序具有多种结果,其实现方法也有多种,请利用图的深度遍历算法DFS 来实现拓扑排序。
DFS 算法从起始顶点开始优先找到图中最深的点,直到不能再深入为止,然后离开该点寻找下一个最深点。这句话我们换个说法就是,DFS 会沿着有向边一直走,直到某个结点没有指向别的顶点的边的点为止。没有指向别的顶点的边的点就是出度为0 的点,这正是我们拓扑排序要寻找的最后点。这样我们就将DFS 算法与拓扑排序算法紧密联系起来了
写法一:
/*************************利用DFS 进行拓扑排序*************/
const int maxVertex = 20;
bool adj[maxVertex][maxVertex]; //边的邻接矩阵
int visited[maxVertex]; //记录DFS 遍历过的点
int sorted[maxVertex]; //记录拓扑排序的顺序
int n;
bool cycle = false; //DFS 过程中是否有环
void Dfs(int v)
{
if(visited[v] == 1) cycle = true;//如果v已经访问过,说明进行了二次访问,图中有环
if(visited[v] == 2) return;//如果visited[v]为2,说明此顶点没有出度,是拓扑排序的终点
visited[v] = 1;
for (int i=0; i<9; i++)
if (adj[v][i])
Dfs(i);
visited[v] =2;
sorted[n--] = v;//记录排序后的顶点顺序
}
void topologyByDFS()
{
//初始化
for (int i=0; i<maxVertex; i++) visited[i] = 0;
cycle = false;
n = maxVertex-1;
//进行DFS
for (int s=0; s<9; ++s)
if (visited[s] == 0)
DFS(s);
//输出排序结果
if (cycle)
cout << "图上有环";
else{
for (int i=0; i<9; ++i)
cout << sorted[i]<<" ";
}
}
写法二:
typedef struct anode {
int adjvex;//该边的邻接点编号
struct anode* nexarc;//指向下一条边的指针
int weight;//该边的相关信息,比如权值
}arcnode;//边结点类型
typedef struct vnode {
//InfoTyoe info; 顶点的其他信息
arcnode* firstarc;//指向第一个边结点
}Vnode;//邻接表头结点类型
typedef struct {
vnode adjlist[10000];//邻接表头结点数组
int n, e;//图中顶点数n和边数e
}adjgraph;//完整的图邻接表类型
int visited[maxn];
vector<int>seq;//存放逆拓扑序列
void Dfs(adjgraph*g,int u)//从顶点u除非深度优先遍历
{
visited[u] = 1;
arcnode*p,int w;
p = g->adjlist[u].firstarc;//p指向u的第一个邻接点
while(p!=NULL)
{
if(!visited[p->adjvex]) //没有访问过,从这个点开始深度搜索
Dfs(g,p->adjvex];//从这里递归
p = p->nextarc;//指向下一个邻接点
}
seq.push_back(u);//将u放入序列中
}
void TopSort(adjgraph* g)
{
for(int i = 0;i < g->n;i++)
{
//输出有向无环图的拓扑序列
if(visited[i]) continue;
Dfs(g,i);//开始递归
}
cout<<"拓扑序列:";
for(int i = seq.size()-1;i>=0;i--)
cout<<seq[i];//第一个放入序列的是拓扑排序的最后一个点,所以逆序输出
}
判断无向图顶点是否全部连通
class Graphic{
public:
Graphic(int vertex){
this->vertexNum = vertex;
adjacents = new list<int>[this->vertexNum]; //构造图
}
int getVertexNum(){
return this->vertexNum;//获得顶点数
}
void setVertexNum(int number)
{
//修改顶点数
this->vertexNum = number;
}
void countOutdegree(Graphic*g, int *outdegree);//计算所有顶点的出度
bool isDAG(Graphic *g);//判断是否为有向无环图
bool deleteVertex(int v)
{
list<int>*p = &adjacents[v];//取出该list的顶点v的地址,获取指定点的出度链表
p->clear();
delete p;//删除指定的顶点及其出度边
//需对原来的图结构进行修改
for(int i = v;i < vertexNum -1;i++)
adjacents[v] = adjacents[v+1];//相当于全部元素向前移,因为删除了一个
、this->vertexNum--;//顶点数也要修改
list<int>* newadj = new list[this->vertexNum];
for(int i = 0;i < this->vertexNum;i++)
newadj[i] = adjacents[i];//将所有的原list中的元素赋值到一个临时new的list中
list<int>*q = newadj;
this->adjacents = newadj;
delete q;//删除q
}
private:
int vertexNum; //顶点数
list<int> *adjacents; //邻接表存储边关系 注意它的类型,是一个list类型中存储了很多个指针
};
void Graphic::countOutdegree(Graphic *g, int *indegree){
int total = g->getVertexNum();
int v;
list<int>::iterator it;
for(v = 0;v < total;v++)
{
int number = 0;
for(it =adjacents[v].begin(); it != adjacents[v].end() ;it++)
{
number++;
}
indegree[v] = number;
}
}
bool Graphic::isDAG(Graphic *graphic){
int total = graphic->getVertexNum();
int* outdegree = new int[total];
for(int i = 0;i < total;i++)
outdegree[i] = 0;//初始化数组
Graphic::countOutdegree(graphic,outdegree);
bool ifHas = false;//标志,判断是否存在出度为0的点
list<int>zeroOutDegree;//存储所有出度为0的点
for(int i = 0;i < total;i++)
{
if(outdegree[i] == 0)
{
zeroOutDegree.push_back(outdegree[i]);
ifHas = true;
}
}
if(ifHas)//如果有出度为0的边
{
graphic->deleteVertex(zeroOutDegree.front());
if(graphic->getVertexNum() > 0)
{
isDAG(graphic);//递归入口
}
else
return true;//递归判断该图 每次都删除出度为0的点和出度边
}
else return false;//如果ifHas为False表示没有出度为0的点
}
判断图G中从顶点u到v是否存在简单路径
假设图G采用邻接表存储,设计算法判断图G中从顶点u到v是否存在简单路径(深度优先遍历的方法,如果遍历到v说明存在简单路径)
关键:
1.遍历所有结点
2.要注意标记是否已经遍历过
3.递归调用,当从u->v经过w时,改u为w,继续递归调用,若w==v,说明存在从u出发经过v的环
4.先是判断对于某一个结点,是否存在从它出发的环,然后判断整个图
5.注意在判断图G时,从i=0开始,cycle(G,v,v,-1);即从v到v且路径长度大于1
//在用邻接表来表示图时,图中的每个顶点v都对应一个单链表,每个单链表有一个头结点 所有这些头结点构成头结点数组
typedef struct anode {
int adjvex;//该边的邻接点编号
struct anode* nexarc;//指向下一条边的指针
int weight;//该边的相关信息,比如权值
}arcnode;//边结点类型
typedef struct vnode {
//InfoTyoe info; 顶点的其他信息
arcnode* firstarc;//指向第一个边结点
}Vnode;//邻接表头结点类型
typedef struct {
vnode adjlist[10000];//邻接表头结点数组
int n, e;//图中顶点数n和边数e
}adjgraph;//完整的图邻接表类型
bool visited[maxn]={false};
bool exitpath(adjgraph*G,int u,int v)
{
bool flag;
int w;
arcnode* p;
visited[u] = 1;//设置为已经访问
if(u == v) return true;//找到了一条路径
p = G->adjlist[u].firstarc;//p指向顶点u的第一个邻接点
while(p != NULL)
{
w = p->adjvex;
if(!visited(w))
{//如果顶点w未被访问,递归访问它
flag = exitpath(G,w,v);
if(flag) return true;
}
p = p->nexarc;//p指向顶点u的下一个邻接点
}
}
输出图G中从顶点u到v的所有简单路径
假设图G采用邻接表存储,设计算法输出图G中从顶点u到v的所有简单路径
利用回溯的深度优先搜索,在遍历过程中每个顶点只访问一次,所以这条路径一定是一条简单路径。当从顶点u出发遍历时先将visited[u]置1,加入到path向量中,找到一条路径后从终点v回退继续寻找其他路径,允许曾经访问过的顶点出现在另外的路径中
bool visted[maxn]={false};
void findpath(adjgraph*G,int u,int v,vector<int>path)
{
int w,i;
arcnode* p;
visited[u]=true;//设置为已经访问
path.push_back(u);
if(u == v && path.size()>0)
{
for(i = 0;i < path.size();i++)//输出一条路径
cout<<path[i];
cout<<"\n";
}
p = G->adjlist[u].firstarc;//p指向u的第一个邻接点
while(p!= NULL)
{
w = p->adjvex;
if(visited[w]==false)
{
findpath(G,w,v,path);
}
p = p->nextarc;
}
visited[u]=false;//恢复环境,使得u可用
}
如果要求 输出从u到v长度为m的路径,则增加判断path.size==m+1
判断无向图是否存在经过顶点v的环
从v出发深度优先搜索,用d记录经过的路径长度(初始值d=-1),如果搜索到顶点v且d>1表示从v到v有一个长度大于1的路径
bool visited[maxn]={false};
bool cycle(adjgraph*G,int u,int v,int d)
{
visited[u]=true;
bool flag;
arcnode*p;
p=G->adjlist[u].firstarc;
d++;
int w;
while(p!=NULL)
{
w = p->adjvex;
if(visited[w]==false)//如果顶点没有被访问,递归访问它
{
flag = cycle(G,w,v,d);//从顶点w出发搜索
if(flag)return true;
}
else if(w == v && d>1)//搜索到顶点v且环的长度大于1
return true;
p = p->nextarc;
}
return false;
}
bool hascycle(adjgraph*G,int v)
{
return cycle(G,v,v,-1);//从顶点v出发搜索
}
若改为:
输出所有无向图中经过顶点v的环
v出发深度优先搜索,用path向量记录经过的路径长度,如搜到一条路径,将path添加到allpath中,将visited[u]恢复为false
最后输出allpath中所有的环
bool visited[maxn]={false};
vector<vector<int>>&allpath;//全局向量的向量 存放所有环
void allcycle(adjgraph* G,int u,int v,vector<int>path)
{
int w;
arcnode* p;
visited[u]=true;
path.push_back(u);
p = G->adjlist[u].firstarc;
while(p!=NULL)
{
w = p->adjvex;
if(visited[w]==false )//若w未被访问 ,递归访问它 从w出发搜索
{
allcycle(G,w,v,path);
}
else if(w ==v && path.size()>2)
allpath.push_back(path);//从v到v之间找到一个环且长度大于2
p = p->nextadj;
}
visited[u]=false;
}
void displaypath(adjgraph*G,int v)
{
vector<int>path;
vector<vector<int>>allpath;
allcycle(G,v,v,path);
for(int i = 0;i < allpath.size();i++)
{
for(int j = 0;j < allpath[i].size();j++)
cout<<allpath[i][j];
cout<<"\n";
}