图的定义
定义:图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
图的构成
1、顶点:图中数据元素
2、边:顶点之间的逻辑关系
3、顶点集合为有穷非空集合
4、权:与图的边或弧相关的数
图的种类
- 无向图:若顶点Vi到Vj之间的边没有方向,则称为这条边为无向边,用无序偶对(Vi,Vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。
- 有向图:若顶点Vi到Vj之间的边有方向,则称为这条边为有向边(弧),用有序偶<Vi,Vj>来表示。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。(注意弧就是Vi到Vj的的有向边,Vi是弧头,Vj是弧尾,两个的位置不能变)
- 简单图:若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图
- 多重图:图中某两个结点的边数多与一条,又允许顶点通过同一条边和自己关联,则称为多重图
- 无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图
- 有向完全图:在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图
- 稀疏图:有很少条边或弧的图称为稀疏图,反之称为稠密图
- 网:带权的图
图的顶点
对于无向图来说:
顶点v的度是指依附于该顶点的边的条数,记为TD(v)。
总顶点的度的和等于边数的2倍
对于有向图来说:
入度是以顶点v为终点的有向边的数目,记为ID(v);
出度时以顶点v为起点的有向边的数目,记为OD(v).
v的度为TD(v)=ID(v)+OD(v);
各顶点出度数=入度数=边数
路径:顶点到顶点之间的一条路径是指顶点序列
简单路径:在路径序列中,顶点不重复出现的路径称为简单路径
回路:第一个顶点和最后一个顶点相同的路径称为回路或环
简单回路:除第一个顶点和最后一个顶点外,其余不重复出现的回路称为简单回路
路径长度:路径上边的数目
顶点到顶点的距离:从顶点u出发到顶点v的最短路径若存在,则次路径的长度称为从u到v的距离,若从u到v不存在路径,则记该距离为无穷
无向图中,若从顶点v到顶点w有路径存在,则称v和w是连通的,如果任意两个顶点都是连通的,则称该图为连通图,否则称为非连通图。
对于n个顶点的无向图G,若图G是连通图,则最少有n-1
条边。
若图G是非连通图,则最多可能有C上标2下标为n-1条边
有向图中,若从顶点v到顶点w和从顶点w到顶点v之间都有路径,则称这两个顶点是强连通的,如果任意一对顶点都是强连通的,则称此图为强连通图。
对于n个结点的有向图G,若G是强连通图,则最少有n条边(形成回路)
图的局部–子图
如果子图所有结点都有,则称其为G的生成子图
连通分量
无向图中的极大连通子图称为连通分量
有向图中的极大强连通子图称为有向图的强连通分量
生成树
连通图的生成树是包含图中全部顶点的一个极小连通子图
若图中顶点树为n,则它的生成树含有n-1条边。对于生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边则会形成一个回路。
无向树
n个顶点的树,必有n-1条边
n个顶点的图,若|E|>n-1,则一定有回路
有向树
一个顶点的入度为0,其余顶点的入度均为1的有向图,称为有向树
生成森林
在非连通图中,连通分量的生成树构成了非连通图的生成森林
图的存储
图的存储有四种方法
1、邻接矩阵
2、邻接表
3、十字链表
4、邻接多重表
邻接矩阵
含义:图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息
空间复杂度:O(|V|平方2)——只和顶点数相关,和实际的边数无关
应用:适合用于存储稠密图
矩阵相乘
邻接表
含义:将数组与链表相结合的存储方法称为邻接表
无向图——边结点的数量是2|E|,整体空间复杂度为O(|V|+2|E|)
有向图——边结点的数量是|E|,整体空间复杂度为O(|V|+|E|)
邻接表的实现
1、图中的顶点用一个一维数组存储
2、图中每个顶点v的所有邻接点构成一个线性表
data是数据域,存储顶点的信息
Firstdae是指针域,指向边表的第一个结点。
adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标
next则存储指向边表中下一个结点的指针
十字链表
将邻接表与逆邻接表结合起来
用于存储有向图
顶点的结点
Firstin:表示入边表头指针
Firstout:表示出边表的头指针
边表结点
tailvex :指弧起点在顶点表的下标
headvex:指弧终点在顶点表中的下标
headlink:指入边表的指针域,指向终点相同的下一条边
taillink:指边表指针域,指向起点相同的下一条边
如果是网的话,还可以增加一个weight域来存储权值
邻接多重表
ivex和jvex是与某条边依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表结构
用于存储无向图
实现图的实例代码(邻接表法)
/// <summary>
/// 表头结点
/// </summary>
/// <typeparam name="T"></typeparam>
public class Vertex<T>
{
public T data;//数据
public Node<T> firstEdge;//邻接点表头指针
public Boolean visited;//访问标识
public Vertex(T value)
{
data = value;
}
}
//表结点
public class Node<T>
{
public Vertex<T> adjex;//邻接点表头指针
public Node<T> next;//下一个邻接点
public Node(Vertex<T> value)
{
adjex = value;
}
}
public class MyAdjacencyList<T>
{
List<Vertex<T>> items;//图的所有顶点集合
public MyAdjacencyList(int capacity)
{
items = new List<Vertex<T>>();
}
//查找是否含有相同项
public bool Containt(T item)
{
foreach (Vertex<T> v in items)//foreach与for的区别,foreach是安全性线程,而for是非安全性。同时foreach的效率更高
{
if (v.data.Equals(items))
{
return true;
}
} return false;
}
//查找指定项
private Vertex<T> Find(T t)
{
foreach (Vertex<T> item in items)
{
if (item.data.Equals(t))
{
return item;
}
}
return null;
}
//添加结点
public void AddVertex(T t)
{
if (Containt(t))
{
throw new ArithmeticException("插入了重复结点!");
}
items.Add(new Vertex<T>(t));
}
//添加边(无向边)
public void AddEdge(T from, T to)
{
Vertex<T> fromvar = Find(from);//寻找头结点
if (fromvar == null)
{
throw new ArgumentException("头结点不存在");
}
Vertex<T> tovar = Find(to);
if (tovar == null)
{
throw new ArgumentException("头结点不存在");
}
AddDirectEdge(fromvar, tovar);
AddDirectEdge( tovar,fromvar);
}
//添加有向边
public void AddDirectEdge(Vertex<T> fromvar, Vertex<T> tovar)
{
if (fromvar.firstEdge == null)//无邻接结点
{
fromvar.firstEdge = new Node<T>(tovar);
}
else
{
Node<T> tmp, node = fromvar.firstEdge;
do
{
//检查是否添加了重复边
if (node.adjex.data.Equals(tovar.data))
{
throw new ArgumentException("添加了重复边");
}
tmp = node;
node = node.next;
}
while
(node != null);
tmp.next = new Node<T>(tovar);//添加到链尾
}
}
public override string ToString()
{
//打印结点信息
string s=string.Empty;
foreach (Vertex<T> v in items)
{
s += v.data.ToString() + ":";
if (v.firstEdge != null)
{
Node<T> tmp = v.firstEdge;
while (tmp != null)
{
s += tmp.adjex.data.ToString();
tmp = tmp.next;
}
}
s += "\r\n";
}
return s;
}
}
class Program
{
static void Main(string[] args)//邻接表
{
MyAdjacencyList<string> a = new MyAdjacencyList<string>(4);
a.AddVertex("A");
a.AddVertex("B");
a.AddVertex("C");
a.AddVertex("D");
//添加边
a.AddEdge("A", "B");
a.AddEdge("B", "D");
a.AddEdge("A", "D");
a.AddEdge("A", "C");
Console.WriteLine(a.ToString());
Console.ReadLine();
}
}
图的遍历
1、深度优先遍历
public void DFSTravn()
{
IninVisited();
DFS(items[0]);
}
private void DFS(Vertex<T> v)
{
v.visited = true;//先访问此结点
Console.WriteLine(v.data+" ");
Node<T> node = v.firstEdge;
while (node != null)
{
if (!node.adjex.visited)//未被访问
{
DFS(node.adjex);//递归
}
node = node.next;
}
}
2、广度优先遍历
//广度优先遍历
public void BFSTravn()
{
IninVisited();
BFS(items[0]);
}
//采用队列的方法的广度遍历
private void BFS(Vertex<T> v)
{
Queue<Vertex<T>> queue = new Queue<Vertex<T>>();
v.visited = true;
Console.WriteLine(v.data.ToString());
queue.Enqueue(v);
while (queue.Count > 0)//队列中存在元素继续遍历访问
{
Vertex<T> w = queue.Dequeue();
Node<T> node = w.firstEdge;
while (node != null)//访问此表头结点的所有表结点
{
if (!node.adjex.visited)
{
Console.WriteLine(node.adjex.data+" ");
node.adjex.visited = true;
queue.Enqueue(node.adjex);
}
node = node.next;
}
}
}
生成树
对于无向图,含有连通图全部顶点的一个极小连通图
本质:从连通图任一结点出发进行遍历操作所经过的边,再加上所有顶点构成的子图
最小生成树
如果连通图是一个网络,称该网络的所有生成树中权值总和最小的生成树
1、必须只使用该图中的边来构造最小生成树
2、必须使用且使用n-1边来连接途中的n个顶点
3、构造的最小生成树中不存在会路
计算最小生成树的方法
1、Prim算法(普利姆)
从某一个顶点开始构建生成树,每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。
时间复杂度:O(|v|2)适合用于稠密图
2、Kruskal算法(克鲁斯卡尔)
每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选),直到所有结点都连通
时间复杂度:O(|E|log2|E|)适合用于稀疏图