《数据结构、算法与应用 —— C++语言描述》学习笔记 — 图

本文是《数据结构、算法与应用 —— C++语言描述》关于图的学习笔记,涵盖了图的基本概念如顶点、边、有向图和无向图,以及图的表示方法如邻接矩阵和邻接链表。此外,还讨论了加权图的描述,图的类关系和抽象结构,并提供了加权有向图的邻接矩阵实现的详细步骤。
摘要由CSDN通过智能技术生成

一、基本概念

1、顶点和边

图是一个用线或边连接在一起的定点或节点的集合。严格地说,图是有限集V和E的有序对,即 G = ( V , E ) G=(V,E) G=(V,E),其中V的元素称为顶点,E的元素称为

每一条边连接两个不同的顶点,而且用元组(i,j)来表示,其中 ij 是边所连接的两个顶点。带方向的边是有向边,不带方向的边叫无向边。当且仅当(i,j)是图的边,我们称 ij邻接的

2、方向和权

如果图中所有边都是无向边,那么该图叫做无向图;如果所有的边都是有向边,则该图叫做有向图。在图的一些应用中。我们可能要为每条边赋予一个表示成本的值,我们称之为权。这时的图称为加权有向图和加权无向图。

对于有向图来说,有向边(i,j)是关联顶点 j 而关联于顶点 i。顶点 i 邻接至顶点 j,顶点 j 邻接于顶点 i

3、路径

一个顶点序列 P = i 1 , i 2 , ⋅ ⋅ ⋅ , i k P=i_1,i_2,···,i_k P=i1i2ik是图或有向图 G = ( V , E ) G=(V,E) G=(V,E)的一条从 i 1 i_1 i1 i k i_k ik路径,当且仅当对于每个 j 1 ≤ j < k 1 \le j<k 1j<k),边( i j , i j + 1 i_j,i_{j+1} ij,ij+1)都在E中。

一条路径如果除第一个和最后一个顶点外,其余所有顶点均不同,那么该路径被称为一条简单路径

图或有向图的每一条边都有长度,一条路径的长度是该路径所有边的长度之和。

对于图 G = ( V , E ) G=(V,E) G=(V,E),我们说G是连通的,当且仅当G的每一对顶点之间都有一条路径。

如果H的顶点和边的集合分别是G的顶点和边的集合的子集,那么称图H是图G的子图。一条起点和终点相同的简单路径称为环路。没有环路的连通无向图是一棵。一个G的子图,如果包含G的所有顶点,且是一棵树,则称为G的生成树

二、特性

在一个无向图中,于一个顶点 i 相关联的边数称为该顶点的
特性① G = ( V , E ) G=(V,E) G=(V,E)是一个无向图,令 n = ∣ V ∣ , e = ∣ E ∣ n=|V|,e=|E| n=Ve=E,则
(1) ∑ i = 1 n d i = 2 e \sum_{i=1}^nd_i=2e i=1ndi=2e
(2) 0 ≤ e ≤ n ( n − 1 ) 2 0\le e \le \frac{n(n-1)}{2} 0e2n(n1)

一个具有 n 个顶点和 n ( n − 1 ) 2 {n(n-1)}{2} n(n1)2条边的无向图是一个完全图

设G是一个有向图。顶点 i 的入度 d i i n d_i^{in} diin是指关联至该顶点的边数。顶点 i 的出度 d i o u t d_i^{out} diout是指关联于该顶点的边数。

特性② G = ( V , E ) G=(V,E) G=(V,E)是一个有向图,令 n = ∣ V ∣ , e = ∣ E ∣ n=|V|,e=|E| n=Ve=E,则
(1) ∑ i = 1 n d i i n = ∑ i = 1 n d i o u t = e \sum_{i=1}^nd_i^{in}=\sum_{i=1}^nd_i^{out}=e i=1ndiin=i=1ndiout=e
(2) 0 ≤ e ≤ n ( n − 1 ) 0\le e \le n(n-1) 0en(n1)

三、抽象数据类型

1、边及迭代器

// Edge.h
#pragma once
template<class T>
class Edge
{
public:
	virtual T vertex1() const = 0;
	virtual T vertex2() const = 0;
	virtual int weight() const = 0;
};
// VertexIterator.h
#pragma once
template <class T>
class VertexIterator
{
public:
	virtual int next() = 0;
};

2、图接口

#pragma once
#include "Edge.h"
#include "VertexIterator.h"

template <class T>
class Graph
{
public:
	virtual ~Graph() {}

	virtual int numberOfVertices() const = 0;
	virtual int numberOfEdges() const = 0;
	virtual bool existsEdge(int, int) const = 0;
	virtual void insertEdge(Edge<T>* edge) = 0;
	virtual void eraseEdge(int, int) = 0;
	virtual int degree(int) const = 0;
	virtual int inDegree(int) const = 0;
	virtual int outDegree(int) const = 0;

	virtual bool directed() const = 0;
	virtual bool weighted() const = 0;
	virtual VertexIterator<T>* iterator(int) = 0;
};

四、无权图的描述

1、邻接矩阵

一个顶点图 G = ( V , E ) G=(V,E) G=(V,E)的邻接矩阵是一个 n ∗ n n*n nn矩阵(假设是A),其中的每个元素是0或1。假设V={1,2,3,···,n}。如果G是一个无向图,那么其中的元素定义如下: A ( i , j ) = { 1 如 果 ( i , j ) ∈ E 或 ( j , i ) ∈ E 0 其 他 A(i,j)=\left\{\begin{aligned}1 & \quad 如果(i,j)∈E或(j,i)∈E\\ 0 & \quad 其他 \end{aligned}\right. A(i,j)={10(i,j)E(j,i)E如果G是有向图,那么 A ( i , j ) = { 1 如 果 ( i , j ) ∈ E 0 其 他 A(i,j)=\left\{\begin{aligned}1 & \quad 如果(i,j)∈E\\ 0 & \quad 其他 \end{aligned}\right. A(i,j)={10(i,j)E

从以上两个公式,我们不难得出以下结论:
① 无向图的邻接矩阵是对角线对称的,且 A ( i , i ) = 0 , 1 ≤ i ≤ n A(i,i) = 0,1 \le i \le n A(i,i)=01in
② 对于n顶点的无向图,有 ∑ 1 n A ( i , j ) = ∑ 1 n A ( j , i ) = d i \sum_1^nA(i,j)=\sum_1^nA(j,i)=d_i 1nA(i,j)=1nA(j,i)=di
③ 对于n顶点的有向图,有 ∑ 1 n A ( i , j ) = d i o u t \sum_1^nA(i,j)=d_i^{out} 1nA(i,j)=diout ∑ 1 n A ( j , i ) = d i i n \sum_1^nA(j,i)=d_i^{in} 1nA(j,i)=diin,其中 1 ≤ i ≤ n 1 \le i \le n 1in

2、邻接链表

一个顶点 i邻接表 是一个线性表,它包含所有邻接于顶点 i 的顶点。在一个图的邻接表描述中,图的每一个顶点都有一个邻接表。当邻接表用链表表示时,就是邻接链表。

我们可以使用类型为链表的数组 aList 来描述所有邻接表。aList[i].firstNode 指向顶点 i 的邻接表的第一个顶点。如果 x 指向链表 aList[i] 的一个顶点,那么(i,x->element)是图的一条边。
在这里插入图片描述
一个指针和一个整数各需要4字节的存储空间,因此用邻接链表描述一个n顶点图需要 8 ( n + 1 ) 8(n+1) 8(n+1)字节存储 n + 1 n+1 n+1个firstNode指针和aList链表的listSize域,需要 4 ∗ 2 ∗ m 4*2*m 42m字节存储 m 个链表节点,每个链表节点的两个域 nextelement 各需4字节,其中对无向图,m=2e,对有向图,m=e,其中 e 是边数。

e 远远小于 n 2 n^2 n2 时,邻接链表比邻接矩阵需要更少的空间。例如,一个 e = n e=n e=n 的有向图,用邻接链表描述需要 16 n + 8 16n+8 16n+8 字节,用压缩的邻接矩阵描述需要 n 2 n^2 n2 字节。因此,当 e = n ≥ 17 e=n \ge 17 e=n17时,邻接链表描述所需空间更少。

3、临界数组

在邻接数组中,每一个邻接表用一个数组线性表而非链表来描述。我们可以选择使用二维数组实现,也可以使用指针数组实现。

五、加权图的描述

将无向图的描述进行简单扩充就可得到加权图的描述。用成本邻接矩阵C描述加权图。如果C(i,j)表示边(i,j)的权,那么它的使用方法和邻接矩阵的使用方法一样。在这种描述方法中,需要给不存在的边指定一个值,一般是一个很大的值。在实现代码中,我们要求用户用noEdge表示这个值。

如果链表的元素有两个域 vertexweight,就可以从相应的无权图的邻接链表得到加权图的邻接链表。

六、类关系

我们可以通过继承实现图的邻接矩阵和邻接链表表示,其继承关系如下:
在这里插入图片描述

七、抽象结构

1、图

#pragma once
#include "Edge.h"
#include "VertexIterator.h"

template <class T>
class Graph
{
public:
	virtual ~Graph() {}

	virtual int numberOfVertices() const = 0;
	virtual int numberOfEdges() const = 0;
	virtual bool existsEdge(int, int) const = 0;
	virtual void insertEdge(Edge<T>* edge) = 0;
	virtual void eraseEdge(int, int) = 0;
	virtual int degree(int) const = 0;
	virtual int inDegree(int) const = 0;
	virtual int outDegree(int) const = 0;

	virtual bool directed() const = 0;
	virtual bool weighted() const = 0;
	virtual VertexIterator<T>* iterator(int) = 0;
};

2、边

#pragma once
template<class T>
class Edge
{
public:
	virtual ~Edge() {}
	virtual int vertex1() const = 0;
	virtual int vertex2() const = 0;
	virtual T weight() const = 0;
};

3、顶点迭代器

#pragma once
template <class T>
class VertexIterator
{
public:
	virtual ~VertexIterator() {}
	virtual int next() = 0;
	virtual int next(T& weight) = 0;
};

八、加权有向图的邻接矩阵实现

1、声明

#pragma once
#include "Graph.h"
#include <stdexcept>
#include <algorithm>
#include <iostream>

template <class T>
class adjacencyWDGraph : public Graph<T>
{	
public:
	class adjacencyWDIterator : public VertexIterator<T>
	{
	public:
		adjacencyWDIterator(T* row, T noEdge, int vertexNum);
		virtual ~adjacencyWDIterator() {}
		virtual int next() override;
		virtual int next(T& weight) override;

	protected:
		T* row;
		T noEdge;
		int vertexNum;
		int currentVertex;
	};

	adjacencyWDGraph(int vertexNum = 0, T noEdge = T());
	adjacencyWDGraph(const adjacencyWDGraph& other);
	adjacencyWDGraph(adjacencyWDGraph&& other);
	virtual ~adjacencyWDGraph() override;
	adjacencyWDGraph& operator=(const adjacencyWDGraph& other);
	adjacencyWDGraph& operator=(adjacencyWDGraph&& other);

	virtual int numberOfVertices() const override;
	virtual int numberOfEdges() const override;
	virtual bool existsEdge(int, int) const override;
	virtual void insertEdge(Edge<T>* edge) override;
	virtual void eraseEdge(int, int) override;
	virtual int degree(int) const override;
	virtual int inDegree(int) const override;
	virtual int outDegree(int) const override;

	virtual bool directed() const override;
	virtual bool weighted() const override;
	virtual VertexIterator<T>* iterator(int) override;

protected:
	int vertexNum;
	int edgeNum;
	T** elements;
	T noEdge;

	bool checkEdge(int row, int column) const;

	void checkVertex(int vertex) const;

private:
	void makeCopyAndSwap(const adjacencyWDGraph& other);

	adjacencyWDGraph makeCopy(const adjacencyWDGraph& other);

	void swap(adjacencyWDGraph& other);

	void make2DArray(T**& arr, int row, int column);
};

2、构造析构

template<class T>
inline adjacencyWDGraph<T>::adjacencyWDGraph(int vertexNum, T noEdge)
{
	if (vertexNum <= 0) {
		throw std::invalid_argument("vertexNum must be > 0");
	}

	this->vertexNum = vertexNum;
	edgeNum = 0;
	this->noEdge = noEdge;
	make2DArray(this->elements, vertexNum, vertexNum);
	std::for_each(elements, elements + vertexNum, [vertexNum, noEdge](T* arr) {std::fill(arr, arr + vertexNum, noEdge); });
}

template<class T>
inline adjacencyWDGraph<T>::~adjacencyWDGraph()
{
	std::for_each(elements, elements + vertexNum, [](T* arr) {delete arr; });
	delete elements;
}

不难看出,构造函数的时间复杂度为 O(n)。

3、拷贝控制

template<class T>
inline adjacencyWDGraph<T> adjacencyWDGraph<T>::makeCopy(const adjacencyWDGraph& other)
{
	adjacencyWDGraph returnGraph;

	returnGraph.vertexNum = other.vertexNum;
	returnGraph.edgeNum = other.edgeNum;
	make2DArray(this->elements, returnGraph.vertexNum, returnGraph.vertexNum);
	for (int i = 0; i < other.vertexNum; ++i)
	{
		copy(other.elements[i], other.elements[i] + other.vertexNum, returnGraph.elements[i]);
	}

	return returnGraph;
}

template<class T>
inline void adjacencyWDGraph<T>::make2DArray(T**& arr, int row, int column)
{
	arr = new T * [row];
	std::for_each(arr, arr + row, [&column](T*& arr) {arr = new T[column]; });
}

4、边界检查

我们需要保证插入或删除的边不是自连边,且顶点符合边界要求:

template<class T>
inline bool adjacencyWDGraph<T>::checkEdge(int row, int column) const
{
	if (row < 0 || row >= vertexNum || column < 0 || column >= vertexNum || row  == column)
	{
		return false;
	}

	return true;
}

template<class T>
inline void adjacencyWDGraph<T>::checkVertex(int vertex) const
{
	if (vertex < 0 || vertex >= vertexNum)
	{
		throw std::invalid_argument("illegal vertex index");
	}
}

5、迭代器

由于使用的是有向图,因此这里提供的迭代器访问只需按行顺序遍历:

template<class T>
inline adjacencyWDGraph<T>::adjacencyWDIterator::adjacencyWDIterator(T* row, T noEdge, int vertexNum):
	row(row),
	noEdge(noEdge),
	vertexNum(vertexNum),
	currentVertex(0)
{
}

template<class T>
inline int adjacencyWDGraph<T>::adjacencyWDIterator::next()
{
	T weight;
	return next(weight);
}

template<class T>
inline int adjacencyWDGraph<T>::adjacencyWDIterator::next(T& weight)
{
	for (int i = currentVertex + 1; i < vertexNum; ++i)
	{
		if (row[i] != noEdge)
		{
			currentVertex = i;
			weight = row[i];
			return i;
		}
	}

	currentVertex = vertexNum + 1;
	return -1;
}

6、边声明

#pragma once
#include "Edge.h"

template<class T>
class WEdge : public Edge<T>
{
public:
	WEdge(int v1, int v2, T weight);
	~WEdge() {}
	virtual int vertex1() const override;
	virtual int vertex2() const override;
	virtual T weight() const override;

protected:
	int v1;
	int v2;
	T w;
};

template<class T>
inline WEdge<T>::WEdge(int v1, int v2, T weight):
	v1(v1),
	v2(v2),
	w(weight)
{
}

template<class T>
inline int WEdge<T>::vertex1() const
{
	return v1;
}

template<class T>
inline int WEdge<T>::vertex2() const
{
	return v2;
}

template<class T>
inline T WEdge<T>::weight() const
{
	return w;
}

7、边操作

template<class T>
inline void adjacencyWDGraph<T>::insertEdge(Edge<T>* edge)
{
	auto vertex1 = edge->vertex1();
	auto vertex2 = edge->vertex2();
	if (!checkEdge(vertex1, vertex2) || vertex1 == vertex2)
	{
		throw std::invalid_argument("the edge doesn't exist");
	}

	if (elements[vertex1][vertex2] == noEdge)
	{
		edgeNum++;
	}

	elements[vertex1][vertex2] = edge->weight();
}

template<class T>
inline void adjacencyWDGraph<T>::eraseEdge(int row, int column)
{
	if (checkEdge(row, column) && elements[row][column] != noEdge)
	{
		elements[row][column] = noEdge;
		edgeNum--;
	}
}

8、入度出度

template<class T>
inline int adjacencyWDGraph<T>::inDegree(int vertex) const
{
	checkVertex(vertex);
	int count = 0;
	std::for_each(elements, elements + vertexNum,
		[&count, vertex, this](T* arr)
		{
			if (arr[vertex] != noEdge)
			{
				count++;
			}
		});

	return count;
}

template<class T>
inline int adjacencyWDGraph<T>::outDegree(int vertex) const
{
	checkVertex(vertex);
	int count = 0;
	std::for_each(elements[vertex], elements[vertex] + vertexNum,
		[&count, this](T element)
		{
			if (element != noEdge)
			{
				count++;
			}
		});

	return count;
}

9、其余接口

template<class T>
inline int adjacencyWDGraph<T>::numberOfVertices() const
{
	return vertexNum;
}

template<class T>
inline int adjacencyWDGraph<T>::numberOfEdges() const
{
	return edgeNum;
}

template<class T>
inline int adjacencyWDGraph<T>::degree(int) const
{
	throw std::runtime_error("no such method");
	return 0;
}

template<class T>
inline bool adjacencyWDGraph<T>::directed() const
{
	return true;
}

template<class T>
inline bool adjacencyWDGraph<T>::weighted() const
{
	return true;
}

template<class T>
inline VertexIterator<T>* adjacencyWDGraph<T>::iterator(int vertex)
{
	checkVertex(vertex);
	return new adjacencyWDIterator(elements[vertex], noEdge, vertexNum);
}

九、加权无向图

加权无向图通过派生于加权有向图实现。我们需要重写 insertEdgeeraseEdge 以实现对称边的插入和删除;除此之外,directed 方法应该直接返回 falsedegree 方法也需要被定义。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值