图的BFS,DFS算法

图的存储(领接矩阵,领接表法)

领接表是在优化表的存储方法,当采用领接矩阵的时候,当要查看“表有多上条边“,“图是否是连通”的时候,必须要对对角线以外的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条边都查找完毕;

代码后后面补上;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值