{"id":"dsa-trees-traversals","slug":"traversals","title":"Traversals and validation","text":"A **traversal** visits every node of a tree once. Done recursively, it comes in three classic orders, named after when the node itself comes compared with its two subtrees:\n\n- **pre-order:** the node, then its left subtree, then its right subtree;\n- **in-order:** the left subtree, then the node, then the right subtree;\n- **post-order:** the left subtree, then the right subtree, then the node.\n\n```python\nclass Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef preorder(node, out):\n    if node is not None:\n        out.append(node.value)\n        preorder(node.left, out)\n        preorder(node.right, out)\n\n\ndef inorder(node, out):\n    if node is not None:\n        inorder(node.left, out)\n        out.append(node.value)\n        inorder(node.right, out)\n\n\ndef postorder(node, out):\n    if node is not None:\n        postorder(node.left, out)\n        postorder(node.right, out)\n        out.append(node.value)\n\n\n#        8\n#       / \\\n#      3   12\n#     / \\    \\\n#    1   6    15\nroot = Node(8, Node(3, Node(1), Node(6)), Node(12, None, Node(15)))\nfor visit in [preorder, inorder, postorder]:\n    out = []\n    visit(root, out)\n    print(out)\n```\n\n```output\n[8, 3, 1, 6, 12, 15]\n[1, 3, 6, 8, 12, 15]\n[1, 6, 3, 15, 12, 8]\n```\n\nThe tree is a BST, and its **in-order is sorted**. That's no accident: at every node, the smaller values of its left subtree all come out before it, and the greater values of its right subtree all come out after it.\n\nEach order has its jobs. Pre-order meets every parent before its children, so it's the order for copying or saving a tree: inserting the values into an empty BST in pre-order builds the same tree again. Post-order finishes both children before their parent, which is what `size` and `height` did in the first step: they needed the answers of the subtrees first. In-order gives a BST's values in sorted order.\n\nAll three are O(n) time: every node is visited once and appended once, to the one list that every call shares. The tempting one-liner `return inorder(node.left) + [node.value] + inorder(node.right)` gives the same list, but `+` copies both lists it joins, so a value is copied again at every level above it: O(n·h) time, which is O(n²) for a chain.\n\n### Is it a valid BST?\n\nA tempting check compares each node with its own children only:\n\n```python\nclass Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef looks_valid(node):\n    if node is None:\n        return True\n    if node.left is not None and node.left.value >= node.value:\n        return False\n    if node.right is not None and node.right.value <= node.value:\n        return False\n    return looks_valid(node.left) and looks_valid(node.right)\n\n\n#      8\n#     /\n#    3\n#     \\\n#      10\nprint(looks_valid(Node(8, Node(3, None, Node(10)))))\n```\n\n```output\nTrue\n```\n\nThat's wrong. 10 is greater than its parent 3, but it's in the left subtree of 8, where every value must be smaller than 8: a search for 10 goes right at 8 and never finds it. The rule is about whole subtrees, not just children.\n\nThe fix is to pass **bounds** down the tree: every value must lie strictly between a `low` and a `high`, and the root has no bounds (`None` for both). Going left, the node's value becomes the new `high`, because everything down there must be smaller than it. Going right, it becomes the new `low`. Above, 3 gets the bounds `None` and 8, and 10 gets 3 and 8: 10 isn't below 8, so the tree isn't valid. Each node is still checked once, O(n) time. (Another correct check: the in-order must be strictly increasing.)\n\n### Deep trees: a stack of your own\n\nA recursive traversal of a chain of 5,000 nodes stops with a `RecursionError`. The calls that wait on Python's call stack can just as well wait in a list of your own, which can hold millions of items. Used this way, the list is a **stack**: the item added last is the first to come out, like plates in a pile. `append` puts an item on top, O(1) amortized, and `pop` takes the top one off, O(1). For in-order: go as far left as you can, putting each node on the stack. Then pop the top node, visit it, and go on with its right subtree in the same way:\n\n```python\nclass Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef inorder_loop(root):\n    out = []\n    stack = []\n    node = root\n    while stack or node is not None:\n        while node is not None:\n            stack.append(node)\n            node = node.left\n        node = stack.pop()\n        out.append(node.value)\n        node = node.right\n    return out\n\n\nchain = None\nfor value in range(5000, 0, -1):\n    chain = Node(value, None, chain)\nvalues = inorder_loop(chain)\nprint(len(values), values[:3], values[-1])\n```\n\n```output\n5000 [1, 2, 3] 5000\n```\n\n`chain` has 1 at the root, 2 as its right child, 3 below that and so on: a valid BST, 5,000 levels deep. `append` and `pop` at the end of a list are O(1), so the loop is O(n) time and O(h) memory, like the recursion, but without its limit.\n\n### Two more jobs for a stack\n\nA stack fits whenever the newest open item must be dealt with first. **Matching brackets** is the classic: in `([]{})` every closing bracket closes the last bracket still open. Go through the text, push each opening bracket, and at each closing bracket pop the top and check that the two are a pair. If the stack is empty at a closing bracket, or the top isn't its partner, the text isn't balanced: `)(` fails at once. It is balanced when every check passes and the stack ends empty. That's one pass, O(n).\n\nA stack kept in order finds the **next greater value** to the right of each item, such as how many days you wait for a warmer day. Keep a stack of the days still waiting for their answer. Their temperatures never rise from the bottom of the stack to the top, which is why it's called a **monotonic stack**. Each new day pops every waiting day that is colder, since it's their answer, and then waits itself:\n\n```python\ndef days_until_warmer(temps):\n    answer = [0] * len(temps)\n    waiting = []\n    for day, temp in enumerate(temps):\n        while waiting and temps[waiting[-1]] < temp:\n            earlier = waiting.pop()\n            answer[earlier] = day - earlier\n        waiting.append(day)\n    return answer\n\n\nprint(days_until_warmer([18, 16, 17, 21, 19, 19, 23, 15]))\n```\n\n```output\n[3, 1, 1, 3, 2, 1, 0, 0]\n```\n\nThe 21 on day 3 pops the 17 and then the 18 of day 0, which waited 3 days. The last two days never see a warmer one, so they keep their 0. There's a loop inside a loop, but each day is pushed once and popped at most once, so the function is O(n). Comparing each day with every later one would be O(n²).\n","task":"Fill in the blanks of the bounds check: the bound that each recursive call gets from the node. The program should print:\n\n```\nTrue\nFalse\nFalse\n```\n","kind":"fill","seed":"class Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef is_valid(node, low=None, high=None):\n    if node is None:\n        return True\n    if low is not None and node.value <= low:\n        return False\n    if high is not None and node.value >= high:\n        return False\n    return is_valid(node.left, low, [[node.value]]) and is_valid(node.right, [[node.value]], high)\n\n\nprint(is_valid(Node(8, Node(3, Node(1), Node(6)), Node(12))))\nprint(is_valid(Node(8, Node(3, None, Node(10)))))\nprint(is_valid(Node(8, None, Node(12, Node(7)))))\n","solution":"class Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef is_valid(node, low=None, high=None):\n    if node is None:\n        return True\n    if low is not None and node.value <= low:\n        return False\n    if high is not None and node.value >= high:\n        return False\n    return is_valid(node.left, low, node.value) and is_valid(node.right, node.value, high)\n\n\nprint(is_valid(Node(8, Node(3, Node(1), Node(6)), Node(12))))\nprint(is_valid(Node(8, Node(3, None, Node(10)))))\nprint(is_valid(Node(8, None, Node(12, Node(7)))))\n","hints":["Everything in the left subtree must be smaller than the node, so the node's value is the new upper bound there. The lower bound stays what it was.","Both blanks are `node.value`: as `high` for the left subtree and as `low` for the right subtree."],"tests":[{"name":"Output","stdin":"","expect":"True\nFalse\nFalse","hidden":false}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef first(node):\n    if node is None:\n        return \"\"\n    return node.value + first(node.left) + first(node.right)\n\n\ndef last(node):\n    if node is None:\n        return \"\"\n    return last(node.left) + last(node.right) + node.value\n\n\ntree = Node(\"A\", Node(\"B\", Node(\"D\"), Node(\"E\")), Node(\"C\", None, Node(\"F\")))\nprint(first(tree))\nprint(last(tree))\n","solution":"class Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef first(node):\n    if node is None:\n        return \"\"\n    return node.value + first(node.left) + first(node.right)\n\n\ndef last(node):\n    if node is None:\n        return \"\"\n    return last(node.left) + last(node.right) + node.value\n\n\ntree = Node(\"A\", Node(\"B\", Node(\"D\"), Node(\"E\")), Node(\"C\", None, Node(\"F\")))\nprint(first(tree))\nprint(last(tree))\n","hints":["Draw the tree: A at the root, B and C its children, D and E the children of B, and F the right child of C.","`first` puts the node's own letter before its subtrees: that's pre-order, which starts A, B, D.","`last` puts the node's letter after both subtrees: that's post-order, which starts with the leaf D and ends with the root A."],"tests":[{"name":"Output","stdin":"","expect":"ABDECF\nDEBFCA","hidden":false}],"require":[],"forbid":[],"lines":["ABDECF","DEBFCA"]},{"task":"Write `kth_smallest(root, k)` that returns the k-th smallest value of a binary search tree (`k` 1 gives the smallest), or `None` when the tree has fewer than `k` values. Walk the tree in order with a stack of your own, and stop as soon as you've taken `k` values. The tests include trees that are chains 3,000 levels deep, where a recursive traversal stops with a `RecursionError`.\n\nFor example, this program:\n\n```python\nroot = Node(8, Node(3, Node(1), Node(6)), Node(12, None, Node(15)))\nprint(kth_smallest(root, 1), kth_smallest(root, 4), kth_smallest(root, 6))\nprint(kth_smallest(root, 7))\n```\n\nprints:\n\n```\n1 8 15\nNone\n```\n","kind":"code","seed":"class Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef kth_smallest(root, k):\n    pass\n\n\nif __name__ == \"__main__\":\n    root = Node(8, Node(3, Node(1), Node(6)), Node(12, None, Node(15)))\n    print(kth_smallest(root, 1), kth_smallest(root, 4), kth_smallest(root, 6))\n    print(kth_smallest(root, 7))\n","solution":"class Node:\n    def __init__(self, value, left=None, right=None):\n        self.value = value\n        self.left = left\n        self.right = right\n\n\ndef kth_smallest(root, k):\n    stack = []\n    node = root\n    count = 0\n    while stack or node is not None:\n        while node is not None:\n            stack.append(node)\n            node = node.left\n        node = stack.pop()\n        count += 1\n        if count == k:\n            return node.value\n        node = node.right\n    return None\n\n\nif __name__ == \"__main__\":\n    root = Node(8, Node(3, Node(1), Node(6)), Node(12, None, Node(15)))\n    print(kth_smallest(root, 1), kth_smallest(root, 4), kth_smallest(root, 6))\n    print(kth_smallest(root, 7))\n","hints":["Start from the lesson's `inorder_loop`. Instead of appending each value to a list, count the values you take off the stack.","When the count reaches `k`, return that node's value right away: the rest of the tree is never visited.","If the loop ends, the tree had fewer than `k` values: return `None`."],"tests":[{"name":"Test 1","stdin":"","call":"root = Node(8, Node(3, Node(1), Node(6)), Node(12, None, Node(15)))\nprint(kth_smallest(root, 1), kth_smallest(root, 4), kth_smallest(root, 6))\nprint(kth_smallest(root, 7))\n","expect":"1 8 15\nNone","hidden":false},{"name":"print(kth_smallest(None, 1))","stdin":"","call":"print(kth_smallest(None, 1))\n","expect":"None","hidden":true},{"name":"kth_smallest(Node(5), 1)","stdin":"","call":"kth_smallest(Node(5), 1)\n","expect":"5","hidden":true,"returns":true},{"name":"Test 4","stdin":"","call":"root = Node(20, Node(10, Node(5), Node(15)), Node(30, Node(25), Node(35)))\nprint(kth_smallest(root, 3), kth_smallest(root, 5), kth_smallest(root, 2))\n","expect":"15 25 10","hidden":true},{"name":"Test 5","stdin":"","call":"root = None\nfor value in range(1, 3001):\n    root = Node(value, root)\nprint(kth_smallest(root, 1), kth_smallest(root, 2999))\n","expect":"1 2999","hidden":true},{"name":"Test 6","stdin":"","call":"root = None\nfor value in range(3000, 0, -1):\n    root = Node(value * 2, None, root)\nprint(kth_smallest(root, 2500), kth_smallest(root, 3001))\n","expect":"5000 None","hidden":true}],"require":[],"forbid":[]}]}