1. 肯定性:算法的每一個步驟都是明確的,對結果的預期也是肯定的。node
2. 有窮性:算法必須由有限個步驟組成,必須有一個肯定的結束條件。算法
3. 可行性:算法額每個步驟都是可行的,只要一個步驟不行算法就是失敗的。數組
4. 輸入和輸出:算法是要解決特定的問題,問題來源就是算法的輸入,指望結果就是算法的輸出。數據結構
算法是爲了解決一個特定的問題而精心設計的一套數學模型以及在這套數學模型上的一系列操做步驟,app
這些操做步驟將問題描述的輸入數據逐步處理、轉換,並最後獲得一個肯定的結果。dom
一個算法中的語句執行次數成爲語句頻度或時間頻度。記做T(n)ide
n稱爲問題的規模,n不斷變化,T(n)也不斷變化,時間複雜度就是變化時呈現的規律。函數
算法中執行次數是T(n),如有某個輔助函數f(n),使得n趨近於無限大時,T(n)/f(n)的極限值不等於零的常數,稱fn(n)是T(n)的同數量級函數。性能
記做T(n)=O(f(n)),稱O(f(n))爲時間複雜度。例如:O(n^2)ui
按數量級遞增排列,常見的時間複雜度:
時間複雜度記憶方法
冒泡、選擇、插入須要兩個for,每次只關注一個元素,平均複雜度爲O(n2) 。一遍找元素O(n),一遍找位置O(n)
快速、歸併、希爾、堆基於二分思想,log以2爲底,平均複雜度O(nlogn) 。一遍找元素O(n),一遍找位置O(logn)
穩定性記憶方法
排序算法的穩定性指:排序先後相同元素的相對位置不變,則稱排序算法是穩定的。不然是不穩定的
log10100至關於問10的多少次方等於100。答案是2,由於10*10=100。
這裏使用的是大O表示法討論運行時間時,log指的都是log2。若是列表包含8個數字,使用二分查找,最多須要檢查logn個元素。也就是log8=3。
記錄一下log,舉個例子:for(int j=1;j<n;j*=2)。這個循環的時間複雜度是:O(log2n)
j每循環一次乘以2;j初始化爲1,循環x後爲j=2^x;j>n時循環中止,2^x>n,此時x=log2n
當列表是有序的時候,二分查找是一種很是好的查找方式。
def binary_search(list, item): low = 0 high = len(list) - 1 while low <= high: mid = int((low + high) / 2) guess = list[mid] if guess == item: return mid if guess > item: high = mid - 1 else: low = mid + 1 return None oriList = [1, 2, 3, 4, 5, 6, 7, 8] print(binary_search(oriList, 7))
最多須要猜想的次數與列表長度相同,被稱爲線性時間(linear time)
二分查找的運行時間稱爲對數時間
特殊的表示法,指出了算法的速度有多快。
#
數據結構中最簡單的基本數據結構就是線性表。最多見的線性表有四種:數組、鏈表、棧、隊列
一種相對比較簡單的數據關係,全部數據存放在一片連續區域內。經過下標訪問數組元素,能夠進行插入、刪除查找
數組直接訪問沒有開銷,插入和刪除操做須要移動數組元素,開銷比較大。
所以在插入和刪除操做比較頻繁場合下,不適合使用數組。
數組中查找一個元素的時間複雜度是O(n),若是數組是有序的,使用二分查找,能夠將時間複雜度降爲O(logn)
在長度不能肯定的場合,通常採用鏈表的形式。鏈表有兩部分組成,1存放實體數據,2.指針指向
單鏈表,指針指向後方。雙鏈表,是由先後指針兩個組成的。
鏈表的插入和刪除,只須要修改指針指向便可完成。比數組的插入和刪除效果高。
可是查詢效率很低,須要從頭至尾部遍歷,時間複雜度是O(n)
能夠將鏈表尾部節點的向後指針,指向鏈表頭部節點,構成環形鏈表。從任何一個節點開始均可以遍歷整個鏈表。
對於一些插入和刪除操做比較少,查找、遍歷操做比較多的場合,應優先選擇可變長數組替代鏈表。
class Node(): def __init__(self, data): self.data = data self.next = None # 頭插法 class SingnalNode(): def __init__(self): self.current_node = None def add_node(self, data): """ 頭插法插入節點 :param data: :return: """ node = Node(data) node.next = self.current_node self.current_node = node def append_node(self, data): """ 尾插法插入節點 :param data: :return: """ node = Node(data) cur = self.current_node # 遍歷鏈表直到頭節點處中止遍歷 while cur: if cur.next == None: break cur = cur.next cur.next = node def travel(self): """ 遍歷鏈表 :return: """ cur = self.current_node while cur: print(cur.data) cur = cur.next def is_empty(self): """ 判斷鏈表非空 :return: """ return self.current_node == None def get_lenth(self): """ 獲取鏈表的長度 :return: """ cur = self.current_node count = 0 while cur: count += 1 cur = cur.next return count def insert_node(self, index, data): """ 指定位置插入節點 :param index: :param data: :return: """ link_len = self.get_lenth() if index == 0: self.add_node(data) elif index >= link_len: self.append_node(data) else: cur = self.current_node for i in range(1, index): cur = cur.next node = Node(data) node.next = cur.next cur.next = node def del_node(self, index): """ 根據索引刪除節點 :param index: :return: """ # 找到前節點 cur = self.current_node # 前驅節點 pre = None count = 1 len_num = self.get_lenth() while cur: if index == 1: self.current_node = cur.next break if count == index and count < len_num: pre.next = cur.next break if count >= len_num: pre.next = None break count += 1 pre = cur cur = cur.next def del_node(self, index): """ 根據索引刪除節點 :param index: :return: """ # 找到前節點 cur = self.current_node if index == 1: self.current_node = cur.next return # 前驅節點 pre = None len_num = self.get_lenth() for i in range(1, len_num): if i == index: break pre = cur cur = cur.next pre.next = cur.next if __name__ == "__main__": test = SingnalNode() list_data = [1, 2, 3] for i in list_data: test.add_node(i) test.travel() test.del_node(4) test.travel()
棧是一種特殊的線性表,只能在表的一端插入和刪除數組元素,分別稱爲:入棧,出棧。遵循後入先出原則
class Stack(object): """棧""" def __init__(self): self.items = [] def is_empty(self): """判斷是否爲空""" return self.items == [] def push(self, item): """加入元素""" self.items.append(item) def pop(self): """彈出元素""" return self.items.pop() def peek(self): """返回棧頂元素""" return self.items[len(self.items) - 1] def size(self): """返回棧的大小""" return len(self.items) if __name__ == "__main__": stack = Stack() stack.push("hello") stack.push("world") stack.push("itcast") print(stack.size()) print(stack.peek()) print(stack.pop())
一種特殊的線性表,普通的隊列只能在表的一端插入數據,在另外一端刪除數據,不能再其餘地方插入和刪除。
插入和刪除動做,分別成爲「入隊」和「出隊」。遵循先進先出原則
class Queue(object): """隊列""" def __init__(self): self.items = [] def is_empty(self): return self.items == [] def enqueue(self, item): """進隊列""" self.items.insert(0, item) def dequeue(self): """出隊列""" return self.items.pop() def size(self): """返回大小""" return len(self.items) if __name__ == "__main__": q = Queue() q.enqueue("hello") q.enqueue("world") q.enqueue("itcast") print(q.size()) print(q.dequeue())
import numpy as np oriList = np.random.randint(0, 1000, 50) resultList = [] T = [0] * (max(oriList) + 1) for i in range(len(oriList)): T[oriList[i]] += 1 for i in range(0, len(T) - 1): for j in range(0, T[i]): resultList.append(i) print(resultList)
時間複雜度:O(N) 線性階
數字間隔不大,使用一組數據來當作桶,進行插入排序。桶排序主要是利用下標的輸出,而後根據數值循環下標。
import numpy as np oriList = np.random.randint(0, 1000, 50) n = len(oriList) - 1 for i in range(n): for j in range(n, i, -1): if oriList[j - 1] > oriList[j]: S = oriList[j] oriList[j] = oriList[j - 1] oriList[j - 1] = S print(oriList)
時間複雜度:O(N2) 平方階
import numpy as np oriList = np.random.randint(0, 1000, 50) n = len(oriList) st = -1 swapped = True while st < n and swapped: n -= 1 st += 1 swapped = False for j in range(st, n): if oriList[j] > oriList[j + 1]: S = oriList[j] oriList[j] = oriList[j + 1] oriList[j + 1] = S swapped = True for j in range(n - 1, st - 1, -1): if oriList[j] > oriList[j + 1]: S = oriList[j] oriList[j] = oriList[j + 1] oriList[j + 1] = S swapped = True print(oriList)
時間複雜度:O(N2) 平方階
保持間距並不斷減小,初始設置列表長度,每次會除以損耗因子。間距能夠四捨五入,不斷重複,直到間距變成1
import numpy as np oriList = np.random.randint(0, 1000, 50) n = len(oriList) swaps = 1 while n != 1 or swaps == 0: n = int(n / 1.3) if n < 1: n = 1 i = 0 swaps = 0 while i + n < len(oriList): if oriList[i] > oriList[i + n]: S = oriList[i] oriList[i] = oriList[i + n] oriList[i + n] = S swaps = 1 i += 1 print(oriList)
時間複雜度:O(Nlog2N) 線性對數階
import numpy as np oriList = np.random.randint(0, 1000, 50) for i in range(len(oriList)): item = oriList[i] pos = i swapped = True while i != pos or swapped: to = 0 swapped = False for j in range(len(oriList)): if j != i and oriList[j] < item: to += 1 if pos != to: while pos != to and item == oriList[to]: to += 1 temp = oriList[to] oriList[to] = item item = temp pos = to print(oriList)
不穩定的排序,理論上是最優的比較算法。把數列分解爲圈,能夠分別旋轉獲得排序結果,與其餘排序不一樣的是,元素不會被放入數組的任何位置。若是在正確位置則不動,不然只會寫入一次
時間複雜度:O(N2) 平方階
從數據集構建一個數據堆,提取最大元素,放到有序數列末尾。而後從新構造新的數據堆,一直到沒有數據位置。輸入插入排序
import numpy as np def heapSort(list): for i in range(int((len(list) - 1) / 2), -1, -1): adjust(list, i, len(list) - 1) for i in range(len(list) - 1, 0, -1): S = list[i] list[i] = list[0] list[0] = S adjust(list, 0, i - 1) return list def adjust(list, i, m): temp = list[i] j = i * 2 + 1 while j <= m: if j < m and list[j] < list[j + 1]: j += 1 if temp < list[j]: list[i] = list[j] i = j j = 2 * i + 1 else: j = m + 1 list[i] = temp oriList = np.random.randint(0, 1000, 50) print(heapSort(oriList))
時間複雜度:O(Nlog2N)
插入排序的原理是構造一個有序數列,對未排序的數據,從後向前掃描,找到相應的位置並插入。須要反覆把排序元素逐步向右挪位,爲最新元素提供插入空間
import numpy as np oriList = np.random.randint(0, 1000, 50) for i in range(len(oriList)): val = oriList[i] j = i - 1 done = False while not done: if oriList[j] > val: oriList[j + 1] = oriList[j] j -= 1 if j < 0: done = True else: done = True oriList[j + 1] = val print(oriList)
時間複雜度:O(N2) 平方階
經過比較相鄰的奇偶數進行排序,對存在錯誤的順序進行交換。並一直重複這個過程,直到列表有序
import numpy as np oriList = np.random.randint(0, 1000, 50) sorted = False while not sorted: sorted = True for i in range(1, len(oriList) - 1, 2): if oriList[i] > oriList[i + 1]: S = oriList[i + 1] oriList[i + 1] = oriList[i] oriList[i] = S sorted = False for i in range(0, len(oriList) - 1, 2): if oriList[i] > oriList[i + 1]: S = oriList[i + 1] oriList[i + 1] = oriList[i] oriList[i] = S sorted = False print(oriList)
時間複雜度:O(N2) 平方階
快速排序會把集合分紅兩個集合,並選擇一個元素做爲基準。把小於基準的數據排到基準前面,大於放到後面
import numpy as np def QuickSort(list, left, right): right = right == 0 and len(list) - 1 or right i, j = left, right x = list[int((left + right) / 2)] while i <= j: while list[i] < x: i += 1 while list[j] > x: j -= 1 if i <= j: S = list[j] list[j] = list[i] list[i] = S i += 1 j -= 1 if left < j: QuickSort(list, left, j) if right > i: QuickSort(list, i, right) return list oriList = np.random.randint(0, 1000, 50) print(QuickSort(oriList, 0, 0))
時間複雜度:O(Nlog2N) 線性對數
在未排序的列表中找到最小或最大的元素,存放在排序序列的起始位置,而後再從剩餘的排序元素中繼續找尋最小元素放到末尾
import numpy as np
oriList = np.random.randint(0, 1000, 50)
for i in range(len(oriList)):
min = i
for j in range(i + 1, len(oriList), 1):
if oriList[j] < oriList[min]:
min = j
S = oriList[min]
oriList[min] = oriList[i]
oriList[i] = S
print(oriList)
時間複雜度:O(N2) 平方階
經過將比較的所有元素分紅幾個區域來提高插入排序的性能。可讓一個元素一次性地朝最終位置前進一大步。而後步伐愈來愈小,最後就是普通的插入排序。
import numpy as np oriList = np.random.randint(0, 1000, 50) h = int(len(oriList) / 2) while h > 0: for i in range(h, len(oriList)): temp = oriList[i] if temp < oriList[i - h]: for j in range(0, i, h): if temp < oriList[j]: temp = oriList[j] oriList[j] = oriList[i] oriList[i] = temp h = int(h / 2) print(oriList)
時間複雜度:O(Nlog2N)
#
例子:將序列進行去重複操做,而且進行排序。限制時間1秒。序列數字區間很大。
數字區間很大的話,就不可使用桶排序了。那麼咱們就使用快速排序,而後在輸出的時候,作一些手腳。
快速排序結束後,輸出數組使用下面代碼來進行去重複
if (list[i] != list[i - 1])
#
規則跟拉馬車同樣,發牌後計算誰獲勝。
思路:使用隊列,來模擬玩家。使用棧模擬牌桌。使用桶排序模擬是否存在牌。出牌後結果兩種,出列入列。根據出列入列計算。
使用while循環到手牌爲空,其中贏牌的話講Stack中的牌,添加到Queue隊尾。
static void Main(string[] args) { new Program().quicksort("7453123", "2193217"); Console.ReadKey(); } void quicksort(string texthe, string textha) { //初始化兩個隊列來存放 Queue<char> quHe = new Queue<char>(texthe); Queue<char> quHa = new Queue<char>(textha); //定義棧表明牌桌 Stack<char> stack = new Stack<char>(); //使用桶排序來存儲出牌統計 int[] book = new int[10]; //循環執行出牌 int i = 0; while (quHe.Count > 0 && quHa.Count > 0) { Console.WriteLine("玩了{0}局", ++i); if (i > 1000) { Console.WriteLine("大於1000局,默認爲平局"); return; } quickStack(quHe,book,stack); quickStack(quHa, book, stack); } if (quHe.Count == 0) Console.WriteLine("A獲勝"); else Console.WriteLine("B獲勝"); } void quickStack(Queue<char> quHa,int[] book,Stack<char> stack) { //出牌 char t = quHa.Peek(); //判斷是否能夠贏牌 if (book[int.Parse(t.ToString())] == 0) //不能贏牌,入棧 { quHa.Dequeue(); stack.Push(t); book[int.Parse(t.ToString())]++; } else { quHa.Enqueue(quHa.Dequeue()); while (stack.Count != 0 && stack.Peek() != t) { char tt = stack.Pop(); quHa.Enqueue(tt); book[int.Parse(tt.ToString())]++; } } if (quHa.Count == 0) return; }
窮舉法的基本思想就是「有序的去嘗試每一種可能」。分別實驗每一個數,完成等式。代碼略
深度優先搜索(Depth First search,DFS)的關鍵在於解決,當下該如何去作。至於下一步如何去作,是和當下如何去作同樣的。因此咱們主要是寫當前怎麼作的代碼。
下面的代碼就是深度優先搜索的基本模型,
void dfs(int step) { //判斷邊界 //判斷每一種可能 for (int i = 1; i <= n; i++) { //繼續下一步 dfs(step + 1); } //返回 }
輸入一個數N,輸出1~N的全排列。
思路:設1,2,3三個牌,1,2,3三個箱子。約定順序:每一個箱子都先放1,而後2,最後3. 使用for循環約定,設爲i。箱子設爲book
第一重循環,由於BooK是空的,輸出1。而後將箱子遞增,遞歸到第二重循環。
第二重循環,由於book1是有值,因此i=1跳過,進入i=2。箱子遞增,到第三重循環。
第三重循環,i=3。箱子遞增到第四重循環。
第四重循環,箱子數大於傳遞過來的牌數。輸出現有集合的內容,而且return;到第三重循環。
第三重循環,book3的值拿出來,而後return;到第二重循環。
第二重循環,book2的值拿出來,繼續執行約定,i=3.而後在箱子遞增,進入第三重循環。
以此類推,直到第一重循環結束,便可。
class Program { static void Main(string[] args) { Program pro = new Program(3); pro.dfs(1); Console.ReadKey(); } public readonly int[] book; //使用過的集合 public readonly int[] hezi; //排列集合 public readonly int n; //數量 public Program(int numberN) { n = numberN; book = new int[numberN + 1]; hezi = new int[numberN + 1]; } //深度優先搜索(Depth First Search,DFS) void dfs(int t, int step = 1) { //step是盒子,若是盒子大於傳遞過來數量,那麼進行打印 if (step == n + 1) { //分別打印盒子裏的數據 for (int i = 1; i <= n; i++) { Console.Write(hezi[i]); } Console.WriteLine(""); //返回跳出遞歸 return; } //123都試一遍 for (int i = 1; i <= n; i++) { //試1,2,3那個有 if (book[i] == 0) { hezi[step] = i; //放入盒子 book[i] = 1; //記錄 dfs(t + 1, step + 1); //執行下一個盒子 book[i] = 0; //撤銷記錄 } } //此時會進行跳出操做,如上例子不包括打印是三重循環。 //第三重輸出後撤回第三個數,而後跳出到第二重循環。此時第二重循環是執行到2的,當跳出之後會將2撤回,而且進行3的循環。 //這時會輸出3,而後進入三重循環,輸出2 return; } }
將不一樣的走法,表明不一樣的可能性。當前走的點,若是與終點吻合,當作邊界。執行尋路的時候,須要判斷不是障礙物,而且沒有走過
class Program { static void Main(string[] args) { Program pro = new Program(); Console.ReadKey(); } public Dictionary<string, string> text = new Dictionary<string, string>(); public List<string> lx = new List<string>(), lx2 = new List<string>(); public int h, l, p, q, min = int.MaxValue; public int[,] a, book; public Program() { //設置行走對應名稱 text.Add("0,1", "向右"); text.Add("1,0", "向下"); text.Add("0,-1", "向左"); text.Add("-1,0", "向上"); text.Add("1,1", "右下"); text.Add("1,-1", "左下"); text.Add("-1,1", "左上"); text.Add("-1,-1", "右上"); //設置邊界 h = 5; l = 4; //設置已走路徑 book = new int[h + 1, l + 1]; //設置迷宮 a = new int[h + 1, l + 1]; int[,] b = { { 0, 0, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 1 } }; for (int i = 0; i < h; i++) { for (int j = 0; j < l; j++) { a[i + 1, j + 1] = b[i, j]; } } //設置起點,標記在此起點 book[1, 1] = 1; //設置終點 p = 5; q = 3; //進行尋路 dfs(1, 1, 0); Console.WriteLine(min); int oldx = 1, oldy = 1; foreach (var item in lx2) { string[] s = item.Split(','); int newx = int.Parse(s[0]), newy = int.Parse(s[1]); Console.WriteLine(text[(newx - oldx) + "," + (newy - oldy)]); oldy = newy;oldx = newx; } } //深度優先搜索(Depth First Search,DFS) void dfs(int x, int y, int step) { //定義向右、向下、向左、向上的移動。左側爲x軸,右側爲y軸 int[,] next = new int[8, 2] { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } }; int tx, ty, k; //判斷邊界,是否到達 if (x == p && y == q) { if (step < min) { min = step; lx2 = lx.ToList(); } return; } //判斷每一種可能,枚舉4種走法 for (k = 0; k <= 7; k++) { //計算下一步的座標 tx = x + next[k, 0]; ty = y + next[k, 1]; //判斷是否越界,超過邊界 if (tx < 1 || tx > h || ty < 1 || ty > l) { continue; } //不是障礙物,而且沒有走過 if (a[tx, ty] == 0 && book[tx, ty] == 0) { book[tx, ty] = 1; //記錄路線 lx.Add(tx + "," + ty); //繼續下一步 dfs(tx, ty, step + 1); book[tx, ty] = 0; lx.Remove(tx + "," + ty); } } return; } }
廣度優先(Breadth First Search,BFS)也稱寬度優先搜索。在迷宮的例子中,也可使用廣度優先來實現。
在深度優先裏,是遍歷了全部的行走可能性,使用函數遞歸實現。廣度優先是經過一層一層擴展的方法來找到目標點的。
class note { internal int x; //橫座標 internal int y; //縱座標 internal int s; //步數 internal note f; //父節點編號 } private static void BFS() { int h = 5, l = 4, p = 4, q = 3, flag = 0, tx, ty; int[,] a = new int[h + 1, l + 1], book = new int[h + 1, l + 1]; //設置步數列隊 Queue<note> que = new Queue<note>(); DiTu(h, l, a); //繪製地圖 //定義向右、向下、向左、向上的移動。左側爲x軸,右側爲y軸 int[,] next = new int[4, 2] { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } }; //起始位置加入隊列 que.Enqueue(new note() { x = 1, y = 1, s = 0 }); book[1, 1] = 1; while (que.Count != 0) { //枚舉四個方向 for (int k = 0; k < 4; k++) { tx = que.Peek().x + next[k, 0]; ty = que.Peek().y + next[k, 1]; //判斷是否邊界 if (tx < 1 || tx > h || ty < 1 || ty > l) continue; //判斷是否障礙物或者已在路徑中 if (a[tx, ty] == 0 && book[tx, ty] == 0) { //標記走過 book[tx, ty] = 1; //插入新的點到隊列中 que.Enqueue(new note { x = tx, y = ty, f = que.Peek(), s = que.Peek().s + 1 }); } if (tx == p && ty == q) { flag = 1; break; } } if (flag == 1) break; que.Dequeue(); } while (que.Count > 1) { que.Dequeue(); } Console.WriteLine(que.Peek().s); List<note> resultList = new List<note>(); note result = que.Peek(); while (result != null) { resultList.Add(result); result = result.f; if (result.f == null) break; } resultList.Reverse(); foreach (var item in resultList) { Console.WriteLine(item.x + "-" + item.y); } } private static void DiTu(int h, int l, int[,] a) { //繪製地圖 int[,] b = { { 0, 0, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 1 } }; for (int i = 0; i < h; i++) { for (int j = 0; j < l; j++) { a[i + 1, j + 1] = b[i, j]; } } }
深度優先遍歷的主要思想是:
首先以一個未被訪問過的頂點做爲起始頂點,沿當前頂點的邊走到未訪問過的頂點。
當沒有未訪問過的頂點,則返回上一個頂點,直到全部的頂點都被訪問過。
關於存放無向圖,點邊。可使用二維數組來存放。1 表示有邊,0 表示自己位置,∞ 表示沒有邊。
這種存儲圖的方式稱爲:圖的鄰接矩陣存儲法
下面用代碼來解決,深度優先遍歷圖表。順序爲 1 2 4 3 5
public Program() { //初始化二維矩陣 for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { if (i == j) { e[i, j] = 0; } else { e[i, j] = int.MaxValue; } } } //讀入頂點之間的邊 e[1, 2] = 1; e[2, 1] = 1; e[1, 3] = 1; e[3, 1] = 1; e[1, 5] = 1; e[5, 1] = 1; e[2, 4] = 1; e[4, 2] = 1; e[3, 5] = 1; e[5, 3] = 1; //從1號城市出發 book[1] = 1; dfs(1); } int[] book = new int[101]; int[,] e = new int[101, 101]; int sum; void dfs(int cur) { Console.Write(cur + " "); sum++; int i; if (sum == 5) { return; } for (i = 1; i <= 5; i++) { if (e[cur, i] == 1 && book[i] == 0) { book[i] = 1; dfs(i); } } return; }
上圖若是使用廣度優先遍歷,那麼順序爲 1 2 3 5 4
首先找起點,將1號起點放入隊列,而後將2,3,5號依次放入隊列中。在將2號頂點相鄰的4號,放入隊列。
public Program() { //初始化二維矩陣 for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { if (i == j) { e[i, j] = 0; } else { e[i, j] = int.MaxValue; } } } //讀入頂點之間的邊 e[1, 2] = 1; e[2, 1] = 1; e[1, 3] = 1; e[3, 1] = 1; e[1, 5] = 1; e[5, 1] = 1; e[2, 4] = 1; e[4, 2] = 1; e[3, 5] = 1; e[5, 3] = 1; //從1號城市出發 book[1] = 1; dfs(1); } int[] book = new int[101]; int[,] e = new int[101, 101]; void dfs(int cur) { int head = 1; IList<int> que = new List<int>(); //放入頂點 que.Add(cur); while (head <= 5) { cur = que[head - 1]; for (int i = 1; i <= 5; i++) //從1~n依次嘗試 { //判斷是否有邊,而且是否訪問過 if (e[cur, i] == 1 && book[i] == 0) { //若是從頂點cur到頂點i有邊,而且頂點i沒有被訪問過,則入隊 que.Add(i); book[i] = 1; } //判斷邊界 if (que.Count >= 5) { break; } } head++; } foreach (var item in que) { Console.Write(item.ToString() + " "); } }
城市地圖,實際上就是圖遍歷的一種應用。深度優先遍歷,就能夠實現城市地圖功能。
須要制定地址,公路,路線。這三個數據,就跟圖表中的點,邊相似。
int min = int.MaxValue, n; int[] book; int[,] e; public Program() { n = 5; book = new int[n + 1]; e = new int[9, 9]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) { e[i, j] = 0; } else { e[i, j] = int.MaxValue; } } } SetE(1, 2, 2); SetE(1, 5, 10); SetE(2, 3, 3); SetE(2, 5, 7); SetE(3, 1, 4); SetE(3, 4, 4); SetE(4, 5, 5); SetE(5, 3, 3); book[1] = 1; dfs(1, 0); Console.WriteLine(min); } private void SetE(int a, int b, int c) { e[a, b] = c; e[b, a] = c; //單向路則註釋此行 } void dfs(int cur, int dis) { if (dis > min) return; if (cur == n) { if (dis < min) min = dis; return; } for (int j = 1; j <= n; j++) { if (e[cur, j] != int.MaxValue && book[j] == 0) { book[j] = 1; dfs(j, dis + e[cur, j]); book[j] = 0; } } return; }
若是要求到達目的地,最少換乘。就可使用廣度優先,來實現此功能。廣度優先更適用於全部邊都同樣長。
struct note { public int x; //城市編號 public int s; //起色次數 }; public Program() { Queue<note> que = new Queue<note>(); int[] book = new int[50]; int n = 5, m = 7, start = 1, end = 5, cur, flag = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) e[i, j] = 0; else e[i, j] = int.MaxValue; } } SetE(1, 2); SetE(1, 3); SetE(2, 3); SetE(2, 4); SetE(3, 4); SetE(3, 5); SetE(4, 5); que.Enqueue(new note { x = start, s = 0 }); book[start] = 1; int title = 2; while (que.Count > 0) { cur = que.Peek().x; for (int j = 1; j <= n; j++) { if (e[cur, j] != int.MaxValue && book[j] == 0) { que.Enqueue(new note { x = j, s = que.Peek().s + 1 }); book[j] = 1; title++; } if (que.Count > 0 && que.AsEnumerable().ElementAt(que.Count - 1).x == end) { flag = 1; break; } } if (flag == 1) break; que.Dequeue(); } Console.WriteLine(que.AsEnumerable().ElementAt(que.Count - 1).s); } int[,] e = new int[51, 51]; private void SetE(int a, int b) { e[a, b] = 1; e[b, a] = 1; }
多源最短路徑,有Robert W.Floyd(羅伯特.弗洛伊德)與1962年發表。
單向公路,求兩個城市之間最短路徑。也就是兩個點之間的最短路徑。可使用圖的鄰接矩陣存儲法來表示。
若是要讓任意兩點之間的距離變短,只能引用第三個點(頂點K),並經過k進行中轉。這樣才能縮短原來從頂點a到b的路徑。
中轉點k,有時候不僅經過一個點,而是通過兩個點或更多的點。
若是隻容許通過1號頂點,求任意兩點之間的最短路徑。只須要判斷:e[i,1] + e[1,j] 是否比 e[i,j] 小,便可。
也就是 1號點到b的距離 + a到1號點的距離 < a到b點的距離。
以此類推,通過1 和 2 兩個頂點的寫法,以下
public Program() { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (e[i, j] > e[i, 1] + e[1, j]) e[i, j] = e[i, 1] + e[1, j]; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (e[i, j] > e[i, 2] + e[2, j]) e[i, j] = e[i, 2] + e[2, j]; } } }
綜上所示,若是容許全部頂點做爲中轉,那麼Floyd-Warshall算法核心代碼以下
public Program() { for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (e[i, j] > e[i, k] + e[k, j]) { e[i, j] = e[i, k] + e[k, j]; } } } } }
單源最短路徑,由Edsger Wybe Dijkstra與1959年提出
指定一個點到其他各個頂點的最短路徑,也須要使用二維數組e來存儲頂點之間的關係。
還須要用一個一維數組dis來存儲1號頂點到其他各個頂點的初始路程,將此時dis數組中的值稱爲最短路徑的「估計值」
接下來經過估計值進行發散搜索,那麼1點到3點的距離則爲
dis[3] = 12,dis[2] + e[2,3] = 1+9=10,dis[3] > dis[3] + e[2,3]。所以要更新dis[3]的值。這個過程叫「鬆弛」
這個時候由於dis[3]已經肯定最短路徑,那麼變爲「肯定值」
Dijkstra算法的主要思想:每次找到離源點最近的一個點,而後以該點爲中心進行擴展,最終獲得全部點的最短路徑。步驟以下:
1.將全部的頂點分爲兩部分:已知最短路徑的頂點集合P和未知最短路徑的頂點集合Q。使用book數組記錄那些點在集合P中。若是book[i]爲1則表示這個頂點在集合P中,若是book[i]爲0則表示這個頂點在集合Q中。
2.設置源點s到本身的最短路徑爲0,即dis[s] = 0、若存在有源點能直接到達的頂點i,則把dis[i] 設爲 e[s,j]。同時把全部其餘的頂點的最短路徑設爲∞。
3.在集合Q的全部頂點中選擇一個離源點最近的頂點u,加入到集合P中。並考察全部以點u爲起點的邊,對每一條邊進行鬆弛操做。例如存在一條從u到v的邊,那麼能夠經過將邊u->v添加到尾部來拓展一條從s到v的路徑,這條路徑的長度是dis[u] + e[u,v]。若是這個值比目前已知的dis[v] 值要小,咱們可使用新值來代替當前dis[v]中的值。
4.重複第三步,若是集合Q爲空,算法結束。
public Program() { int[,] e = new int[10, 10]; int[] dis = new int[10], book = new int[10]; int n = 6; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) e[i, j] = 0; else e[i, j] = int.MaxValue; } } e[1, 2] = 1; e[1, 3] = 12; e[2, 3] = 9; e[2, 4] = 3; e[3, 5] = 5; e[4, 3] = 4; e[4, 5] = 13; e[4, 6] = 15; e[5, 6] = 4; for (int i = 1; i <= n; i++) { dis[i] = e[1, i]; } for (int i = 1; i <= n; i++) { book[i] = 0; } book[1] = 1; //Dijkstra算法核心語句 int min = 0, u = 0; for (int i = 1; i <= n - 1; i++) { min = int.MaxValue; for (int j = 1; j <= n; j++) { if (book[j] == 0 && dis[j] < min) { min = dis[j]; u = j; } } book[u] = 1; for (int v = 1; v <= n; v++) { if (e[u, v] < int.MaxValue) { if (dis[v] > dis[u] + e[u, v]) dis[v] = dis[u] + e[u, v]; } } } for (int i = 1; i <= n; i++) { Console.WriteLine(dis[i]); } }
Dijkstra算法雖然很好,可是沒法解決邊的權值爲負數的問題。而BellmanFord算法不管從思想上仍是代碼上都堪稱完美的最短路算法。
核心代碼只有4行,而且能夠完美的解決帶有負權邊的圖。
for (int k = 1; k <= n - 1; k++) { for (int i = 1; i <= m; i++) { if (dis[v[i]] > dis[u[i]] + w[i]) { dis[v[i]] = dis[u[i]] + w[i]; } } }
外循環一共循環了n-1次,n爲頂點的個數。內循環循環了m次,m爲邊的個數,枚舉每一條邊。dis數組做用和Dijkstra算法同樣,用來記錄源點到各個頂點的最短路徑。
u,v,m三個數組用來記錄邊的信息。例如第i條邊存儲在u[i],v[i],w[i]中,表示從頂點u[i]到頂點v[i]這條邊,權值爲w[i] 。
最後的if 和Disjkstra的鬆弛操做同樣,把全部的邊都鬆弛一遍。
由於最短路徑上最多有n-1條邊,所以BellmanFord算法最多有N-1個階段。在每個階段,咱們對每一條邊都要執行鬆弛操做。
每一次鬆弛操做,就會有一些頂點已經求得最短路,將估計值變爲肯定值。此後這些頂點的最短路會一直不變。
在前K個階段結束後,就已經找到了從源點發出到達各個頂點的最短路。直到進行完n-1個階段後,辨得出了最多通過n-1條邊的最短路。
public Program() { int[] dis = new int[10], v = new int[10], u = new int[10], w = new int[10]; int n = 5, m = 5, check, flag; Queue<string> que = new Queue<string>(); que.Enqueue("2,3,2"); que.Enqueue("1,2,-3"); que.Enqueue("1,5,5"); que.Enqueue("4,5,2"); que.Enqueue("3,4,3"); //讀入邊 for (int i = 1; i <= m; i++) { string[] result = que.Dequeue().Split(','); u[i] = int.Parse(result[0]); v[i] = int.Parse(result[1]); w[i] = int.Parse(result[2]); } //初始化數據 for (int i = 1; i <= n; i++) { dis[i] = 99999999; } dis[1] = 0; for (int k = 1; k <= n - 1; k++) { check = 0; //第一輪鬆弛 for (int i = 1; i <= m; i++) { if (dis[v[i]] > dis[u[i]] + w[i]) { dis[v[i]] = dis[u[i]] + w[i]; check = 1; } } //檢查數組dis是否有更新 if (check == 0) break; } //檢查負權迴路 flag = 0; for (int i = 1; i <= m; i++) { if (dis[v[i]] > dis[u[i]] + w[i]) flag = 1; } if (flag == 1) Console.WriteLine("此圖含有負權迴路"); else { for (int i = 1; i <= n; i++) { Console.Write(dis[i] + " "); } } }
二叉樹是一種特殊的樹。二叉樹的特色是每一個結點最多有左右兩個子節點。
二叉樹的使用範圍最廣,一棵多叉樹也能夠轉換爲二叉樹。二叉樹中還有兩種特殊的二叉樹,叫作滿二叉樹和徹底二叉樹。
滿二叉樹指二叉樹中每一個結點都有兩個子節點。徹底二叉樹指除了最右邊位置上有一個或者幾個結點缺乏外,其餘事豐滿的。
堆是一種特殊的徹底二叉樹。若是全部父節點都比子節點小,咱們成爲最小堆。反之,成爲最大堆。
假若有14個數,咱們找出這14個數中最小的數。最簡單的辦法就是遍歷全部的數,用一個循環就能夠解決。
如今咱們須要刪除其中最小的數,並添加一個新數。再求14個數中最小的一個數。就可使用最小堆。
當一個數被放置到堆頂時,若是不符合最小堆的特性,則須要將這個數向下調整,直到找到合適的位置爲止。
#