为了OFFER,继续深入学习树和二叉树
@Author:Runsen
@Date:2020/9/10
現(xiàn)在大四基本是重刷數(shù)據(jù)結(jié)構(gòu)和算法,因為筆試真的太重要了。 Runsen又重溫了爭大佬專欄的隊列,又鞏固了下。而且Runsen發(fā)現(xiàn)留言區(qū)大佬的筆記很多,下面很多都是來自大佬總結(jié)的。
文章目錄
- 樹:
- 二叉樹:
- 二叉樹的遍歷
- 二叉樹遍歷的時間復(fù)雜度
- 思考
樹:
節(jié)點的高度=節(jié)點到葉子節(jié)點的最長路徑(邊數(shù))
節(jié)點的深度=根節(jié)點到這個節(jié)點所經(jīng)歷的邊的個數(shù)
節(jié)點的層數(shù)=節(jié)點的深度 + 1
樹的高度=根節(jié)點的高度
二叉樹:
1,二叉樹,每個節(jié)點最多有兩個叉,即兩個子節(jié)點,分布是左子節(jié)點和右子節(jié)點。
2,二叉樹不要求每個節(jié)點都有兩個子節(jié)點,有的節(jié)點只有左子節(jié)點,有的節(jié)點只有右子節(jié)點。
3,滿二叉樹:葉子節(jié)點全部都在最底層,除了葉子節(jié)點之外,每個節(jié)點都有左右兩個子節(jié)點,這種二叉樹就叫做滿二叉樹。
4,完全二叉樹:葉子節(jié)點都在最底下兩層,最后一層的葉子節(jié)點都靠左排列,并且除了最后一層,其他層的節(jié)點個數(shù)都要達(dá)到最大。
5,二叉樹的表示和儲存方式:
①:存儲一顆二叉樹,有兩種方法:
基于指針或者引用的二叉鏈表存儲法;
基于數(shù)組的順序存儲法
②:鏈?zhǔn)酱鎯Ψ?#xff1a;
每個節(jié)點有三個字段,其中一個存儲數(shù)據(jù),另兩個指向左右子節(jié)點的指針。這種存儲方式比較常用。
③:順序存儲法
把根節(jié)點存儲在下標(biāo)i=1的位置,左子節(jié)點存儲在下標(biāo) 2i=2的位置,右子節(jié)點存儲在2 * I +1=3的位置。以此類推。
如果節(jié)點X存儲在數(shù)組中下標(biāo)為i的位置,下標(biāo)2i的位置存儲的就是左子節(jié)點,下標(biāo)為2*I +1的位置存儲的就是右子節(jié)點。
反之,下標(biāo)為i/2的位置存儲就是他的父節(jié)點。通過這種方式,只要知道根節(jié)點存儲位置,就可以通過下標(biāo)計算,把整棵樹都串起來。
使用數(shù)組儲存二叉樹,如果是一課完全二叉樹,所以僅“浪費”一個下標(biāo)為0的存儲位置。如果是非完全二叉樹,會浪費比較多的數(shù)組存儲空間。
④:完全二叉樹
如果某顆二叉樹是一課完全二叉樹,那用數(shù)組存儲是最節(jié)省內(nèi)存的一種方式。因為數(shù)組存儲方式不需要像鏈?zhǔn)酱鎯Ψ菢?#xff0c;要存儲額外的左右子節(jié)點的指針。這個是完全二叉樹要求最后一層子節(jié)點都靠左的原因。
堆其實也是一種完全二叉樹,最常用的存儲方式就是數(shù)組。
二叉樹的遍歷
二叉樹遍歷是非常常見的面試題,將所有節(jié)點都遍歷打印出來的經(jīng)典方法有三種:前序遍歷,中序遍歷,后序遍歷。其中,前,中,后序,表示的是節(jié)點于它的左右子樹節(jié)點遍歷打印的先后順序。
①:前序遍歷:
對于樹中的任意節(jié)點來將,先打印這個節(jié)點,然后在打印它的左子樹,最后打印它的右子樹。
②:中序遍歷:
對于樹中的任意節(jié)點來說,先打印它的左子樹,然后在打印它本身,最后打印它的右子樹。
③:后序遍歷:
對于樹中的任意節(jié)點來說,先打印它的左子樹,然后在打印它的右子樹,最后打印這個節(jié)點本身。
實際上,二叉樹的前,中,后遍歷就是一個遞歸的過程。
前序遍歷的遞推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)具體實現(xiàn)代碼:Leetcode 144. 二叉樹的前序遍歷
class Solution:def preorderTraversal(self, root: TreeNode) -> List[int]:res = []def dfs(root):if not root:return []res.append(root.val)dfs(root.left)dfs(root.right)dfs(root)return res中序遍歷的遞推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)、具體實現(xiàn)代碼:Leetcode 94. 二叉樹的中序遍歷
class Solution:def inorderTraversal(self, root: TreeNode) -> List[int]:res = []def dfs(root):if not root:return []dfs(root.left)res.append(root.val)dfs(root.right)dfs(root)return res后序遍歷的遞推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r具體實現(xiàn)代碼:Leetcode 145. 二叉樹的后序遍歷
class Solution:def postorderTraversal(self, root: TreeNode) -> List[int]:res = []def dfs(root):if not root:return []dfs(root.left)dfs(root.right)res.append(root.val)dfs(root)return res二叉樹遍歷的時間復(fù)雜度
每個節(jié)點最多會被訪問兩次,所以遍歷操作的時間復(fù)雜度,更節(jié)點的個數(shù)n成正比,就是說二叉樹遍歷的時間復(fù)雜度是O(n)。
二叉樹的遍歷方式是最基本,也是最重要的一類題目,上面Runsen總結(jié)了「前序」、「中序」、「后序」、還剩一個「層序」遍歷方式,方式就是使用雙端隊列。
下面具體二叉樹層次遍歷實現(xiàn)代碼:Leetcode 102. 二叉樹的層次遍歷
class Solution:def levelOrder(self, root: TreeNode) -> List[List[int]]:'''使用雙端隊列,方便兩端元素入隊出隊操作遍歷每一層前,記錄當(dāng)前層節(jié)點數(shù)量 n具體處理每一個節(jié)點時,若它有孩子,則將它的孩子入隊當(dāng)前層遍歷結(jié)束后立即將結(jié)果存儲到最終結(jié)果中'''if not root:return []res,q = [],[root]while q:n =len(q)level = []for i in range(n):node = q.pop(0)level.append(node.val)if node.left:q.append(node.left)# level.append(node.left)if node.right:q.append(node.right)# level.append(node.right)res.append(level)return res上面代碼是bfs的廣度優(yōu)先搜索。下面補充下dfs深度優(yōu)先搜索解決層次遍歷的方法。
class Solution:def __init__(self):self.res = [] #存儲最終結(jié)果的大列表#增加一個參數(shù)表示層級,注意使用參數(shù)形式傳遞,這樣子不會有全局干擾def levelOrder(self, root: TreeNode,level=0) -> List[List[int]]:#如果節(jié)點為空。直接返回一個空列表。根節(jié)點為空返回這個結(jié)果。子節(jié)點為空,相當(dāng)于回退,不影響if not root:return []# print((root.val,level)) #調(diào)試用#如果當(dāng)前層的子列表不存在,先新增一個空列表,后續(xù)再根據(jù)層值去插值if len(self.res) <= level:self.res.append([]) self.res[level].append(root.val) level += 1 #更新層級self.levelOrder(root.left,level)self.levelOrder(root.right,level)return self.res在Leetcode中,Runsen發(fā)現(xiàn)了還有一個103. 二叉樹的鋸齒形層次遍歷,也順便搞定。
給定一個二叉樹,返回其節(jié)點值的鋸齒形層次遍歷。(即先從左往右,再從右往左進(jìn)行下一層遍歷,以此類推,層與層之間交替進(jìn)行)。例如: 給定二叉樹 [3,9,20,null,null,15,7],3/ \9 20/ \15 7 返回鋸齒形層次遍歷如下:[[3],[20,9],[15,7] ]這里和層次很像,就是多一個方向的判斷,這里我直接用1表示右邊,-1就是左邊,這樣使用列表切片可以反轉(zhuǎn)。
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = Noneclass Solution:def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:""":type root: TreeNode:rtype: List[List[int]]"""if not root:return []res, level, direction = [], [root], 1while level:res.append([n.val for n in level][::direction])direction *= -1# 這里的level 需要去除上面res添加的level = [kid for node in level for kid in (node.left, node.right) if kid]return res思考
給定一組數(shù)據(jù),比如 1,3,5,6,9,10。你來算算,可以構(gòu)建出多少種不同的二叉樹?
結(jié)果是卡特蘭數(shù),是C[n,2n] / (n+1)種形狀,c是組合數(shù),節(jié)點的不同又是一個全排列,一共就是n!?C[n,2n]/(n+1)n!*C[n,2n] / (n+1)n!?C[n,2n]/(n+1)個二叉樹。可以通過數(shù)學(xué)歸納法推導(dǎo)得出。
確定兩點:
1)n個數(shù),即n個節(jié)點,能構(gòu)造出多少種不同形態(tài)的樹?
2)n個數(shù),有多少種不同的排列?
當(dāng)確定以上兩點,將【1)的結(jié)果】乘以 【2)的結(jié)果】,即為最終的結(jié)果。
但是有一個注意的點: 如果n中有相等的數(shù),產(chǎn)生的總排列數(shù)就不是n!n!n!了喲
通過這一題,Runsen學(xué)到了【卡塔蘭數(shù)】:https://en.wikipedia.org/wiki/Catalan_number
補充:
卡塔蘭數(shù),通常記作c(n),是指n個A和n個B組成的這樣的序列的數(shù)目,從前往后讀,A的數(shù)目始終大于等于B。用組合數(shù)表示的話,c(n)=C(2n,n)/(n+1)。
卡特蘭數(shù)的前10項分別為:1、2、5、14、42、132、429、1430、4862。
與斐波那契數(shù)列(Fibonacci Sequence)一樣,卡塔蘭數(shù)也是離散數(shù)學(xué)和編程算法中相當(dāng)重要的數(shù)列,它以比利時的數(shù)學(xué)家歐仁·查理·卡塔蘭(Eugene Charles Catalan,1814年~1894年)命名。
Runsen不是學(xué)這方面的,看到文章底部有人說起,就百度了解下。
參考:極客時間王爭大佬:https://time.geekbang.org/column/article/67856
總結(jié)
以上是生活随笔為你收集整理的为了OFFER,继续深入学习树和二叉树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为了OFFER,我加深学习队列,现在还一
- 下一篇: 六十一、深入学习位运算