图的深度、广度优先遍历和最小生成树算法复习(邻接矩阵)

一、图的深度优先遍历

原则

一直向深处遍历,直到到头就返回到上一节点继续向深处遍历。

举例

基本原理

选定起点后,打印起点,for循环在邻接矩阵里暴力查找边存在并且终点还未被走到的边,用终点作为下次递归的起点。

代码实现

​
void _DFS(int srci, const vector<bool>& visited)
{
    cout << srci << "->" << _vertexs[srci] << endl;
    visited[srci] = true;
    for(int i = 0; i < _vertexs.size(); i++)
    {
        if(_matrix[srci][i] != INT_MAX && visited[i] == false)
        {
            _DFS(i, visited);
        }
    }
}


void DFS(const V& src)
{
    int srci = GetIndex(src);
    // 记录走过的顶点
    vector<bool> visited(_vertexs.size(), false);
    _DFS(srci, visited);
}

​

二、图的广度优先遍历

原则

找到起点的一度顶点,二度顶点,三度顶点······

举例

基本原理

先入队起点,若队列不为空进行循环,循环中先拿到队头顶点,然后打印出队,再用for循环在邻接矩阵中找到以队头顶点为起点的边,要求边的终点未遍历到,将终点入队。此时我们就拿到了对头顶点的一度顶点。直至循环结束即可。

代码实现

void BFS(const V& src)
{
    int n = _vertexs.size();
    int srci = GetIndex(src);
    queue<int> q;
    q.push(srci);
    vector<bool> visited(n, false);
    visited[srci] = true;
    while(!q.empty())
    {
        int front = q.front();
        cout << front << "->" << _vertexs[front] << endl;
        q.pop();
        for(int i = 0; i < n; i++)
        {
            if(_matrix[front][i] != INT_MAX && visited[i] == false)
            {
                q,push(i);
                visited[i] = true;
            }
        }
    }
}

三、广度优先遍历进阶

如何实现一次打印一层节点?

如上图我们是否能实现:

A

B C D 

E F

G H

I

基本原理

我们可以用levelsize来记录每一层有几个节点,出队时可以循环levelsize次。

代码实现:
 

void BFSLevel(cinst V& src)
{
    int front;
    int n = _vertexs.size();
    int srci = GetIndex(src);
    int levelsize = 1;
    vector<bool> visited(n, false);
    queue<int> q;
    q.push(srci);
    visited[srci] = true;
    while(!q.empty())
    {
        while(levelsize)
        {
            front = q.front();
            q.pop();
            // 出队时要减一
            levelsize--;
            cout << front << "->" << _vertexs[front] << " ";
        }
        cout <<endl;
        for (int i = 0; i < n; i++)
		{
			if (_matrix[front][i] != INT_MAX)
			{
				if (visited[i] == false)
				{
					q.push(i);
					visited[i] = true;
					levelsize++;
				}
			}
		}
    }
}

四、最小生成树算法

1、Kruskal算法(一种从全局考虑的算法)

原则

每次都挑最小的边进行插入,但是不能产生环,因为一旦有环那就不是用最少的边把n个顶点连接起来,就不是生成树.。

基本原理

只要我们每次都挑的是全部边中权值最小并且不会构成环的边,那循环结束挑到的n-1条边就是我们需要的。

注意几个问题

1、如何判断一条边加进去之后是否会变成环

我们可以用并查集来做检查。每次加边进去的时候我们就把起点终点并到一个集合中,当我们准备加边之前可以先判断起点终点是否在一个集合中。

2、如何高效的取到最小的边

可以借助stl中的优先级队列priority_queue,但是要注意因为我们要判断边的权值大小,而边是自定义类型,判断大小我们要在类里面重载operator<()。还要注意挑小的边要用greater,每次把大的边压到堆底。

代码实现

// 由于我们要频繁用到边,但是在邻接矩阵中边的遍历太麻烦,所以我们在写一个边
struct Edge
{
	int _srci;
	int _dsti;
	W _w;

	Edge(int srci, int dsti, W w)
		:_srci(srci)
		, _dsti(dsti)
		, _w(w)
	{}


	//要让大的数在后面
	bool operator>(const Edge& e) const
	{
		return _w > e._w;
	}
};


W Kruskal(Self& minTree)
{
	int n = _vertexs.size();
	minTree._indexmap = _indexmap;
	minTree._vertexs = _vertexs;
	minTree._matrix.resize(n);
	for (int i = 0; i < n; i++)
	{
		minTree._matrix[i].resize(n, MAX_W);
	}

	//优先级队列,里面需要元素类型,选用结构存储,仿函数比较大小(自定义类型要自己写仿函数)
	priority_queue<Edge, vector<Edge>, greater<Edge>> minq;

	//最小生成树只有n-1条边
	int size = n - 1;

	//先把所有的边加到队列
	for (int i = 0; i < n; i++)
	{
		//由于是无向图,只要看一半
		for (int j = 0; j < i; j++)
		{
			if (_matrix[i][j] != INT_MAX)
			{
				minq.push(Edge(i, j, _matrix[i][j]));
			}
		}
	}

	//需要判环 所以要用到并查集
	UnionFindSet ufs(n);

	W totalW = W();
	while (!minq.empty())
	{
		Edge min = minq.top();
		minq.pop();
		//先判环
		if (!ufs.InSet(min._dsti, min._srci))
		{
			cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
			minTree.AddEdge(min._srci, min._dsti, min._w);
			ufs.Union(min._srci, min._dsti);
			totalW += min._w;
			size--;
		}
	}

	//循环走完
	if (size == 0)
	{
		return totalW;
	}
    //如果没找到就要返回默认值
	else
	{
		return W():
	}
}

代码解释

为了方便我加了typedef Graph<V, W, MAX_W, Direction> Self

创建minTree,其中顶点集合_vertexs和_indexmap不变,minTree自己的邻接矩阵要通过算法得到,所以只要初始化成最大值就行。

创建优先级队列,并把所有边放入队列。

用while循环, 先找到最小边,并查集检查起点终点是否在同一集合,不同就minTree添加边。不要忘记并查集合并两个顶点。

若找到n-1条边就返回totalW,没有就返回默认值。

2、Prim算法(一种从起点考虑的算法)

基本原理

设走过的顶点在集合X中,未走过的再Y中,在XY中分别挑一个点使构成的边权值是最小的加入到最小生成树中,重复n-1次。当然加入的边的终点要从Y变到X。

代码实现

struct Edge
{
	int _srci;
	int _dsti;
	W _w;

	Edge(int srci, int dsti, W w)
		:_srci(srci)
		, _dsti(dsti)
		, _w(w)
	{}


	//要让大的数在后面
	bool operator>(const Edge& e) const
	{
		return _w > e._w;
	}
};

W Prim(Self& minTree, const V& src)
{
	int n = _xertexs.size();
	int srci = GetIndex(src);
	minTree._indexmap = _indexmap;
	minTree._vertexs = _vertexs;
	minTree._matrix.resize(n);
	for (int i == 0; i < n; i++)
	{
		minTree._matrix[i].resize(n, MAX_W);
	}
	W totalW = W();
	//创建一个集合标记走过的顶点
	vector<bool> x(n, false);
	x[srci] = true;
	priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
	for (int i = 0; i < n; i++)
	{
		if (_matrix[srci][i] != MAX_W)
		{
			minq.push(Edge(srci, i, _matrix[srci][i]));
		}
	}
	int size = 0;
	while (!minq.empty())
	{
		Edge min = minq.top();
		minq.pop();
			
		//起点一定在x,只要终点在x就构成环
		if (X[min.dsi])
		{
			cout << "构成环" << endl;
		}

		else
		{
			minTree.AddEdge(min._srci, min._dsti, min._w);
			X[min._dsti] = true;
			size++;
			totalW += min._w;

			if (size == n - 1)
				break;

			for (int i = 0; i < n; i++)
			{
				if (_matrix[dsti][i] != MAX_W && X[i] == false)
				{
					minq.push(Edge(dsti, i, _matrix[dsti][i]));
				}
			}
		}
	}

	if (size == n - 1)
		return totalW;
	else
		return W():
}

代码解释

创建minTree,其中顶点集合_vertexs和_indexmap不变,minTree自己的邻接矩阵要通过算法得到,所以只要初始化成最大值就行。

创建X集合来标记走过的顶点,先把srci存在X中。

创建优先级队列,在while循环开始之前先把以srci为起点的边入队,由于一开始只有srci是走过的顶点,所以可以不要判断X[i]==false。

找到队列中最小边min,若min的终点在X中就表示min的起点与终点都在X,构成环。若不在就先minTree入边,并把min的终点加到X集合。

此时我们要把min的终点dsti看成起点,用for循环选出以dsti为起点的边,注意此时要判断X[i]==false,我们不能让dsti->i这条边的终点是已经走过的顶点入队。

最后在while中特判是否已经选到n-1条边。

若找到n-1条边就返回totalW,没有就返回默认值。

  • 34
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值