# Binary - Trees ## Inorder traversal Left -> Node -> Right ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: res = [] def inorder(root): if not root: return inorder(root.left) res.append(root.val) inorder(root.right) inorder(root) return res ``` ## Preorder Traversal Node -> Left -> Right ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def preorderTraversal(self, root: Optional[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 ``` ## Postorder traversal Left -> Right -> Node ```python= # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def postorderTraversal(self, root: Optional[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 ``` # Binary Search Tree (BST) ## Binary Search Tree? Hiểu đơn giản thì đây là Binary Tree nhưng có quy tắc đối với mỗi Node, các Node bên phải đều lớn hơn nó và các Node bên trái phải bé hơn nó. ## Problems ### Convert Sorted Array -> BST: Sử dụng kĩ thuật `chia để trị - Divine and Conquer`: Node gốc là node giữa Array, đối với mỗi Node thì Node bên trái là Node ở Giữa - `mid` của Array con bên trái, tương tự với Node phải chỉ ngược chiều. Ta làm tuần tự theo công thức đệ quy cho đến khi không thể chia đôi mảng thành trái-phải được nữa: ```python # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution(object): def sortedArrayToBST(self, nums): """ :type nums: List[int] :rtype: Optional[TreeNode] """ def dnq(nums, l, r): if l <= r: mid = (l + r) // 2 node = TreeNode(nums[mid]) node.left = dnq(nums, l, mid - 1) node.right = dnq(nums, mid + 1, r) return node return dnq(nums, 0, len(nums) - 1) ``` ### Search in a BST Để tìm 1 giá trị trong BST nhìn trung đơn giản hơn do ta không cần dùng đến đệ quy mà chỉ cần vòng lặp là đủ: Đặc biệt với cấu trúc của BST, thay vì duyệt tất cả các Node ta có thể rút ngắn thời gian tìm kiếm còn $O(logn)$ bằng phương pháp tương tự như `binary search`: ```python # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution(object): def searchBST(self, root, val): """ :type root: Optional[TreeNode] :type val: int :rtype: Optional[TreeNode] """ while root: if root.val == val: return root elif root.val > val: root = root.left else: root = root.right return None ``` ### Validate BST Để kiểm tra 1 Binary Tree có phải là BST hay không, ta cần đảm bảo các điều kiện BST bằng cách: - Với Mỗi Node: nhánh bên phải chỉ được chứa các Node có giá trị cao hơn nó - Với Mỗi Node: nhánh bên trái chỉ được chứa các Node có giá trị nhỏ hơn nó. => Để làm được điều này, ta cần setup các giá trị `big` và `small`, dùng `dfs` duyệt tứng Node, và với mỗi Node: - Nó phải đủ điều kiện là nằm trong khoảng `(small, big)`, nếu lọt thì đây không phải BST. - Tiếp theo, với từng Node: - Mỗi khi duyệt xuống nhánh trái: ta phải update `big` là giá trị của nó. - Mỗi khi duyệt xuống nhánh phải: ta phải update `small` là giá trị của nó. ```python # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution(object): def isValidBST(self, root): """ :type root: Optional[TreeNode] :rtype: bool """ check = [0] def dfs(root, big, small): if not root: return elif root.val >= big or root.val <= small: check[0] += 1 return dfs(root.left, root.val, small) #update big if go left dfs(root.right, big, root.val) # update small if go right dfs(root, float('inf'), float('-inf')) return check[0] == 0 ``` ### Delete a Node in BST Việc xóa Node mang giá trị `Key` trong BST khá tricky khi mà nó sẽ chia thành 2 trường hợp đối với cách giải đệ quy và 3 trường hợp khi dùng vòng lặp để xóa. Để xóa ta tiến hành 2 bước chính: - Tìm kiếm Node có giá trị `Key` để xóa. - Xóa Node đó. Để tìm 1 Node mang giá trị `Key` trong BST khá đơn giản và đã được trình bày từ trước. Cái khó ở đây là khi đã tìm được Node mang `Key` rồi thì ta cần xử lý trường hợp: 1. Node là Node lá hoặc Node chỉ có 1 con (trường hợp này được tách ra trong cách giải vòng lặp). - Ta sẽ tiến hành nối Node con của nó với cha. Hoặc nối Node cha với Null khi Node là Node lá. 2. Node là Node trong(tức là có 2 con). - Khi này ta cần tìm `inorder successor - Node nhánh phải bé nhất` của Node hiện tại. Sau đó tiến hành thay giá trị của Node này bằng giá trị của `inorder successor`. Sau đó, xóa node `inorder successor`. #### Sử dụng vòng lặp: ```python # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution(object): def deleteNode(self, root, key): """ :type root: Optional[TreeNode] :type key: int :rtype: Optional[TreeNode] """ def find_successor(root): #Hàm tìm node nhỏ nhất bên trái của node hiện tại, đồng thời node cha của nó prev = None while root: if not root.left: break prev = root root = root.left return prev, root pre = None #Thiết lập con trỏ trước con trỏ sau cur = root while cur: if key > cur.val: #Tiến hành duyệt cây theo logic BST. pre = cur cur = cur.right elif key < cur.val: pre = cur cur = cur.left elif key == cur.val: #Nếu ta gặp node có chứa giá trị key, ta tiến hành xóa node đó theo 3 trường hợp đã được định nghĩa trên. if not cur.left and not cur.right: #Nếu là node lá if pre == None: #Trường hợp chỉ có 1 node root = None break elif pre.left == cur: #nếu đây là node lá trái của node cha pre.left = None break else: #nếu là node phải pre.right = None break elif not cur.left or not cur.right: #Nếu node chỉ có 1 con if pre == None: #Trường hợp cần xóa là root. Ta xóa root và gán node mới là left hoặc right của nó tùy thuộc theo việc bên nào có tồn tại. if root.left: root = root.left break else: root = root.right break elif not cur.left: #Nếu chỉ có node con phải, ta sẽ tiến hành nối node cha của nó vào node phải của nó, tương tự với trường hợp chỉ có node trái ở dưới if pre.left == cur: pre.left = cur.right break else: pre.right = cur.right break else: if pre.left == cur: pre.left = cur.left break else: pre.right = cur.left break else: # Nếu gặp trường hợp cần xóa 1 node trong, ta sẽ cần phải thay đổi node đó thành node lớn nhất bên phải (inorder successor), đặc biệt nếu nó có node con ta cũng cần phải nối cả chúng vào nữa prev, successor = find_successor(cur.right) cur.val = successor.val if prev != None: prev.left = successor.right else: cur.right = successor.right break return root ``` #### Giải theo đệ quy: ```python # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]: if not root: #Basecase 1: Nếu node là Null, return return root # Tiến hành tìm kiếm Node có giá trị Key trong BST # Đặc biệt, nếu ta có thể tìm được node chứa Key trong 1 trong các nhánh # trái hoặc phải của node hiện tại, ta sẽ tiến hành nối node hiện # tại với nhánh đã bị biến đổi (cắt đi 1 node) elif root.val < key: root.right = self.deleteNode(root.right, key) elif root.val > key: root.left = self.deleteNode(root.left, key) # Nếu tìm được node chứa Key: # TH1: Node chỉ có 1 con hoặc là node lá, ta nối node cha của nó # vào node con của nó là xong elif root.val == key: if not root.left: return root.right elif not root.right: return root.left # TH2: Node là node trong (2 node): Ta cần thay giá trị của node # bằng giá trị của node nhỏ nhất bên phải (inorder successor), # sau đó, xóa node nhỏ nhất đó (điều này đơn giản vì nó sẽ là node rơi vào TH1) else: cur = root.right while cur.left: cur = cur.left root.val = cur.val root.right = self.deleteNode(root.right, cur.val) return root ```