数据结构之图的遍历【1】

5 篇文章 0 订阅
3 篇文章 0 订阅

前言:

图和树一样都是一种非线性结构。和前面的另外两种数据结构线性表和树相比,图是一种更加灵活,更加多变(可以具有的统一性质更少了)的数据结构。线性表是一对一的线性结构,树是一对多的非线性结构,而图则是任意数据节点间,多对多的非线性结构,它的每一个节点都可以与其他任意多个节点相关联。灵活性使得图可以用来描述、求解我们实际生活中的问题,同时也使得我们使用它的难度加大了。


一、图的存储表示方法

1. 以邻接矩阵为存储结构

struct information                    //边的附带信息
{
	int r;
	int info;
};
struct matrix                        //邻接矩阵储存图
{
	int vexnum;     //顶点数
	int arcnum;     //边数
	char* vexs;   //节点数组
	information** acrs;  //矩阵
};

从中可以看出,用邻接矩阵来储存图的话,主要的空间耗费在于储存节点连接信息的矩阵上,如果是比较稀疏的图(即边数较少),或者是无向图(边没有方向)的话,那么这种存储方式无疑会带来极大的浪费。


2. 以邻接表为存储结构

struct edgenode
{
	int num;
	char nodedata;
	edgenode* next;
};
struct vnode
{
	char  nodedata;
	edgenode* first;
};

从中可以看出,相较于邻接矩阵,用邻接表来存储图的话,有效的减少了对空间的浪费。实际上,这里邻接表的方法就是把邻接矩阵中的每一行,分别组织为一个单链表,由此去除了没有用到的空间占用。


二、建立 graph 类并初始化

class graph
{
public:
	matrix realgraph;         //邻接矩阵
	vnode* adjlist;         //邻接表
	int** adj;      //输入边信息的矩阵
	int n, e;         //n为节点数,e为边数
	void create_matrix();
	void creat_list();        //建立一个邻接表储存矩阵的图
	int getnumber(char word);
	void DFSvisit();
	void DFS(matrix g, int v);
	int getFirstNeighbor(matrix g, int v);
	int getNextNeighbor(matrix g, int v, int w);
	void BFSvisit();
	void BFS(matrix g, int iTemp);
private:
	bool* visited;          //邻接矩阵节点遍历判断
	bool* visited2;         //邻接表节点遍历判断
	queue<int>  qMatrixBfs;          //队列 用于邻接矩阵广度优先遍历
	queue<int>  qListBfs;          //队列 用于邻接表广度优先遍历
};

graph::graph()
{
	cout << "请分别输入图的顶点数和边数" << endl;
	cin >> n >> e;
	realgraph.vexnum = n;
	realgraph.arcnum = e;
	realgraph.vexs = new char[n];
	realgraph.acrs = new information*[n];
	for (int i = 0; i <= n - 1; i++)
	{
		realgraph.acrs[i] = new information[n];
	}

	adjlist = new vnode[n];
	visited = new bool[n];
	visited2 = new bool[n];
	for (int i = 0; i <= n - 1; i++)
	{
		visited[i] = false;
		visited2[i] = false;
	}

	adj = new int*[e];
	for (int i = 0; i <= e - 1; i++)
	{
		adj[i] = new int[2];
	}
}


三、邻接矩阵的深度优先和广度优先遍历

1. 初始化邻接矩阵

void graph::create_matrix()
{
	int i = 0;
	int j = 0;
	char** a = new char*[e];
	for (int i = 0; i <= e - 1; i++)
	{
		a[i] = new char[2];
	}
	cout << "请输入" << n << "个字符用做图的顶点(邻接矩阵)" << endl;
	for (i = 0; i <= realgraph.vexnum - 1; i++)
	{
		cin >> realgraph.vexs[i];
	}

	for (i = 0; i < realgraph.vexnum; i++)       //初始化矩阵为所有顶点都没有边
	{
		for (j = 0; j < realgraph.vexnum; j++)
		{
			realgraph.acrs[i][j].r= 0;
		}
	}

	cout << "请依次输入有边相连的两个顶点序号(如:a b)" << endl;
	for (i = 0; i < realgraph.arcnum; i++)
	{
		cin >> a[i][0] >> a[i][1];
		adj[i][0] = getnumber(a[i][0]);
		adj[i][1] = getnumber(a[i][1]);
	}

	for (int i = 0; i <= realgraph.arcnum - 1; i++)
	{
		realgraph.acrs[adj[i][0]][adj[i][1]].r = 1;
		/*realgraph.acrs[adj[i][1]][adj[i][0]].r = 1;*/
	}
}
int graph::getnumber(char word)
{
	for (int i = 0; i <= realgraph.vexnum - 1; i++)
	{
		if (word == realgraph.vexs[i])
			return i;
	}
	cout << "请输入正确符号!" << endl;
	return 100;
}
getnumber( ) 函数用于传入一个字符,返回该字符在节点数组中的位置。通过这种方法,获得一条边的两个端点的顺序值,由此建立一个矩阵。

2. 邻接矩阵深度优先遍历

void graph::DFSvisit()
{
	int i;
	for (i = 0; i < realgraph.vexnum; i++)
	{
		if (visited[i] == false)
		{
			DFS(realgraph, i);
		}
	}
}

void graph::DFS(matrix g, int v)   //递归函数体
{
	int i;
	int w;
	cout << g.vexs[v] << " ";
	visited[v] = true;
	w = getFirstNeighbor(g, v);     //v的邻接节点w
	while (w>=0)
	{
		if (visited[w] == false)   
		{  //如果w还没有被遍历过,那么递归遍历w及其邻接节点,如果已经遍历过,那么遍历w之后的邻接点
			
			DFS(g, w);

		}   //如果此邻接点已被遍历,那么就从它的下一个邻接点开始遍历                         
		    

		w = getNextNeighbor(g, v, w);  //获得当前节点v的邻接点w之后的一个邻接点
	}
}

int graph::getFirstNeighbor(matrix g, int v)
{
	for (int i = 0; i < g.vexnum; i++)
	{
		if (g.acrs[v][i].r != 0 && v != i)  //排除邻接矩阵中对角线上的点
		{
			return i;
		}
	}
	return -1;
}
int graph::getNextNeighbor(matrix g, int v, int w)
{
	for (int i = w + 1; i < g.vexnum; i++)
	{
		if (g.acrs[v][i].r != 0 && v != i)
		{
			return i;
		}
	}
	return -1;
}

深度优先遍历邻接矩阵的方法其实很简单,就是对于每一个节点(即矩阵的每一行),查看它的第一个相邻的节点是什么(有边即为相邻),对此相邻节点再查看它的第一个相邻的节点,利用递归函数重复此过程。若是当前结点的第一个相邻节点已经遍历过了,那么就查看它的下一个相邻节点,然后再调用递归函数,就和上面的步骤是一样的了。


3.邻接矩阵广度优先遍历

void graph::BFSvisit()
{
	int i;
	for (i = 0; i < realgraph.vexnum; i++)
	{
		visited[i] = false;
	}
	for (i = 0; i < realgraph.vexnum; i++)       //对所有没有遍历到的节点调用BFS函数遍历
	{
		if (visited[i] == false)
		{
			BFS(realgraph, i);
		}
	}
}

void graph::BFS(matrix g, int iTemp)
{
	int i;
	cout << g.vexs[iTemp];             //遍历输出当前节点
	visited[iTemp] = true;
	qMatrixBfs.push(iTemp);
	while (!qMatrixBfs.empty())          //循环直至队列为空
	{
		iTemp = qMatrixBfs.front();       //获取队列的当前头结点
		qMatrixBfs.pop();
		for (i = 0; i < g.vexnum; i++)    
		{
		   //然后遍历此节点的所有邻接节点并将它们加入队列当中,重复此步骤

			if (g.acrs[iTemp][i].r != 0 && iTemp != i && visited[i] == false)
			{
				cout << g.vexs[i]<<" ";
				visited[i] = true;
				qMatrixBfs.push(i);
			}
		}
	} 
}

广度优先遍历比深度优先遍历还要简单。主要的操作对象是一个队列,这里我没有自己建立一个队列结构。主要的思想就是将当前节点的所有相邻节点都遍历一次,且遍历的同时将它们依次放入队列之中。然后再弹出队列的队首,对它重复上面的操作,直至队列为空。不过要注意的是,这样遍历完的只是联通的节点,对于一些孤立的点没有办法遍历到,所以BFSvisit( )函数中的循环不能少。


四、邻接表的深度优先和广度优先遍历

1.初始化邻接表

void graph::creat_list()
{
	int i;
	cout << "请输入" << n << "个字符用做图的顶点(邻接表)" << endl;
	for (i = 0; i < n; i++)
	{
		cin >> adjlist[i].nodedata;
		adjlist[i].first = NULL;
	}

	edgenode* edgTempLeft;
	edgenode* edgTempRight;
	int iTempLeft, iTempRight;
	for (i = 0; i < e; i++)
	{
		edgTempLeft = new edgenode;
		edgTempRight = new edgenode;

		iTempLeft = adj[i][0];
		iTempRight = adj[i][1];   //获得边的两个端点

		//其实就是根据输入的边(如ab),新建一个节点b,然后将它插入到a的右边的链表中(插入到头部)

		edgTempRight->nodedata = adjlist[iTempRight].nodedata;  //如果是有向图则只用这部分即可
		edgTempRight->num = iTempRight;
		edgTempRight->next = adjlist[iTempLeft].first;
		adjlist[iTempLeft].first = edgTempRight;

		edgTempLeft->nodedata = adjlist[iTempLeft].nodedata;  //如果是无向图则需要将相反的方向也链接起来
		edgTempLeft->num = iTempLeft;
		edgTempLeft->next = adjlist[iTempRight].first;
		adjlist[iTempRight].first = edgTempLeft;
	}
}

注释的部分就是邻接表的主要思想,或者说原理


2.邻接表的深度优先遍历

void graph::DFS_list(vnode* adjlist, int v)
{
	cout << adjlist[v].nodedata << " ";     //完成对当前节点的遍历
	visited2[v] = true;

	edgenode* edgTemp;             //寻找与当前节点相连的第一个节点,即链表的首节点
	edgTemp = adjlist[v].first;
	while (edgTemp!=NULL)             //如果有相连的节点且没有遍历过的话则递归遍历
	{
		if (visited2[edgTemp->num] == false)
		{
			DFS_list(adjlist, edgTemp->num);
		}
		edgTemp = edgTemp->next;    //没有就选择下一个相连的节点,继续循环递归直至所有相连的节点都遍历过了
	}
 }

3.邻接表的广度优先遍历

void graph::BFS_list(vnode* adjlist, int v)
{
	int i;
	edgenode* edgTemp ;          //临时节点

	for (i = 0; i <= n - 1; i++)
		visited2[i] = false;

	cout << adjlist[v].nodedata << " ";    //完成对当前节点的遍历
	visited2[v] = true;
	qListBfs.push(v);
	while (qListBfs.empty != true)  //循环直至所有的节点都遍历了
	{
		edgTemp = adjlist[qListBfs.front()].first; //弹出队首,遍历所有和它相连且没有遍历过的节点
		qListBfs.pop();
		while (edgTemp != NULL)
		{
			if (visited2[edgTemp->num] == false)  
			{
				cout << edgTemp->nodedata << " "; //完成一次遍历,并且将之加入队列当中
				qListBfs.push(edgTemp->num);
				visited2[edgTemp->num] = true;
			}
			edgTemp = edgTemp->next; //下一个节点
		}
	}
}

可以看出邻接表的深度优先和广度优先遍历的实现思路,都和邻接矩阵的基本一样。


五、总结

       以上就是邻接矩阵、邻接表的初始化和两种主要的遍历方法,两者的深度优先和广度优先遍历都十分的相似,个人觉得这是因为邻接表和邻接矩阵的本质其实是相同的。

       另外,对于队列结构和递归函数的使用方法,有一些可以总结的地方,个人感觉队列和栈一样往往用来保存某种状态(?)或者说路径点(?),将外循环的跳出条件设置为队列或栈为空(以及其他相应条件),然后在内循环中进行相应的入队、入栈操作,最后将出队、出栈操作放置在两层循环的中间,而各类判定语句(if)就按需要设置,感觉是遍历非线性数据结构的一个思路吧,大概。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值