昨天終於證實了這兩天的疑惑:爲什麼我下載的這本數據結構和算法頁數這麼少?二叉樹部分明明沒有講完就開始下一章了,把那些頁數刪了還傳到網上的人也是有點兒可惡,唉,誰讓自己沒去買正版呢。。。先按照已有的內容把整本過一遍吧,之後再用另一本和做題的方法查漏補缺吧。。。
圖
圖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;
}