python常用演算法學習(4)——資料結構
資料結構簡介
1,資料結構
資料結構是指相互之間存在著一種或多種關係的資料元素的集合和該集合中資料元素之間的關係組成。簡單來說,資料結構就是設計資料以何種方式組織並存貯在計算機中。比如:列表,集合與字典等都是一種資料結構。而之前已經學習過列表,字典,集合,元組等,這裡就簡單說一下不再贅述。
N.Wirth:“程式=資料結構+演算法”
資料:資料即資訊的載體,是能夠輸入到計算機中並且能被計算機識別,儲存和處理的符號總稱。
資料元素:資料元素是資料的基本單位,又稱之為記錄(Record),一般,資料元素若由若干基本型(或稱欄位,域,屬性)組成。
2,資料結構的分類
資料結構按照其邏輯結構可分為線性結構,樹結構,圖結構
- 線性結構:資料結構中的元素存在一對一的相互關係
- 樹結構:資料結構中的元素存在一對多的相互關係
- 圖結構:資料結構中的元素存在多對多的相互關係
3,儲存結構
1,特點
- 1,是資料的邏輯結構在計算機儲存器中的映象(或表示)
- 2,儲存結構是通過計算機程式來實現,因而是依賴於具體的計算機語言的
2,儲存結構分類
- 1,順序儲存(Sequential Storage):將資料結構中各元素按照其邏輯順序存放於儲存器一片連續的儲存空間中。
- 2,鏈式儲存(Linkend Storage):將資料結構中各元素分佈到儲存器的不通電,用記錄下一個結點位置的方式建立他們之間的聯絡,由此得到的儲存結構為鏈式儲存結果。
- 3,索引儲存(Indexed Storage):在儲存資料的同時,建立一個附加的索引表,即索引儲存結構=資料檔案+索引表。
常用的資料結構
1,列表
列表(其他語言稱為陣列,但是陣列和Python的列表還是有區別的)是一種基本資料型別。
列表,在Python中稱為list,使用中括號包含,其中內部元素使用逗號分隔,內部元素可以是任何型別包含空。
1.1 陣列和列表不同點
- 1,陣列元素型別要相同,而Python列表不需要
- 2,陣列長度固定,而Python列表可以增加
Python列表操作方法請參考此部落格:https://www.cnblogs.com/wj-1314/p/8433116.html
2,元組
元組,Python中類為tuple。使用小括號包含,內部元素使用逗號分隔,可為任意值。與列表不同之處為,其內部元素不可修改,而且不能做刪除,更新操作。
注意:當元組中元素只有一個時,結尾要加逗號,若不加逗號,Python直譯器都將解釋成元素本身的型別,而非元組型別。
舉個例子:
>>> a = (1,2,3) >>> b = ('1',[2,3]) >>> c = ('1','2',(3,4)) >>> d = () >>> e = (1,)
2.1 元組的操作
通過下標操作
- 通過小標來獲取元素值,使用方法同列表
- 切片的處理,使用方法同列表
- 不可通過下標做刪除和更新操作
如下,做出更改和刪除會報異常:
>>> c[0] = 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> del c[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object doesn't support item deletion
2.2 運算子及內建函式操作
元組本身是不可變,但是可以通過 + 來構成新的元組。
>>> a (1, 2, 3) >>> b ('1', [2, 3]) >>> a + b (1, 2, 3, '1', [2, 3])
可以通過使用內建函式 len 獲取元組長度,可以使用 * 元素來實現元素的重複
>>> a = (1,2,3) >>> len(a) 3 >>> a*3 (1, 2, 3, 1, 2, 3, 1, 2, 3)
元組也支援 in 和 not in 成員運算子。
3,集合
集合用於包含一組無序的物件,與列表和元組不同,集合是無序的,也無法通過數字進行索引。此外,集合中的元素不能重複。set和dict一樣,只是沒有 value,相當於 dict 的 key 集合,由於 dict 的 key 是不重複的,且 key 是不可變物件,因此 set 有如下特性:
- 1,不重複,(互異性),也就是說集合是天生去重的
- 2,元素為不可變物件,(確定性,元素必須可 hash)
- 3,集合的元素沒有先後之分,(無序性)
Python的集合與數學中的集合一樣,也有交集,並集和合集。
舉例如下:
>>> s1 = {1,2,3,4,5} >>> s2 = {1,2,3} >>> s3 = {4,5} >>> s1&s2 # 交集 {1, 2, 3} >>> s1|s2 # 並集 {1, 2, 3, 4, 5} >>> s1 - s2 # 差差集 {4, 5} >>> s3.issubset(s1) # s3 是否為s1 的子集 True >>> s1.issuperset(s2) # s1 是否為 s2 的超集 True
更多集合相關的操作參考部落格:https://www.cnblogs.com/wj-1314/p/8423273.html
4,棧
棧(Stack)是一個數據集合,可以理解為只能在一段進行插入或刪除操作的列表。
棧的特點:後進先出 LIFO(Last-in, First-out)
棧的概念有:棧頂,棧底
棧的基本操作:
- 進棧(壓棧):push
- 出棧:pop
- 取棧頂:gettop
對棧的理解,就像是一摞書,只能從上面放和取。
4.1 棧的實現
使用一般列表結構即可實現棧:
- 進棧:li.append
- 出棧:li.pop
- 取棧頂:li[-1]
那現在我們寫一個類實現它:
class Stack: def __init__(self): self.stack = [] def push(self, element): self.stack.append(element) def pop(self): # 移除列表中的一個元素(預設最後一個),並返回該元素的值 return self.stack.pop() def get_top(self): if len(self.stack) > 0: return self.stack[-1] else: return None stack = Stack() stack.push(1) stack.push(2) print(stack.pop()) # 2
4.2 棧的應用——括號匹配問題
我們可以通過棧來解決括號匹配的問題,就是解決IDE實現括號未匹配成功報錯的問題。
程式碼:
class Stack: def __init__(self): self.stack = [] def push(self,element): return self.stack.append(element) def pop(self): return self.stack.pop() def get_top(self): if len(self.stack) > 0: return self.stack[-1] else: return None def is_empty(self): return len(self.stack) == 0 def brace_match(s): match = {'}': '{', ']': '[', ')': '('} stack = Stack() for ch in s: if ch in {'(', '[', '{'}: stack.push(ch) else: if stack.is_empty(): return False elif stack.get_top() == match[ch]: stack.pop() else: return False if stack.is_empty(): return True else: return False print(brace_match('{[{()}]}'))
5,佇列
佇列(Queue)是一個數據集合,僅允許在列表的一端進行插入,另一端進行刪除。進行插入的一端稱為隊尾(rear),插入動作稱為進隊或者入隊。進行刪除的一端稱為對頭(front),刪除動作稱為出隊。
佇列的性質:先進先出(First-in , First-out)
佇列可以併發的派多個執行緒,對排列的執行緒處理,並切每個需要處理執行緒只需要將請求的資料放入佇列容器的記憶體中,執行緒不需要等待,當排列完畢處理完資料後,執行緒在準時來取資料即可,請求資料的執行緒只與這個佇列容器存在關係,處理資料的執行緒down掉不會影響到請求資料的執行緒,佇列會派給其他執行緒處理這份資料,它實現瞭解耦,提高效率。佇列內會有一個順序的容器,列表與這個容器是有區別的,列表中資料雖然是排列的,但資料被取走後還會保留,而佇列中這些容器的資料被取後將不會保留。當必須在多個執行緒之間安全的交換資訊時,佇列線上程程式設計中特別有用。
5.1 佇列的實現
佇列能否用列表簡單實現?為什麼?
環形佇列的實現方式
環形佇列:當隊尾指標 front == MaxSize + 1 時,再前進一個位置就自動到0。
- 隊首指標前進1: front = (front + 1) % MaxSize
- 隊尾指標前進1: rear = (rear + 1) % MaxSize
- 隊空條件: front == rear
- 隊滿條件: (rear + 1) % MaxSize == front
class Queue: def __init__(self, size): self.queue = [0 for _ in range(size)] self.size = size self.rear = 0 # 隊尾指標是進隊的 self.front = 0 # 對首指標是出隊的 def push(self, element): if not self.is_filled(): rear = (self.rear + 1) % self.size self.queue[self.rear] = element else: raise IndexError("Queue is filled") def pop(self): if not self.is_empty(): self.front = (self.front + 1) % self.size return self.queue[self.front] else: raise IndexError("Queue is empty") # 判斷隊空 def is_empty(self): return self.rear == self.front # 判斷隊滿 def is_filled(self): return (self.rear + 1) % self.size == self.front q = Queue(5) for i in range(4): q.push(i) print(q.pop())
from collections import deque q = deque() q.append(1) # 隊尾進隊 print(q.popleft()) # 對首出隊 # 用於雙向佇列 q.appendleft(1) # 隊首進隊 print(q.pop()) #隊尾出隊
雙向佇列的完整用法:
# _*_coding:utf-8_*_ # 建立雙向佇列 from collections import deque d = deque() # append(往右邊新增一個元素) d.append(1) d.append(2) print(d) # deque([1, 2]) # appendleft(往左邊新增一個元素) d.appendleft(11) d.appendleft(22) print(d) # deque([22, 11, 1, 2]) # clear清空佇列 d.clear() print(d) # deque([]) # 淺copy copy d.append(1) new_d = d.copy() print(new_d) # deque([1]) # count(返回指定元素的出現次數) d.append(1) d.append(1) print(d) # deque([1, 1, 1]) print(d.count(1)) # 3 # extend(從佇列右邊擴充套件一個列表的元素) d.append(2) d.extend([3, 4, 5]) print(d) # deque([1, 1, 1, 2, 3, 4, 5]) # extendleft(從佇列左邊擴充套件一個列表的元素) d.extendleft([3, 4, 5]) print(d) # deque([5, 4, 3, 1, 1, 1, 2, 3, 4, 5]) # index(查詢某個元素的索引位置) d.clear() d.extend(['a', 'b', 'c', 'd', 'e']) print(d) print(d.index('e')) print(d.index('c', 0, 3)) # 指定查詢區間 ''' deque(['a', 'b', 'c', 'd', 'e']) 4 2 ''' # insert(在指定位置插入元素) d.insert(2, 'z') print(d) # deque(['a', 'b', 'z', 'c', 'd', 'e']) # pop(獲取最右邊一個元素,並在佇列中刪除) x = d.pop() print(x) print(d) ''' e deque(['a', 'b', 'z', 'c', 'd']) ''' # popleft(獲取最左邊一個元素,並在佇列中刪除) print(d) x = d.popleft() print(x) print(d) ''' deque(['a', 'b', 'z', 'c', 'd']) a deque(['b', 'z', 'c', 'd']) ''' # remove(刪除指定元素) print(d) d.remove('c') print(d) ''' deque(['b', 'z', 'c', 'd']) deque(['b', 'z', 'd']) ''' # reverse(佇列反轉) print(d) d.reverse() print(d) ''' deque(['b', 'z', 'd']) deque(['d', 'z', 'b']) ''' # rotate(把右邊元素放到左邊) d.extend(['a', 'b', 'c', 'd', 'e']) print(d) d.rotate(2) # 指定次數,預設1次 print(d) ''' deque(['d', 'z', 'b', 'a', 'b', 'c', 'd', 'e']) deque(['d', 'e', 'd', 'z', 'b', 'a', 'b', 'c']) '''
Python四種類型的佇列
Queue:FIFO 即 first in first out 先進先出
LifoQueue:LIFO 即 last in first out 後進先出
PriorityQueue:優先佇列,級別越低,越優先
deque:雙邊佇列
匯入三種佇列,包
from queue import Queue,LifoQueue,PriorityQueue
Queue:先進先出佇列
#基本FIFO佇列 先進先出 FIFO即First in First Out,先進先出 #maxsize設定佇列中,資料上限,小於或等於0則不限制,容器中大於這個數則阻塞,直到佇列中的資料被消掉 q = Queue(maxsize=0) #寫入佇列資料 q.put(0) q.put(1) q.put(2) #輸出當前佇列所有資料 print(q.queue) #刪除佇列資料,並返回該資料 q.get() #輸也所有佇列資料 print(q.queue) # 輸出: # deque([0, 1, 2]) # deque([1, 2])
LifoQueue:後進先出佇列:
#LIFO即Last in First Out,後進先出。與棧的類似,使用也很簡單,maxsize用法同上 lq = LifoQueue(maxsize=0) #佇列寫入資料 lq.put(0) lq.put(1) lq.put(2) #輸出佇列所有資料 print(lq.queue) #刪除隊尾資料,並返回該資料 lq.get() #輸出佇列所有資料 print(lq.queue) #輸出: # [0, 1, 2] # [0, 1]
優先佇列
# 儲存資料時可設定優先順序的佇列 # 優先順序設定數越小等級越高 pq = PriorityQueue(maxsize=0) #寫入佇列,設定優先順序 pq.put((9,'a')) pq.put((7,'c')) pq.put((1,'d')) #輸出隊例全部資料 print(pq.queue) #取隊例資料,可以看到,是按優先順序取的。 pq.get() pq.get() print(pq.queue) #輸出: [(9, 'a')]
雙邊佇列
#雙邊佇列 dq = deque(['a','b']) #增加資料到隊尾 dq.append('c') #增加資料到隊左 dq.appendleft('d') #輸出佇列所有資料 print(dq) #移除隊尾,並返回 print(dq.pop()) #移除隊左,並返回 print(dq.popleft()) #輸出: deque(['d', 'a', 'b', 'c']) c d
6,連結串列
當我們儲存一大波資料時,我們很多時候是使用陣列,但是當我們執行插入操作的時候就是非常麻煩,比如,有一堆資料1,2,3,5,6,7 我們要在3和5之間插入4,如果用陣列,我們會怎麼辦?當然是將5之後的資料往後退一位,然後再插入4,這樣非常麻煩,但是如果用連結串列,就直接在3和5之間插入4即可。
所以連結串列的節點結構如下:
data為自定義的資料,next為下一個節點的地址。head儲存首位節點的地址。
首先可以看一個小連結串列,定義連結串列:
class Node: def __init__(self, item): self.item = item self.next = None a = Node(1) b = Node(2) c = Node(3) # 通過next 將 a,b,c聯絡起來 a.next = b b.next = c # 列印a的下一個的下一個結果是什麼 print(a.next.next.item) # 3
當然,我們不可能這樣next來呼叫連結串列,使用迴圈等,下面繼續學習。
連結串列永遠有一個頭結點,head
頭插法和尾插法的程式碼如下:
class Node: def __init__(self, item): self.item = item self.next = None # 頭插法 # 這裡我們使用 li 進行迴圈插入 def create_linklist_head(li): head = Node(li[0]) for element in li[1:]: node = Node(element) node.next = head # 將頭結點 給剛插入的節點 head = node # 然後將插入的節點設定為頭結點 return head # 尾插法 def create_linklist_tail(li): head = Node(li[0]) tail = head for element in li[1:]: node = Node(element) tail.next = node tail = node return head def print_linklist(lk): while lk: print(lk.item, end=',') lk = lk.next print('*********') lk = create_linklist_head([1, 2, 3, 4]) # print(lk.item) # 4 print_linklist(lk) # 4,3,2,1,********* lk = create_linklist_tail([1, 2, 3, 4, 5]) print_linklist(lk) # 1,2,3,4,5,
插入如下,非常簡單,所以時間複雜度也低。
6.1 關於連結串列的方法
1,判斷是否為空:isEmpty()
def isEmpty(self): return (self.length == 0)
2,增加一個節點(在連結串列尾新增):append()
def append(self, dataOrNode): item = None if isinstance(dataOrNode, Node): item = dataOrNode else: item = Node(dataOrNode) if not self.head: self.head = item self.length += 1 else: node = self.head while node._next: node = node._next node._next = item self.length += 1
3,刪除一個節點:delete()
# 刪除一個節點之後,記得把連結串列長度減一 def delete(self, index): if self.isEmpty(): print("this chain table is empty") return if index < 0 or index >= self.length: print('Error: out of index') return # 要注意刪除第一個節點的情況,如果有空的頭節點就不用這樣 if index == 0: self.head = self.head._next self.length -= 1 return # prev 為儲存前導節點 # node 為儲存當前節點 # 當j 與index 相等時就相當於找到要刪除的節點 j = 0 node = self.head prev = self.head while node._next and j < index: prev = node node = node._next j += 1 if j == index: prev._next = node._next self.length -= 1
4,修改一個節點:update()
def update(self, index, data): if self.isEmpty() or index < 0 or index >= self.length: print 'error: out of index' return j = 0 node = self.head while node._next and j < index: node = node._next j += 1 if j == index: node.data = data
5,查詢一個節點:getItem()
def getItem(self, index): if self.isEmpty() or index < 0 or index >= self.length: print "error: out of index" return j = 0 node = self.head while node._next and j < index: node = node._next j += 1 return node.data
6,查詢一個節點的索引:getIndex()
def getIndex(self, data): j = 0 if self.isEmpty(): print "this chain table is empty" return node = self.head while node: if node.data == data: return j node = node._next j += 1 if j == self.length: print "%s not found" % str(data) return
7,插入一個節點:insert()
def insert(self, index, dataOrNode): if self.isEmpty(): print "this chain tabale is empty" return if index < 0 or index >= self.length: print "error: out of index" return item = None if isinstance(dataOrNode, Node): item = dataOrNode else: item = Node(dataOrNode) if index == 0: item._next = self.head self.head = item self.length += 1 return j = 0 node = self.head prev = self.head while node._next and j < index: prev = node node = node._next j += 1 if j == index: item._next = node prev._next = item self.length += 1
8,清空連結串列:clear()
def clear(self): self.head = None self.length = 0
6.2 雙鏈表
6.3 連結串列複雜度分析
順序表(列表/陣列)與連結串列
- 按照元素值查詢
- 按下標查詢
- 在某元素後插入
- 刪除某元素
連結串列與順序表
- 連結串列在插入和刪除的操作上明顯快於順序表
- 連結串列的記憶體可以更加靈活地分配(可以試利用連結串列重新實現棧和佇列)
- 連結串列這種鏈式儲存的資料結構對樹和圖的結構有很大的啟發性
7,雜湊表(Hash table)
眾所周知,HashMap是一個用於儲存 Key-Value鍵值對的集合,每一個鍵值對也叫Entry,這些鍵值對(Entry)分散儲存在一個數組當中,這個陣列就是HashMap的主幹。
使用雜湊表可以進行非常快速的查詢操作,查詢時間為常數,同時不需要元素排列有序;Python的內建資料型別:字典就是用雜湊表實現的。
Python中的這些東西都是雜湊原理:字典(dictionary)、集合(set)、計數器(counter)、預設字典Defaut dict)、有序字典(Order dict).
雜湊表示一個高效的查詢的資料結構,雜湊表一個通過雜湊函式來計算資料儲存位置的資料結構,通常支援如下操作:
- insert(key, value):插入鍵值對(key, value)
- get(key):如果存在鍵為key的鍵值對則返回其value,否則返回空值
- delete(key):刪除鍵為key的鍵值對
直接定址表
直接定址技術缺點:
- 當域U很大時,需要消耗大量記憶體,很不實際
- 如果域U很大而實際出現的key很少,則大量空間被浪費
- 無法處理關鍵字不是數字的情況
雜湊
直接定址表:key為k的元素放到k位置上
改進直接定址表:雜湊(Hashing)
- 構建大小為m的定址表T
- key為k的元素放到 h(k)位置上
- h(k) 是一個函式,其將域U對映到表 T[0, 1, ... , m-1]
雜湊表
雜湊表(Hash Table,又稱為散列表),是一種線性表的儲存結構。雜湊表由一個直接定址表和一個雜湊函式組成。雜湊函式h(k)將元素關鍵字 k 作為自變數,返回元素的儲存下標。
假設有一個長度為7的雜湊表,雜湊函式 h(k)=k%7.元素集合 {14, 22, 3, 5}的儲存方式如下圖:
程式碼如下:
class LinkList: class Node: def __init__(self, item=None): self.item = item self.next = None class LinkListIterator: def __init__(self, node): self.node = node def __next__(self): if self.node: cur_node = self.node self.node = cur_node.next return cur_node.item else: raise StopIteration def __iter__(self): return self def __init__(self, iterable=None): self.head = None self.tail = None if iterable: self.extend(iterable) def append(self, obj): s = LinkList.Node(obj) if not self.head: self.head = s self.tail = s else: self.tail.next = s self.tail = s def extend(self, iterable): for obj in iterable: self.append(obj) def find(self, obj): for n in self: if n == obj: return True else: return False def __iter__(self): return self.LinkListIterator(self.head) def __repr__(self): return "<<" + ",".join(map(str, self)) + ">>" # 類似於集合的結構 class HashTable: def __init__(self, size=101): self.size = size self.T = [LinkList() for i in range(self.size)] def h(self, k): return k % self.size def insert(self, k): i = self.h(k) if self.find(k): print('Duplicated Insert') else: self.T[i].append(k) def find(self, k): i = self.h(k) return self.T[i].find(k)
7.1 雜湊表的應用
1,集合與字典
字典與集合都是通過雜湊表來實現的。比如:
dic_res = {'name':'james', 'age':32, 'gender': 'Man'}
使用雜湊表儲存字典,通過雜湊表將字典的鍵對映為下標,假設 h('name')=3,h('age')=4,則雜湊表儲存為:
[None, 32, None, 'james', 'Man']
如果發生雜湊衝突,則通過拉鍊法或開發定址法解決。
2,md5演算法
MD5(Message-Digest Algorithm 5)曾經是密碼學中常用的雜湊函式,可以把任意長度的資料對映為128位的雜湊值,其曾經包含如下特徵:
- 1,同樣的訊息,其MD5值必定相同;
- 2,可以快速計算出任意給定訊息的MD5值;
- 3,除非暴力的列舉所有可能的訊息,否則不可能從雜湊值反推出訊息本身;
- 4,兩天訊息之間即使只有微小的差別,其對應的MD5值也應該是完全不同,完全不相關的;
- 5,不能再有意義的時間內人工的構造兩個不同的訊息,使其具有相同的MD5值
3,md5
應用舉例:檔案的雜湊值
算出檔案的雜湊值,若兩個檔案的雜湊值相同,則可認為這兩個檔案時相同的,因此:
- 1,使用者可以利用它來驗證下載的檔案是否完整
- 2,雲端儲存服務商可以利用它來判斷使用者要上次的檔案是否已經存在於伺服器上,從而實現秒傳的功能,同時避免儲存過多相同的檔案副本。
4,SHA2演算法
歷史上MD5和SHA-1曾經是使用最為廣泛的 cryptographic hash function,但是隨著密碼學的發展,這兩個雜湊函式的安全性相繼受到了各種挑戰。
因此現在安全性較重要的場合推薦使用 SHA-A等新的更安全的雜湊函式。
SHA-2包含了一系列的雜湊函式:SHA-224, SHA-256, SHA-384,SHA-512,SHA-512/224,SHA-512/256,其對應的雜湊值長度分別為 224, 256, 384 or 512位。
SHA-2 具有和 MD5 類似的性質。
5,SHA2演算法應用舉例
例如在比特幣系統中,所有參與者需要共同解決如下問題:對於一個給定的字串U,給定的目標雜湊值H,需要計算一個字串V,使得 U+ V的雜湊值與H的差小於一個給定值D。此時,只能通過暴力列舉V來進行猜測。首先計算出結果的人可獲得一定獎金。而某人首先計算成功的概率與其擁有的計算量成正比,所以其獲得的獎金的期望值與其擁有的計算量成正比。
棧和佇列的應用——迷宮問題
給出一個二維列表,表示迷宮(0表示通道,1表示圍牆)。給出演算法,求一條走出迷宮的路徑。
將其轉化為陣列如下:
那有兩種思路,一種是使用棧(深度優先搜尋)來儲存當前路徑,也可以稱為回溯法。但是深度優先有一個缺點,就是雖然能找到路徑,但是不能保證是最短路徑,其優點就是簡單,容易理解。
另一種方法就是佇列,
1,棧——深度優先搜尋
回溯法
思路:從一個節點開始,任意找下一個能走的點,當炸不到能走的點時,退回上一個點尋找是否有其他方向的點。
使用棧儲存當前路徑
如下圖所示:
程式碼如下:
# _*_coding:utf-8_*_ maze = [ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 1, 1, 0, 0, 1], [1, 0, 1, 1, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 1, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1, 1, 0, 1], [1, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ] # 這裡我們封裝一個列表,用來表示走迷宮的四個方向 dirs = [ lambda x, y: (x + 1, y), # 表示向上走 lambda x, y: (x - 1, y), # 表示向下走 lambda x, y: (x, y - 1), # 表示向左走 lambda x, y: (x, y + 1), # 表示向右走 ] def maze_path(x1, y1, x2, y2): stack = [] stack.append((x1, y1)) while (len(stack) > 0): curNode = stack[-1] # 當前節點是 stack最後一個位置 if curNode[0] == x2 and curNode[1] == y2: return True # 如果有路則返回TRUE,沒有路則返回FALSE # x,y當前座標,四個方向上下左右分別是:x-1,y x+1,y x, y-1 x,y+1 for dir in dirs: nextNode = dir(curNode[0], curNode[1]) # 如果下一個節點能走 if maze[nextNode[0]][nextNode[1]] == 0: stack.append(nextNode) maze[nextNode[0]][nextNode[1]] = 2 # 2表示已經走過了 break else: # 如果一個都找不到,就需要回退了 maze[nextNode[0]][nextNode[1]] = 2 stack.pop() # 棧頂出棧,也就是回退 else: print("沒有路") return False maze_path(1, 1, 8, 8)
2,佇列——廣度優先搜尋
思路:從下一個節點開始,尋找所有接下來能繼續走的點,繼續不斷尋找,直到找到出口。
使用佇列儲存當前正在考慮的節點
思路圖如下:
或者這樣:
可以通過如下方式理解:
程式碼如下:
# _*_coding:utf-8_*_ from collections import deque maze = [ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 1, 1, 0, 0, 1], [1, 0, 1, 1, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 1, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1, 1, 0, 1], [1, 1, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ] dirs = [ lambda x, y: (x + 1, y), # 表示向上走 lambda x, y: (x - 1, y), # 表示向下走 lambda x, y: (x, y - 1), # 表示向左走 lambda x, y: (x, y + 1), # 表示向右走 ] def print_r(path): real_path = [] # 用來儲存真實的路徑 i = len(path) - 1 # 用i指向path的最後一個下標 while i >= 0: real_path.append(path[i][0:2]) i = path[i][2] real_path.reverse() # 起點座標(x1, y1) 終點座標(x2, y2) def maze_path_queue(x1, y1, x2, y2): queue = deque() path = [] # -1 為當前path的路徑的下標 queue.append((x1, y1, -1)) while len(queue) > 0: # 當佇列不空時迴圈 # append是從右邊加入的,popleft()從左邊出去 cur_Node = queue.popleft() path.append(cur_Node) if cur_Node[0] == x2 and cur_Node[1] == y2: # 如果當前節點等於終點節點,則說明到達終點 print_r(path) return True # 佇列是找四個方向,每個能走就繼續 for dir in dirs: # 下一個方向的節點座標 next_node = dir(cur_Node[0], cur_Node[1]) if maze[next_node[0]][next_node[1]] == 0: queue.append((next_node[0], next_node[1], len(path) - 1)) maze[next_node[0]][next_node[1]] = 2 # 標記為已走過 return False # 如果佇列結束了,空了,還沒找到路徑,則為FALSE maze_path_queue(1, 1, 8, 8)