二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。
爲什麼研究二叉樹的遍歷?
因爲計算機只會處理線性序列,而我們研究遍歷,就是把樹中的結點變成某種意義的線性序列,這給程序的實現帶來了好處
數據結構定義
在二叉樹遍歷過程中用到了鏈表、堆棧、隊列
節點定義
class node: #用鏈表實現隊列 只用來記錄節點
def __init__(self):
self.data = None
self.next = None
隊列定義
class queue():
def __init__(self):
self.front = node()
self.rear = node()
self.front = None
self.rear = None
def push(self,data):
# print('加入新節點',data.data)
newnode = node()
newnode.data = data
newnode.next = None
if self.rear == None:
self.front = newnode #定義頭指針
else:
self.rear.next = newnode #鏈接下一個指針
self.rear = newnode #和front指向同一個對象
def pop(self):
if self.front == None:
print('空隊列')
else:
self.front = self.front.next
def get_front(self):
if self.front == None:
print('空隊列')
else:
return self.front
堆棧定義
class stack():#用列表實現堆棧
def __init__(self):
self.top = node()
self.top = None
def push(self,data):
newnode = node()
newnode.data = data
newnode.next = self.top
self.top = newnode
def pop(self):
if self.top is None:
print('空堆棧')
else:
temp = self.top
self.top = self.top.next
return temp
def isEmpty(self):
if self.top is None:
return True
else:
return False
class tree:
def __init__(self):
self.data = 0
self.left = None
self.right = None
二叉樹創建
def Btree_create(root,data):
newnode = tree()
newnode.data = data
newnode.left = None
newnode.right = None
if root == None:
root = newnode
return root
else:
current = root
while current is not None:
backup = current #一直記錄沒有數據的點
if data > current.data: #每來一個數據,都是先從根據開始比較的,左邊的都小於根,右邊的都大於根
current = current.right
else:
current = current.left
if backup.data < data:
backup.right = newnode
else:
backup.left = newnode
return root
data = [7,4,1,5,16,8,11,12,15,9,2]
ptr = None
root = None
for i in range(len(data)):
ptr = Btree_create(ptr,data[i])
root = ptr
遍歷方法
前序遍歷
step1:輸出根部節點
step2:遍歷左子樹
step3:遍歷由子樹
遞歸方式實現遍歷:
def preorder(ptr):#前序法
if ptr is not None:
print('[%d]' % ptr.data,end='')
preorder(ptr.left)
preorder(ptr.right)
其實這裏也可以看到分治的思想,按照遍歷的邏輯進行操作
非遞歸方式1
具體過程:
1.首先申請一個新的棧,記爲s;
2. 將頭結點head壓入stack中;
3. 每次從stack中彈出棧頂節點,記爲pNode,然後打印pNode值,如果pNode右孩子不爲空,則將右孩子壓入棧中;如果pNode的左孩子不爲空,將其壓入stack中;
4.重複步驟3,直到stack爲空.
def preorder3(ptr):#前序法,非遞歸
if ptr is None:
return
s = stack()
s.push(ptr)
while s.isEmpty() is False:
pNode = s.pop().data
print('{}\t'.format(pNode.data),end='')
if pNode.right:
s.push(pNode.right)
if pNode.left:
s.push(pNode.left)
該過程動態演示
非遞歸方式2
具體實現過程:
1.首先申請一個新的棧,記爲s;
2.一直打印左節點,不斷令pNode=pNode.left,同時將當前節點的右孩子壓入堆棧;
3.如果當前節點沒有左子樹,則開始輸出右子樹,重複步驟2
(能夠保證遍歷所有節點)
def preorder2(ptr):#前序法 非遞歸 出棧順序
if ptr is None:
return
s = stack() #實例化堆棧
pNode = ptr
while pNode is not None or s.isEmpty() is False:
if pNode is not None:
print('{}\t'.format(pNode.data),end='') #一開始輸出根節點
s.push(pNode.right)
pNode = pNode.left
else:
node = s.pop()
pNode = node.data
中序遍歷
遞歸方式
具體過程:
先中序遍歷左子樹
再訪問根節點
最後中序遍歷右子樹
def inorder(ptr): #中序法 左中右
if ptr is not None:
inorder(ptr.left) #一直遞歸到爲None的條件
print('[%d]' % ptr.data,end='') #輸出data
inorder(ptr.right)
非遞歸方式:
具體過程:
申請一個新棧,記爲s,申請一個變量pNode,初始時令pNode爲頭節點;
先把pNode節點壓入棧中,對以pNode節點爲頭的整棵子樹來說,依次把整棵樹的左子樹壓入棧中,即不斷令pNode=pNode.left,然後重複步驟2;
不斷重複步驟2,直到發現pNode爲空,此時從s中彈出一個節點記爲node,打印node的值,並讓pNode = node.right,然後繼續重複步驟2;
當s爲空並且pNode爲空時結束。
def inorder2(ptr): #中序法 非遞歸的方法
if ptr is None:
return
s = stack()
pNode = ptr
while pNode is not None or s.isEmpty() is False:
while pNode is not None:
s.push(pNode)
pNode = pNode.left
node = s.pop()
print('{}\t'.format(node.data.data),end='')
pNode = node.data.right #每彈出一個就遍歷其右節點
動態演示
後序遍歷
先後序遍歷左子樹
再後序遍歷右子樹
最後訪問根節點
遞歸方式
def postorder(ptr):#後序法 遞歸的方法
if ptr is not None:
postorder(ptr.left)
postorder(ptr.right)
print('[%d]' % ptr.data,end='')
非遞歸方式1
使用兩個棧實現
申請兩個棧stack1,stack2,然後將頭結點壓入stack1中;
從stack1中彈出的節點記爲pNode,然後先把pNode的左孩子壓入stack1中,再把pNode的右孩子壓入stack1中;
在整個過程中,每一個從stack1中彈出的節點都放在第二個棧stack2中;
不斷重複步驟2和步驟3,直到stack1爲空,過程停止;
從stack2中依次彈出節點並打印,打印的順序就是後序遍歷的順序;
def postorder2(ptr):#後序法 非遞歸的方法
if ptr is None:
return
s1 = stack()
s2 = stack()
pNode = ptr
s1.push(pNode)
while s1.isEmpty() is False:
pNode = s1.pop()
if pNode.data.left:
s1.push(pNode.data.left)
if pNode.data.right:
s1.push(pNode.data.right)
s2.push(pNode)
while s2.isEmpty() is False:
node = s2.pop()
print('{}\t'.format(node.data.data.data),end='')
動態演示
遞歸方式2:
具體過程:
使用一個棧實現
申請一個棧stack,將頭節點壓入stack,同時設置兩個變量 h 和 c,在整個流程中,h代表最近一次彈出並打印的節點,c代表當前stack的棧頂節點,初始時令h爲頭節點,,c爲null;
每次令c等於當前stack的棧頂節點,但是不從stack中彈出節點,此時分一下三種情況:
(1)如果c的左孩子不爲空,並且h不等於c的左孩子,也不等於c的右孩子,則吧c的左孩子壓入stack中
(2)如果情況1不成立,並且c的右孩子不爲空,並且h不等於c的右孩子,則把c的右孩子壓入stack中;
(3)如果情況1和2不成立,則從stack中彈出c並打印,然後令h等於c;
一直重複步驟2,直到stack爲空.
def postorder3(ptr):
if ptr is None:
return
s = stack()
s.push(ptr)
h = ptr #存儲最近一次彈出並打印的節點
cur = None #當前的棧頂節點
while s.isEmpty() is False:
cur = s.top.data
#當前節點有左孩子並且左孩子沒有被遍歷過並且右孩子沒有被遍歷過
if cur.left and h != cur.left and h != cur.right:
s.push(cur.left)
#當前節點有右孩子 並且右孩子沒有被遍歷過
elif cur.right and h != cur.right:
s.push(cur.right)
else:#沒有孩子節點或已經被遍歷
node = s.pop()
print('{}\t'.format(node.data.data),end='')
h = cur
動態演示:
層序遍歷
具體過程:
首先申請一個新的隊列,記爲queue;
將根結點root壓入queue中;
每次從queue中出隊,記爲front,然後打印front值,如果front左孩子不爲空,則將左孩子入隊;如果front的右孩子不爲空,則將右孩子入隊;
重複步驟3,直到queue爲空
def levelorder(root):#層序遍歷
q = queue() # 存儲隊列
q.push(root)
# temp = []
while q.front is not None:
front = q.get_front()
print([front.data.data], end='')
if front.data.left:
q.push(front.data.left)
if front.data.right:
q.push(front.data.right)
# temp.append(front.data.data)
q.pop()
# return temp
思考:
首先理解每一種方式的遍歷規則,然後再考慮遍歷過程中需要用到什麼數據結構(已經確定打算怎麼遍歷了),使用不使用中間容器。理解的不夠深,後續再補充。。。
參考博客:https://www.cnblogs.com/songwenjie/p/8955856.html