昨天终于证实了这两天的疑惑:为什么我下载的这本数据结构和算法页数这么少?二叉树部分明明没有讲完就开始下一章了,把那些页数删了还传到网上的人也是有点儿可恶,唉,谁让自己没去买正版呢。。。先按照已有的内容把整本过一遍吧,之后再用另一本和做题的方法查漏补缺吧。。。
图
图G(V, E)为非线性结构,即互相之间均可存在二元关系的一组对象。从算法角度处理这类结构时,可通过遍历将其转化为非线性结构,即为树,从而解决问题。
V为顶点合集,E为连接集合V中某一对定点(u, v)的边的合集,V和E都是有限集,通常将其规模记为 n = |V|, e = |E|。
无向边:边对应的顶点(u, v)的次序无所谓
有向边:u和v的次序不对等。有向边(u,v)表示从u指向v,u称为该边的起点或尾顶点,v称为该边的终点或头顶点。
无向图:E中各边均无方向。
有向图:E中只含有向边。
混合图:E同时包含无向边和有向边。
度数:在无向图中与定点u关联的边数。
在有向图中若e = (u, v), e称作点u的出边、点v的入边,一个顶点的出边的总数称为出度,入边的总数成为入度。
自环:连接于同一顶点的边。
简单图:不含任何自环的图。
路径/通路:由m+1个顶点和m条边交替而成的序列
π = {v0, e0, v1, e1 …… v(m-1), e(m), v(m)},
且对于任何 0 < i <= m都有 e(i) = (v(i-1), v(i)), m的总数成为通路的长度,
记作 |π| = m。
简化描述为 π = {v0, v1 …… v(m)}。
简单通路:沿途顶点互异的通路。
环路:对于m>=1的通路,起止顶点相同。
有向无环图:不含有任何环路的有向图。
简单环路:除起止顶点相同外,沿途顶点互异的环路。
欧拉环路:经过图中各边一次且只有一次的环路。
哈密尔顿环路:经过途中各顶点一次且只有一次的环路。
带权图/带权网络/网络:每一条边都带有权重的图 G(V, E, wt())。
复杂度:对于无向图,最多有 n(n-1)/2 条边,有向图最多有 n(n-1)条边,所以复杂度 e = O(n(2))。
邻接矩阵:矩阵A[n][n]表示由n个顶点构成的图。对于无权图,存在从点u到点v的边,当且仅当A[u][v] = 1,不存在则为0或∞。推广到有权网络可记录边的权重。邻接矩阵的时间效率为常数,空间效率最低为O(n(2))。
邻接表:由邻接矩阵转化可得。比矩阵效率高,尤其擅长批量处理同一顶点的所有关联边。
代码参考:C# 邻接表存储结构 和 C#有向图拓扑排序
public class AdjacencyList<T>
{
List<Vertex<T>> items; //顶点集合
List<KeyValuePair<Vertex<T>, Vertex<T>>> edges //边集合
public AdjacencyList():this(10) {} //构造函数
public AdjacencyList(int capacity) { items = new List<Vertex<T>>(capacity)} //指定容量的构造函数
public class Vertex<TValue> //表头结点
{
public TValue data;
public Node fistEdge; //邻接点链表头指针
public Boolean visited; //记录是否被访问过
public int InDegree; //入度
public int OutDegree; //出度
public VerTex(TValue value) { valuse = data; }
}
public class Node //表结点
{
public VerTex<T> adjver; //邻接点
public Node next; //下一个邻接点指针
public Node(Vertext<T> ver) { ver = adjver; }
}
public bool Contain(T item)
{
foreach(Vertex<T> v in items)
{
if(v.data.Equals(item))
{ return true; }
}
return false;
}
public Vertex<T> Find(T item)
{
foreach(Vertex<T> v in items)
{
if(v.data.Equals(item)
{ return v; }
}
return null;
}
public void AddVertex(T item)
{
if(!Contains(item))
{
items.Add(new List<Vertex<T>>(item));
}
}
public void AddDirectedEdge(T fromVer, T toVer) //添加有向边
{
if(fromVer.firstEdge == null)
{
fromVer.firstEdge = new Node(toVer);
}
else
{
Node temp, node = fromVer.firstEdge;
do
{
if( !node.adjver.data.Equals(toVer.data) //不能添加重复的边
{
tmp = node;
node = node.next;
}
}while(node != null)
tmp.next = new Node(toVer); //添加到链表末尾
edges.Add(new List<KeyValuePair<Vertex<T>, Vertex<T>>>(fromVer, toVer));
}
}
public void AddNoDirectedEdge(T from, T to) //添加无向边
{
Vertex<T> fromVer = Find(from);
Vertex<T> toVer = Find(to);
if(fromVer != null && toVer != null)
{
AddDirectedEdge(fromVer, toVer);
AddDirectedEdge(toVer, fromVer);
}
}
public void RemoveEdge(Vertex<T> v) //删除点的出边
{
int i = 0;
while(edges.Count > 0)
{
if(v.data.Equals(edges[i].Key.data))
{
edges.RemoveAt(i);
break;
}
i++;
}
}
public void CountInOutDegrees() //计算顶点的入度和出度
{
foreach(Vertex<T> v in items)
{
int inD = 0;
int outD = 0;
foreach(KeyValuePair<Vertex<T>, Vertex<T>> e in edges)
{
if(e.Key.data.Equals(v.data))
{
outD++;
}
if(e.Value.data.Equals(v.data))
{
inD++;
}
}
v.InDegree = inD;
v.OutDegree = outD;
}
}
public List<Vertex<T>> CycleExistOrNot() //判断是否有环路,有环路返回空,没有环路返回点
{
foreach(Vertex<T> v in items)
{
if(v.InDegree == 0)
{
return v;
}
}
return null;
}
}
图搜索
波峰集:由所有已被访问到的顶点中任未被访问到的邻居顶点构成。
广度优先(BFS):
越早被访问到的顶点,其邻居越优先被选用。
即反复在波峰集中查找最早被访问到的顶点,若其邻居均已被访问到,则将该顶点逐出波峰集,否则任意选择一个其尚未访问到的邻局访问并将其加入波峰集。
(连通图)
public void BFS(Vertex<T> v)
{
print(v.data);
v.visited = true;
Queue<Vertex<T>> q = new Queue<Vertex<T>>();
q.Enqueue(v);
while(q.Any())
{
Vertex<T> nv = q.Dequeue();
Node node = nv.firstEdge;
while(node != null)
{
if(!node.adjver.visited)
{
print(node.adjver.data);
node.adjver.visited = true;
q.Enqueue(node.adjver);
}
node = node.next;
}
}
}
深度优先(DFS):
优先搜索最后一个被访问到的顶点的邻居。
各顶点被访问的次序类似于树的先序遍历,各顶点被访问完毕的次序类似于树的后序遍历。
(连通图)
public void DFS(Vertex<T> v)
{
print(v.data);
v.visited = true;
Node node = v.firstEdge;
while(node != null)
{
if(!node.adjver.visited)
{
DFS(node.adjver);
}
node = node.next;
}
}
非连通图的广度和深度遍历就是以每个结点为顶点遍历整个邻接表
public void BFS()
{
foreach(Vertex<T> v in items)
{
if(!v.visited)
{
BFS(v);
}
}
}
public void DFS()
{
foreach(Vertex<T> v in items)
{
if(!v.visited)
{
DFS(v);
}
}
}
有向图的拓扑排序:
在一个线性序列中,每一个顶点都不会通过边指向其在该序列中的前驱点。
同一有向图的拓扑排序未必唯一。
不含环路的有向无环图,其拓扑排序一定存在。
过程:判断图中是否存在入度为0的点,如果存在,记录该点,然后将该点从图中删除,循环这一过程,直到所有点均被删除,即得到一个排好序的结构。
public List<Vertex<T>> TopoSort()
{
List<Vertex<T>> result = new List<Vertex<T>>();
while(items.Count > 0)
{
List<Vertex<T>> tmp = CycleExistOrNot();
if(tmp != null)
{
result.Add(tmp);
items.Remove(tmp);
RemoveEdge(tmp);
}
}
return result;
}