資料結構 棧求算術表示式_用python描述資料結構
技術標籤:資料結構 棧求算術表示式
pythonData-Structures-and-Algorithms
一、演算法複雜度
O(1)、O(log n),O(n),O(n log n),O(n2),O(n3),O(nn)
計算規則
1.基本迴圈程式:
- 基本操作:複雜度O(1) ,如果是函式呼叫,將其時間複雜度代入,參與整體時間複雜度計算。
- 加法規則(順序複合):兩部分(多部分)的順序複合,複雜度是兩部分的加和。
T(n)=T1(n)+T2(n)=O(T1(n))+O(T2(n)) = O(max()T1(n),T2(n)) - 乘法規則(迴圈結構):迴圈體執行T1(n)次,每次執行要T2(n)時間,那麼:
- 取最大規則(分支結構): 如果演算法是條件分支,兩個分支的時間複雜性分別為 T1(n)和T2(n) ,那麼:
T(n) = O(max()T1(n),T2(n))
2.python的計算代價
- 時間開銷:
- 基本算術運算和邏輯運算是常量時間運算,O(1)
- 組合物件的操作:
複製和切片操作O(n),與長度有關
list,tuple元素訪問和賦值,是O(1)
dict操作比較複雜 - 字串看組合物件來定
- 建立物件餘姚輔助空間和時間,代價都與物件大小有關
- 構造新結構,如list、tuple、set等包含n個元素,O(n)
- dict平均複雜度O(1),最壞情況O(n)
- 空間開銷:
- 一般而言,建立n個元素的資料結構,為O(n)
- python中沒有預設最大元素個數,會自動擴充儲存空間。
- 注意python自動儲存管理系統的影響
二、抽象資料型別ADT
定義一個抽象資料型別,目的是要定義一類計算物件,它們具有某些特定功能,可以在計算中使用。
構造有理數抽象資料型別,如下
ADT Rational: #定義有理數抽象資料型別 Rational(int num,int den) #構造有理數num,den +(Rational r1, Rational r2) #r1+r2 -(Rational r1, Rational r2) #r1-r2 *(Rational r1, Rational r2) #r1*r2 /(Rational r1, Rational r2) #r1/r2
日期抽象資料型別:
ADT Date:
Date(int year,int month,int day) #定義年月日
difference(Date d1,Date d2) #求d1和d2的日期差
plus(Date d,int n) #日期d後n天的日期
num_date(int year,int n) #計算year年第n天的日期
ADT是一種思想,也是一種組織程式的技術:
- 圍繞一類資料定義程式模組
- 模組的介面和實現分離
- 在需要實現的時候,採用合理的技術,選擇合適的機制,實現這種ADT功能,包括具體的資料表示和操作。
三、類
- 類定義和使用:
class <類名>: <語句組>
類物件支援兩種操作:屬性訪問和例項化。 - 例項物件:初始化和使用
使用__init__
方法完成初始化,第一個引數self
,其可以有更多的形式引數。 - 幾點說明:
- 使用
_
,避免方法名字衝突,比如_num
和num
- 如果在一個方法函式裡需要呼叫在同類裡的另一個方法函式,要使用
self.g(...)
,g()
為 類的另一個函式 - 方法函式可以通過
global
和nonlocal
宣告來訪問全域性變數和函式. - 使用
isinstance(obj,cls)
檢查類和物件的關係,檢查obj
物件是否是類cls
的例項
- 解調方法和類方法:
- 在def行前加
@staticmethod
表示靜態方法,不需要self
引數,通過類名或值為例項物件的變數,以屬性引用的方式呼叫靜態函式 - 在def行前加
@classmethod
表示類方法,習慣用cls
引數名
- 類定義的作用於規則: 類定義的識別符號具有區域性作用域,只能在類裡使用。外部通過類名字的屬性引用方式來訪問。
- 私有變數
- 用
_
一個下劃線開頭作為例項物件內部的東西,不應該在類以外訪問他們. - 用
__
開頭,但不結尾,在類外訪問不到這個物件。 - 此外,具有
__add__
的形式表示特殊的魔法方法
- 繼承 在class(object):括號裡表示繼承的父類,使用
issubclass(cls1,cls2)
來檢查cls2是否為cls1 的基類
8.方法查詢: 在派生類裡查詢方法,根據繼承關係來找,直到Attribute Error異常為止。
9.super() super.m1()表示呼叫父類的m1函式,而不是本類的m1.
四、線性表
list和tuple採用了順序表的實現技術。
- 基於下標的高效元素訪問和更新,時間複雜度O(1)
- 允許任意元素加入,表物件的標識(id值)不變
- 要求操作為O(1),且保持順序,只能採用連續表技術,表元素儲存在一塊連續儲存區裡。
- 要求能容納任意多的元素,且保持id不變,採用分離式實現技術
list實現策略:建立空表,分配容納8個元素的儲存區,如果區滿換一塊4倍大的儲存區,如果表很大,改變策略,換儲存區時容量加倍,目前值時5000。
操 作性質:
- len()是O(1)操作
- 元素訪問和賦值,尾端加入和尾端刪除(尾端切片刪除)為O(1),pop()
- 一般位置的元素加入、切片替換、切片刪除、表拼接(extend)是O(n),pop(n)指定位置
- list.reverse()反轉操作,O(n) list.clear()是O(1)去除元素的操作,兩種方法
- 將表的元素計數值設定為0,變為空表。
- 另外分配一個空表用的儲存空間,原儲存區有python直譯器的儲存管理器自動回收。
五、字串
1. python字串相關概念:
- 字串的長度:長度為0成為空串,長度為1的字串。
- 字元在字串勵按順序排列,和線性表一樣。
- 字串相等,說明長度相等,且兩串的對應位置的個字元兩兩相同。
- 字典序:字串的一種序關係。就是從左往右看兩個穿中下標相同的各對字元,相比大小。
- 字串拼接:用+來表示字串拼接
- 字串關係:串s1與串s2中的一個連續片段相同,稱s1是s2的字串。
- 字首和字尾是兩種特殊字串
- python字串是不變資料型別,python沒有字元型別
- 統一Unicode編碼字符集
2. 字串匹配(字串查詢)
- 樸素的傳匹配演算法
- 從左往右逐個字元匹配
- 發現不匹配,到目標穿下一個位置開始匹配
- 演算法複雜性O(m*n)
def navie_matching(t,p): m,n = len(p),len(t) i,j = 0,0 while i<m and j<n: if p[i] == t[j]: i,j=i+1,j+1 else: i,j=0,j-i+1 if i == m: return -1
- 無回溯串匹配演算法(KMP) 詳見部落格 KMP演算法
KMP時間複雜度:O(m+n)
3. 正則表示式
- 元字元(特殊字元):
正則表示式包re14個元字元: . ^ $ * + ? | { } [ ] ( )
- 主要操作:
- 生成表示式物件: re.compile(pattern,flag=0)
- 檢索 :
re.search(pattern,string,flag=0)
在裡檢索pattern,返回一個match型別的物件,否則返回None。 - 匹配:
re.match(pattern,string,flag=0)
檢查string是否存在一個與pattern匹配的字首。成功返回match物件,否則None。 - 分割:
re.split(pattern,string,maxsplit=0,flasg=0)
以pattern作為分割串,maxsplit指明最大分割數,用0表示要求處理完整個string。返回一個列表。 - 找出所有匹配串:
re.findall(pattern,string,flags=0)
返回一個表,表中按順序給出string裡與pattern匹配的各個字串 - 完全匹配:
re.fullmatch(string[,pos,[,endpos]])
- 正則表示式構造
- 字元組描述:
[...]
與[]裡的字元序列裡的任意字元匹配。[abc]可以與字元a,b,c匹配。
- 區間形式: [0-9a-zA-Z]
- 特殊形式: [^...],^表示對列出的字元組求補
- 圓點字元(.):萬用字元,匹配所有字元,a..b表示以a開頭b結尾的所有字串
d
:匹配十進位制數字等價於[0-9]D
:匹配所有非十進位制數字[^0-9]s
:匹配所有空白字元 [tvnfr]S
:匹配所有非空字元 [^ tvnfr]w
:匹配字母數字 [0-9a-zA-Z]W
:非字母數字 [^0-9a-zA-Z]
- 重複:
- 貪婪匹配:用 * 匹配0次或任意多次。
re.split('[,]*',str) - 非貪婪匹配 : 用 + 匹配1次或任意多。
d+
等價於dd*
- 可選描述符:
?
a?
要求與a匹配的字串的0或1次重複匹配 - 重複次數描述符:
{n}
a{n}與a匹配的串n次重複 - 重複次數範圍
{n,m}
go{2,5}gle,匹配結構為:google,gooogle,goooogle,gooooogle.
a{n}等價於a{n,n},a?等價於a{0,1}
*、+、?、{m,n}都採取貪婪匹配規則 - 非貪婪匹配描述符:
*?,+?,??,{m,n}?表示非貪婪匹配策略,在後面加? - 選擇
- 選擇描述符
|
表示兩種或多種串匹配模式
- 首位描述符:
- 行首描述符:以
^
開頭的 - 行尾描述符: 以
$
符號結尾 - 串首描述符:
A
開頭的 - 串尾描述符:
Z
結尾的
- 單詞邊界
b
描述單詞邊界,在實際單詞邊界位置匹配空串。單詞是字母數字的任意連續序列,邊界就是非字母數字的或者無字元.表示轉義
- 匹配物件(match物件)
mat表示通過匹配得到的一個match物件
- 取得被匹配的子串:
mat.group()
- 在目標串裡的匹配位置:
mat.start()
- 目標串裡的被匹配子串的結束位置:
mat.end()
- 目標串裡被匹配的區間:
mat.span()
得到匹配開始位置和結束位置組成的二元組 - 其他mat.re和mat.string(是資料與訪問,不是函式)分別取得match物件的正則表示式和目標串。
- 模式裡的組(group)
被匹配的組。
六、棧和佇列
存取關係:
- 棧是保證元素後進先出(LIFO結構)
- 佇列先進先出(FIFO結構)
1.棧抽象資料型別
ADT Stack:
Stack(self) #建立空棧
is_empty(self) #判斷棧是否為空,空返回True否則False
push(self,elem) #將元素elem加入棧,也常稱壓入或推入
pop(self) #彈出
top(self) #取得最後壓入棧的元素
python的list及其操作來實現棧:順序表技術實現棧類
- 建立空棧對應於建立空表[]
- list使用動態順序表技術(分離式),所以棧不會滿
- 壓入元素對應於list.append()
- 訪問棧頂元素 :list[-1]
- 彈出操作: list.pop()無參,預設彈出表尾元素
class SStack(): def __init__(self): self._elems= [] def is_empty(self): return self._elems == [] def top(self): if self._elems == []: raise StackUnderflow('in SStack.top') return self._elems[-1] def push(self,elem): drlf._elems.append() def pop(self): if self._elems == [] raise StackUnderflow('in SStack.pop') return self._elems.pop() st1 = SStack() st1.push(3) st1.push(2) while not st1.is_empty(): print(st1.pop())
棧的連結表實現: 所有棧操作都在一端進行,採用連結表技術,用表頭作為棧頂,用表尾作為棧底。
class LStak():
def __init__(self):
self._top=None
def is_empty(self):
return self._top is None
def top(self):
if self._top is None:
raise StackUnderflow('in LStack.top')
return self._top[-1]
def push(self):
self._top = LNode(elem,self._top)
def pop(self):
if self._top is None:
raise StackUnderflow('in LStack.pop')
p = self._top
self._top = p.next
return p.elem
2.棧的應用:
1.括號匹配問題: 三種括號[],(),{}
def check_parens(text):
'''括號配對檢查函式,text是被檢查的正文串
'''
parens = "[](){}"
open_parens="[({"
def parenthese(text):
"""括號生成器,每次呼叫返回text的下一括號及其位置"""
i,text_len=0,len(text)
while True:
while i < text_len and text[i] not in parens:
i+=1
if i>= text_len:
return
yield text[i],i
i+=1
st = SStack()
for pr,i in parentheses(text):
if pr in open_parens:
st.push(pr)
elif st.pop() != opposite[pr]:
print("Unmatching is found at",i,"for",pr)
return False
print("All parentheses are correctly matched.")
return True
3.佇列
佇列(queue),稱為隊,先進先出,佇列沒有位置的概念,只支援預設方式的元素存入和取出。
ADT Queue:
Queue(self) #建立空佇列
is_empty(self) #判斷佇列是否為空
enqueue(self,elem) #將elem加入佇列,入隊
dequeue(self) #出隊
peek(self) #最早加入的元素,不刪除
佇列類的list實現
class SQueue():
def __init__(self,init_len=8):
self._len = init_len #儲存區元素長度
self._elems = [0]*init_len #元素儲存
self._head = 0 #表頭元素下標
self._num = 0 #元素個數
def is_empty(self):
return self._num == 0
def peek(self):
if self._num == 0:
raise QueueUnderflow
return self._elems[self._head]
def dequeue(self):
if self._num == 0:
raise QueueUnderflow
e = self._elems[self._head]
self._head = (self._head+1) % self._len
self._num -= 1
return e
def enqueue(self,e):
if self._num == self._len:
self.__extend()
self._elems[(self._head+self._num) % self._len] = e
self._num += 1
def __extend(self):
old_len = self._len
self._len *= 2
new_elems = [0]*self._len
for i in range(old_then):
new_elems[i] = self._elems[(self._head + i )% old_len]
self._elems,self._head = new_elems,0
七、二叉樹和樹
1.二叉樹
是節點的有窮集合。包含一個根節點,其餘節點分屬兩顆不相交的二叉樹,分別是根節點的左子樹和右子樹
- 路徑,數從根節點到任意一個節點都有路徑,且唯一。
- 二叉樹是層次結構,樹根看作做高層元素,規定跟層數為0。
- 高度(深度):根節點高度為0。
2.二叉樹性質
- 在非空二叉樹第i層至多有 2i 個節點(i>=0)
- 高度為h的二叉樹至多有 2h+1-1 個節點(h>=0)
- 對於任何非空二叉樹T,其他節點個數為n0,度數為二的節點個數為n2,那麼n0 = n2 +1
3.滿二叉樹,擴充二叉樹
- 滿二叉樹:二叉樹中所有分支節點度數為2,滿二叉樹是一般二叉樹的一個子集。
滿二叉樹的節點比分支節點多一個 - 擴充二叉樹:對二叉樹T,加入足夠多的節點,使其變為滿二叉樹T2。T2稱為T的擴充二叉樹。
3.完全二叉樹:對一個高度h的二叉樹,第0到h-1層的節點都滿,而最後一層不滿,且節點在左邊,空位在右邊。
性質:
1.n個節點的完全二叉樹高度h=[log2n]
2.n個節點的完全二叉樹,按照從上到下從左到右的順序從0開始編號,對任意節點i(0<=i<=n-1)都有
- 序號為0的是根
- 對i>0,父節點編號為(i-1)/2
2*i+1<n
,左子節點序號為2*i+1,否則無左子節點2*i+2<n
,右子節點序號為2*i+2,否則無右子節點
3.抽象資料型別
ADT BinTree:
BinTree(self,data,left,right) #構造操作,建立二叉樹
is_empty(self) #判斷是否為空
num_nodes(self) #求二叉樹節點個數
data(self) #獲取二叉樹根儲存的資料
left(self) #左子樹
right(self) #右子樹
set_left(self,btree) #用btree代替原左子樹
set_right(self,btree) #用btree代替原右子樹
traversal(self) #遍歷二叉樹中個節點資料的迭代器
forall(self,op) #對二叉樹的每個節點進行op操作
4.遍歷二叉樹
- 深度優先:順著一條路儘可能往下走,必要時回溯
- 先根序遍歷(DLR順序)
- 中根序遍歷(LDR)
- 後根序遍歷(LRD)
- 寬度優先:按層次逐層訪問樹中各節點。
5.二叉樹的list實現
二叉樹是遞迴結構
- 空樹用None表示
- 非空二叉樹用包含三個元素的表[d,l,r]表示,d表示根節點元素,l和r兩顆子樹
例如:
['A', ['B',None,None], ['C', ['D',['F',None,None], ['G',None,None]], ['E',['H',None,None], ['I',None,None]] ] ]
ptython二叉樹實現
from graphviz import Digraph
import uuid
from random import sample
# 二叉樹類
class BTree(object):
# 初始化
def __init__(self, data=None, left=None, right=None):
self.data = data # 資料域
self.left = left # 左子樹
self.right = right # 右子樹
self.dot = Digraph(comment='Binary Tree')
# 前序遍歷
def preorder(self):
if self.data is not None:
print(self.data, end=' ')
if self.left is not None:
self.left.preorder()
if self.right is not None:
self.right.preorder()
# 中序遍歷
def inorder(self):
if self.left is not None:
self.left.inorder()
if self.data is not None:
print(self.data, end=' ')
if self.right is not None:
self.right.inorder()
# 後序遍歷
def postorder(self):
if self.left is not None:
self.left.postorder()
if self.right is not None:
self.right.postorder()
if self.data is not None:
print(self.data, end=' ')
# 層序遍歷
def levelorder(self):
# 返回某個節點的左孩子
def LChild_Of_Node(node):
return node.left if node.left is not None else None
# 返回某個節點的右孩子
def RChild_Of_Node(node):
return node.right if node.right is not None else None
# 層序遍歷列表
level_order = []
# 是否新增根節點中的資料
if self.data is not None:
level_order.append([self])
# 二叉樹的高度
height = self.height()
if height >= 1:
# 對第二層及其以後的層數進行操作, 在level_order中新增節點而不是資料
for _ in range(2, height + 1):
level = [] # 該層的節點
for node in level_order[-1]:
# 如果左孩子非空,則新增左孩子
if LChild_Of_Node(node):
level.append(LChild_Of_Node(node))
# 如果右孩子非空,則新增右孩子
if RChild_Of_Node(node):
level.append(RChild_Of_Node(node))
# 如果該層非空,則新增該層
if level:
level_order.append(level)
# 取出每層中的資料
for i in range(0, height): # 層數
for index in range(len(level_order[i])):
level_order[i][index] = level_order[i][index].data
return level_order
# 二叉樹的高度
def height(self):
# 空的樹高度為0, 只有root節點的樹高度為1
if self.data is None:
return 0
elif self.left is None and self.right is None:
return 1
elif self.left is None and self.right is not None:
return 1 + self.right.height()
elif self.left is not None and self.right is None:
return 1 + self.left.height()
else:
return 1 + max(self.left.height(), self.right.height())
# 二叉樹的葉子節點
def leaves(self):
if self.data is None:
return None
elif self.left is None and self.right is None:
print(self.data, end=' ')
elif self.left is None and self.right is not None:
self.right.leaves()
elif self.right is None and self.left is not None:
self.left.leaves()
else:
self.left.leaves()
self.right.leaves()
# 利用Graphviz實現二叉樹的視覺化
def print_tree(self, save_path='./Binary_Tree.gv', label=False):
# colors for labels of nodes
colors = ['skyblue', 'tomato', 'orange', 'purple', 'green', 'yellow', 'pink', 'red']
# 繪製以某個節點為根節點的二叉樹
def print_node(node, node_tag):
# 節點顏色
color = sample(colors,1)[0]
if node.left is not None:
left_tag = str(uuid.uuid1()) # 左節點的資料
self.dot.node(left_tag, str(node.left.data), style='filled', color=color) # 左節點
label_string = 'L' if label else '' # 是否在連線線上寫上標籤,表明為左子樹
self.dot.edge(node_tag, left_tag, label=label_string) # 左節點與其父節點的連線
print_node(node.left, left_tag)
if node.right is not None:
right_tag = str(uuid.uuid1())
self.dot.node(right_tag, str(node.right.data), style='filled', color=color)
label_string = 'R' if label else '' # 是否在連線線上寫上標籤,表明為右子樹
self.dot.edge(node_tag, right_tag, label=label_string)
print_node(node.right, right_tag)
# 如果樹非空
if self.data is not None:
root_tag = str(uuid.uuid1()) # 根節點標籤
self.dot.node(root_tag, str(self.data), style='filled', color=sample(colors,1)[0]) # 建立根節點
print_node(self, root_tag)
self.dot.render(save_path) # 儲存檔案為指定檔案
6. 優先佇列
優先佇列存入的每項資料都有附加的數值,表示由優先程度。任何時候的訪問和彈出都按照優先順序最高的順序。
list實現優先佇列:
class PrioQue:
def __init__(self,elist=[]):
self._elems = list(elist)
self._elems.sort(reverse=True) #從大到小排列
def enqueue(self,e):
i = len(self._elems) - 1
while i>=0:
if self._elems[i] <=e:
i -= 1
else:
break
self._elems.insert(i+1,e)
def is_empty(self):
return not self._elems
def peek(self):
if self.is_empty():
raise PrioQueueError("in top)
return self._elems[-1]
def dequeue(self):
if self.is_empty():
raise PrioQueueError("in pop)
return self._elems.pop()
7.採用樹形結構實現優先佇列的一種有效技術為堆。
- 在一個堆中從樹根到任何一個葉節點的路徑上,節點裡存的資料按照優先關係遞減。
- 堆中最優元素必定位於二叉樹根節點
- 位於數不同路徑上的元素不必關係優先順序
如果是最小元素優先,稱為小頂堆,反之稱為大頂堆
基於堆的優先佇列類
class PrioQueue:
def __init__(self,elist=[]):
self._elems = list(elist)
if elist:
self.buildheap()
def is_empty(self):
return not self._elems
def peek(self):
if self.is_empty():
raise PrioQueueError('in peek')
return self._elems[0]
#入隊操作,主要由siftup完成
def enqueue(self,e):
self._elems.append(None)
self.siftup(e,len(self._elems)-1)
def siftup(self,e,last):
elems,i,j = self._elems,last,(last-1)//2
while i>0 and e < elems[j]:
elems[i] = elems[j]
i,j=j,(j-1)//2
elems[i] = e
def dequeue(self):
if self.is_empty():
raise PrioQueueError('in dequeue')
elems = self._elems
e0 = elems[0]
e = elems.pop()
if len(elems) > 0 :
self.siftdown(e,0,len(elems))
return e0
def dequeue(self,e,begin,end):
elems,i,j = self._elems,begin,begin*2+1
while j< end:
if j+1 < end and elems[j+1] < elems[j]:
#elems[j]不大於其他兄弟節點的資料
j +=1
if e< elems[j]:
break
elems[i] = elems[j]
i,j = j,2*j+1
elems[i] = e
def buildheap(self):
end = len(self._elems)
for i in range(end//2,-1,-1):
self.siftdown(self._elems[i],i,end)
堆構造複雜度為O(n),插入彈出操作為O(logn),最壞情況O(n),其他操作O(1)。
八、字典和集合
九、排序演算法
1. 插入排序
平均複雜度O(n2) ,穩定
def insert_sort(lst):
for i in range(1,len(lst)): #開始時片段[0:1]已排序
x = lst[i]
j = i
while j>0 and lst[j-1].key >x.key:
lst[j] = lst[j-1] #反序逐個後移元素,確定插入排序
j -= 1
lst[j] = x
2. 選擇排序
def select_sort(lst):
for i in range(len(lst)-1): #只需迴圈len(lst)-1次
k=i #k是已知最小元素位置
for j in range(i,len(lst)):
if lst[j].key < lst[k].key:
k=j
if i != k: #lst[k]確定最小的元素,檢查是否需要交換
lst[i],lst[k] = lst[k],lst[i]
3.起泡排序
def bubble_sort(lst=[]):
for i in rane(len(lst):
found = False
for j in range(1,len(lst)-i):
if lst[j-1].key > lst[j].key :
lst[j-1],lst[j] = lst[j],lst[j-1]
found = True
if not found:
break
快速排序
def quick_sort(lst):
qsort_rec(lst,0,len(lst-1))
def qsort_rec(lst,1,r):
if 1 >= r:
return #分段無記錄或只有一個記錄
i=1
j=r
pivot = lst[i] #lst[i]是初始空位
while i<j: #找pivot的最終位置
while i<j and lst[j].key >= pivot.key: #用j向左掃描小於pivot的記錄
j-=1
if i<j:
lst[i] = lst[j]
i+=1 #小記錄移到右邊
while i<j and lst[i].key <= pivot.key:
i+=1 #用i向右掃描找大於pivot的記錄
if i<j:
lst[j] = lst[i]
j-=1 #大記錄移到右邊
lst[i] = pivot #將pivot存入其最終位置
qsort_rec(lst,1,i-1) #遞迴處理左半區
qsort_rec(lst,i+1,r) #遞迴處理右半區