Search code examples
algorithmsortingavl-treein-place

Why AVL sort is not in place?


I was recently told that AVL sort is not in place. Can anyone please explain it? From the below code, I am not sure where I assign extra space when sorting. In this code, when a data structure is built or an element are inserted, elements are ordered by their key.

Reference for the claim: They are using this claim to motivate "binary heap"

[1].https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-spring-2020/lecture-notes/MIT6_006S20_r08.pdf

Reference for code:

[2]. https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-spring-2020/lecture-notes/MIT6_006S20_r06.pdf

[3]. https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-spring-2020/lecture-notes/MIT6_006S20_r07.pdf

def height(A):
    if A: return A.height
    else: return -1

class Binary_Node:
    def __init__(self, x):
        self.item = x
        self.parent = None
        self.left = None
        self.right = None
        self.subtree_update()

    def subtree_update(self):
        self.height = 1 + max(height(self.left), height(self.right))

    def subtree_iter(self):
        if self.left: yield from self.left.subtree_iter()
        yield self
        if self.right: yield from self.right.subtree_iter()
        
    def subtree_first(self):
        if self.left: return self.left.subtree_first()
        else: return self
    
    def subtree_last(self):
        if self.right: return self.right.subtree_last()
        else: return self
        
    def sucessor(self):
        if self.right: return self.right.subtree_first()
        while self.parent and (self is self.parent.right): #A is parent's left child and A's parent exists
            self = self.parent
        return self.parent
        
    def predecessor(self):
        if self.left: return self.left.subtree_last()
        while self.parent and (self is self.parent.left):
            self = self.parent
        return self.parent
    
    def subtree_insert_before(self, A):
        if self.left: 
            self = self.left.subtree_last()
            self.right, A.parent = A, self
        else: 
            self.left, A.parent = A, self
        self.maintain()
        
    def subtree_insert_after(self, A):
        if self.right: 
            self = self.right.subtree_first()
            self.left, A.parent = A, self
        else: 
            self.right, A.parent = A, self
        self.maintain()
        
    def delete(self):
        if not self.left and not self.right: # when self is leaf
            if self.parent: 
                A = self.parent
                if A.left is self: A.left = None
                else: A.right = None
                self.parent = None
            
        if self.left: 
            self.item, self.left.subtree_last().item = self.left.subtree_last().item, self.item
            self.left.subtree_last().delete()
        
        else:
            self.item, self.right.subtree_first().item = self.right.subtree_first().item, self.item
            self.right.subtree_last().delete()
            
    def subtree_delete(self):
        if self.left or self.right:
            if self.left: B = self.predecessor()
            else: B = self.sucessor()
            self.item, B.item = B.item, self.item
            return B.subtree_delete()
            
        if self.parent:
            if self.parent.left is self: self.parent.left = None
            else: self.parent.right = None
            self.parent.maintain()
        return self
    
    def subtree_rotate_right(self):
        assert self.left
        B, E = self.left, self.right
        A, C = B.left, B.right
        B, self = self, B
        self.item, B.item = B.item, self.item
        B.left, B.right = A, self
        self.left, self.right = C, E
        if A: A.parent = B
        if E: E.parent = self
        B.subtree_update()
        self.subtree_update()
    
    def subtree_rotate_left(self):
        assert self.right
        A, D = self.left, self.right
        C, E = D.left, D.right
        self, D = D, self
        self.item, D.item = D.item, self.item
        self.left, self.right = A, C
        D.left, D.right = self, E
        if A: A.parent = self
        if E: E.parent = D
        self.subtree_update()
        D.subtree_update()
    
    def skew(self):
        return height(self.right) - height(self.left)
    
    def rebalance(self):
        if self.skew() == 2:
            if self.right.skew() < 0:
                self.right.subtree_rotate_right()
            self.subtree_rotate_left()
        elif self.skew() == -2:
            if self.left.skew() > 0:
                self.left.subtree_rotate_left()
            self.subtree_rotate_right()
    
    def maintain(self):
        self.rebalance()
        self.subtree_update()
        if self.parent: self.parent.maintain()
    
class Binary_Tree:
    def __init__(self, Node_Type = Binary_Node):
        self.root = None
        self.size = 0
        self.Node_Type = Node_Type
        
    def __len__(self): return self.size
    def __iter__(self):
        if self.root:
            for A in self.root.subtree_iter():
                yield A.item 
        
    def build(self, X):
        A = [x for x in X]
        def build_subtree(A, i, j):
            c = (i + j) // 2
            root = self.Node_Type(A[c])
            if i < c:
                root.left = build_subtree(A, i, c - 1)
                root.left.parent = root
            if j > c:
                root.right = build_subtree(A, c + 1, j)
                root.right.parent = root
            return root
        self.root = build_subtree(A, 0, len(A) - 1)
        
        
class BST_Node(Binary_Node):
    def subtree_find(self, k):
        if self.item.key > k:
            if self.left: self.left.subtree_find(k)
        elif self.item.key < k:
            if self.right: self.right.subtree_find(k)
        else: return self
        
        return None
    
    
    def subtree_find_next(self, k):
        if self.item.key <= k:
            if self.right: return self.right.subtree_find_next(k)
            else: return None
            
        elif self.item.key > k:
            if self.left: return self.left.subtree_find_next(k)
            else: return self 
        
        return self
        
    
    def subtree_find_prev(self, k):
        if self.item.key >= k:
            if self.left: return self.left.subtree_find_prev(k)
            else: return None
        elif self.item.key < k:
            if self.right: return self.right.subtree_find_prev(k)
            else: return self
        
        return self
    
    def subtree_insert(self, B):
        if B.item.key < self.item.key:
            if self.left: self.left.subtree_insert(B)
            else: self.subtree_insert_before(B)
        
        elif B.item.key > self.item.key:
            if self.right: self.right.subtree_insert(B)
            else: self.subtree_insert_after(B)
        
        else:
            self.item = B.item
        
        
class Set_Binary_Tree(Binary_Tree):
    def __init__(self): super().__init__(BST_Node)

    def iter_order(self): yield from self
    
    def build(self, X):
        for x in X: self.insert(x)
    
    def find_min(self): 
        if self.root: return self.root.subtree_first()
        
    def find_max(self): 
        if self.root: return self.root.subtree_last()
        
    def find(self, k):
        if self.root: 
            node = self.root.subtree_find(k)
            if node:
                return node.item
        
    def find_next(self, k): 
        if self.root:
            node = self.root.subtree_find_next(k)
            if node:
                return node.item
            
            
    def find_prev(self, k): 
        if self.root:
            node = self.root.subtree_find_prev(k)
            if node:
                return node.item
        
    def insert(self, x): 
        new = self.Node_Type(x)
        if self.root:
            self.root.subtree_insert(new)
            if new.parent is None: return False
            
        else:
            self.root = new
        
        self.size += 1
        return True
        
        
    def delete(self, k):
        assert self.root
        node = self.root.subtree_find(k)
        assert node
        ext = node.subtree_delete()
        if ext.parent is None: self.root = None
        self.size -= 1
        return ext.item

Solution

  • Wikipedia defines an in-place algorithm as follows:

    In computer science, an in-place algorithm is an algorithm which transforms input using no auxiliary data structure. However, a small amount of extra storage space is allowed for auxiliary variables. The input is usually overwritten by the output as the algorithm executes. An in-place algorithm updates its input sequence only through replacement or swapping of elements.

    So one of the properties of an algorithm that is called "in-place" is that it does not copy all input values into an newly allocated data structure. If an algorithm creates a binary search tree (like AVL), for which node objects are created that are populated with the input values, then it cannot be called in-place by the above definition, even if at the end of the process the values are copied back into the input array.

    As a comparison, heap sort does not have to create a new data structure, as the input array can be used to reorganise its values into a heap. It merely has to swap values in that array in order to sort it. It is therefore an in-place algorithm.