├── AVL_Tree └── main.py ├── BST_Validator └── main.py ├── Binary_Search_Tree └── main.py ├── Linked_List └── main.py └── README.md /AVL_Tree/main.py: -------------------------------------------------------------------------------- 1 | class node: 2 | def __init__(self,value=None): 3 | self.value=value 4 | self.left_child=None 5 | self.right_child=None 6 | self.parent=None # pointer to parent node in tree 7 | self.height=1 # height of node in tree (max dist. to leaf) NEW FOR AVL 8 | 9 | class AVLTree: 10 | def __init__(self): 11 | self.root=None 12 | 13 | def __repr__(self): 14 | if self.root==None: return '' 15 | content='\n' # to hold final string 16 | cur_nodes=[self.root] # all nodes at current level 17 | cur_height=self.root.height # height of nodes at current level 18 | sep=' '*(2**(cur_height-1)) # variable sized separator between elements 19 | while True: 20 | cur_height+=-1 # decrement current height 21 | if len(cur_nodes)==0: break 22 | cur_row=' ' 23 | next_row='' 24 | next_nodes=[] 25 | 26 | if all(n is None for n in cur_nodes): 27 | break 28 | 29 | for n in cur_nodes: 30 | 31 | if n==None: 32 | cur_row+=' '+sep 33 | next_row+=' '+sep 34 | next_nodes.extend([None,None]) 35 | continue 36 | 37 | if n.value!=None: 38 | buf=' '*int((5-len(str(n.value)))/2) 39 | cur_row+='%s%s%s'%(buf,str(n.value),buf)+sep 40 | else: 41 | cur_row+=' '*5+sep 42 | 43 | if n.left_child!=None: 44 | next_nodes.append(n.left_child) 45 | next_row+=' /'+sep 46 | else: 47 | next_row+=' '+sep 48 | next_nodes.append(None) 49 | 50 | if n.right_child!=None: 51 | next_nodes.append(n.right_child) 52 | next_row+='\ '+sep 53 | else: 54 | next_row+=' '+sep 55 | next_nodes.append(None) 56 | 57 | content+=(cur_height*' '+cur_row+'\n'+cur_height*' '+next_row+'\n') 58 | cur_nodes=next_nodes 59 | sep=' '*int(len(sep)/2) # cut separator size in half 60 | return content 61 | 62 | def insert(self,value): 63 | if self.root==None: 64 | self.root=node(value) 65 | else: 66 | self._insert(value,self.root) 67 | 68 | def _insert(self,value,cur_node): 69 | if valuecur_node.value: 77 | if cur_node.right_child==None: 78 | cur_node.right_child=node(value) 79 | cur_node.right_child.parent=cur_node # set parent 80 | self._inspect_insertion(cur_node.right_child) 81 | else: 82 | self._insert(value,cur_node.right_child) 83 | else: 84 | print("Value already in tree!") 85 | 86 | def print_tree(self): 87 | if self.root!=None: 88 | self._print_tree(self.root) 89 | 90 | def _print_tree(self,cur_node): 91 | if cur_node!=None: 92 | self._print_tree(cur_node.left_child) 93 | print ('%s, h=%d'%(str(cur_node.value),cur_node.height)) 94 | self._print_tree(cur_node.right_child) 95 | 96 | def height(self): 97 | if self.root!=None: 98 | return self._height(self.root,0) 99 | else: 100 | return 0 101 | 102 | def _height(self,cur_node,cur_height): 103 | if cur_node==None: return cur_height 104 | left_height=self._height(cur_node.left_child,cur_height+1) 105 | right_height=self._height(cur_node.right_child,cur_height+1) 106 | return max(left_height,right_height) 107 | 108 | def find(self,value): 109 | if self.root!=None: 110 | return self._find(value,self.root) 111 | else: 112 | return None 113 | 114 | def _find(self,value,cur_node): 115 | if value==cur_node.value: 116 | return cur_node 117 | elif valuecur_node.value and cur_node.right_child!=None: 120 | return self._find(value,cur_node.right_child) 121 | 122 | def delete_value(self,value): 123 | return self.delete_node(self.find(value)) 124 | 125 | def delete_node(self,node): 126 | 127 | ## ----- 128 | # Improvements since prior lesson 129 | 130 | # Protect against deleting a node not found in the tree 131 | if node==None or self.find(node.value)==None: 132 | print("Node to be deleted not found in the tree!") 133 | return None 134 | ## ----- 135 | 136 | # returns the node with min value in tree rooted at input node 137 | def min_value_node(n): 138 | current=n 139 | while current.left_child!=None: 140 | current=current.left_child 141 | return current 142 | 143 | # returns the number of children for the specified node 144 | def num_children(n): 145 | num_children=0 146 | if n.left_child!=None: num_children+=1 147 | if n.right_child!=None: num_children+=1 148 | return num_children 149 | 150 | # get the parent of the node to be deleted 151 | node_parent=node.parent 152 | 153 | # get the number of children of the node to be deleted 154 | node_children=num_children(node) 155 | 156 | # break operation into different cases based on the 157 | # structure of the tree & node to be deleted 158 | 159 | # CASE 1 (node has no children) 160 | if node_children==0: 161 | 162 | if node_parent!=None: 163 | # remove reference to the node from the parent 164 | if node_parent.left_child==node: 165 | node_parent.left_child=None 166 | else: 167 | node_parent.right_child=None 168 | else: 169 | self.root=None 170 | 171 | # CASE 2 (node has a single child) 172 | if node_children==1: 173 | 174 | # get the single child node 175 | if node.left_child!=None: 176 | child=node.left_child 177 | else: 178 | child=node.right_child 179 | 180 | if node_parent!=None: 181 | # replace the node to be deleted with its child 182 | if node_parent.left_child==node: 183 | node_parent.left_child=child 184 | else: 185 | node_parent.right_child=child 186 | else: 187 | self.root=child 188 | 189 | # correct the parent pointer in node 190 | child.parent=node_parent 191 | 192 | # CASE 3 (node has two children) 193 | if node_children==2: 194 | 195 | # get the inorder successor of the deleted node 196 | successor=min_value_node(node.right_child) 197 | 198 | # copy the inorder successor's value to the node formerly 199 | # holding the value we wished to delete 200 | node.value=successor.value 201 | 202 | # delete the inorder successor now that it's value was 203 | # copied into the other node 204 | self.delete_node(successor) 205 | 206 | # exit function so we don't call the _inspect_deletion twice 207 | return 208 | 209 | if node_parent!=None: 210 | # fix the height of the parent of current node 211 | node_parent.height=1+max(self.get_height(node_parent.left_child),self.get_height(node_parent.right_child)) 212 | 213 | # begin to traverse back up the tree checking if there are 214 | # any sections which now invalidate the AVL balance rules 215 | self._inspect_deletion(node_parent) 216 | 217 | def search(self,value): 218 | if self.root!=None: 219 | return self._search(value,self.root) 220 | else: 221 | return False 222 | 223 | def _search(self,value,cur_node): 224 | if value==cur_node.value: 225 | return True 226 | elif valuecur_node.value and cur_node.right_child!=None: 229 | return self._search(value,cur_node.right_child) 230 | return False 231 | 232 | 233 | # Functions added for AVL... 234 | 235 | def _inspect_insertion(self,cur_node,path=[]): 236 | if cur_node.parent==None: return 237 | path=[cur_node]+path 238 | 239 | left_height =self.get_height(cur_node.parent.left_child) 240 | right_height=self.get_height(cur_node.parent.right_child) 241 | 242 | if abs(left_height-right_height)>1: 243 | path=[cur_node.parent]+path 244 | self._rebalance_node(path[0],path[1],path[2]) 245 | return 246 | 247 | new_height=1+cur_node.height 248 | if new_height>cur_node.parent.height: 249 | cur_node.parent.height=new_height 250 | 251 | self._inspect_insertion(cur_node.parent,path) 252 | 253 | def _inspect_deletion(self,cur_node): 254 | if cur_node==None: return 255 | 256 | left_height =self.get_height(cur_node.left_child) 257 | right_height=self.get_height(cur_node.right_child) 258 | 259 | if abs(left_height-right_height)>1: 260 | y=self.taller_child(cur_node) 261 | x=self.taller_child(y) 262 | self._rebalance_node(cur_node,y,x) 263 | 264 | self._inspect_deletion(cur_node.parent) 265 | 266 | def _rebalance_node(self,z,y,x): 267 | if y==z.left_child and x==y.left_child: 268 | self._right_rotate(z) 269 | elif y==z.left_child and x==y.right_child: 270 | self._left_rotate(y) 271 | self._right_rotate(z) 272 | elif y==z.right_child and x==y.right_child: 273 | self._left_rotate(z) 274 | elif y==z.right_child and x==y.left_child: 275 | self._right_rotate(y) 276 | self._left_rotate(z) 277 | else: 278 | raise Exception('_rebalance_node: z,y,x node configuration not recognized!') 279 | 280 | def _right_rotate(self,z): 281 | sub_root=z.parent 282 | y=z.left_child 283 | t3=y.right_child 284 | y.right_child=z 285 | z.parent=y 286 | z.left_child=t3 287 | if t3!=None: t3.parent=z 288 | y.parent=sub_root 289 | if y.parent==None: 290 | self.root=y 291 | else: 292 | if y.parent.left_child==z: 293 | y.parent.left_child=y 294 | else: 295 | y.parent.right_child=y 296 | z.height=1+max(self.get_height(z.left_child), 297 | self.get_height(z.right_child)) 298 | y.height=1+max(self.get_height(y.left_child), 299 | self.get_height(y.right_child)) 300 | 301 | def _left_rotate(self,z): 302 | sub_root=z.parent 303 | y=z.right_child 304 | t2=y.left_child 305 | y.left_child=z 306 | z.parent=y 307 | z.right_child=t2 308 | if t2!=None: t2.parent=z 309 | y.parent=sub_root 310 | if y.parent==None: 311 | self.root=y 312 | else: 313 | if y.parent.left_child==z: 314 | y.parent.left_child=y 315 | else: 316 | y.parent.right_child=y 317 | z.height=1+max(self.get_height(z.left_child), 318 | self.get_height(z.right_child)) 319 | y.height=1+max(self.get_height(y.left_child), 320 | self.get_height(y.right_child)) 321 | 322 | def get_height(self,cur_node): 323 | if cur_node==None: return 0 324 | return cur_node.height 325 | 326 | def taller_child(self,cur_node): 327 | left=self.get_height(cur_node.left_child) 328 | right=self.get_height(cur_node.right_child) 329 | return cur_node.left_child if left>=right else cur_node.right_child 330 | -------------------------------------------------------------------------------- /BST_Validator/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Node: 4 | def __init__(self,value=None): 5 | self.value=value 6 | self.left_child=None 7 | self.right_child=None 8 | 9 | def validate_bst(root,min=-sys.maxsize,max=sys.maxsize): 10 | if root==None: 11 | return True 12 | if (root.value>min and 13 | root.valuecur_node.value: 26 | if cur_node.right_child==None: 27 | cur_node.right_child=node(value) 28 | cur_node.right_child.parent=cur_node # set parent 29 | else: 30 | self._insert(value,cur_node.right_child) 31 | else: 32 | print("Value already in tree!") 33 | 34 | def print_tree(self): 35 | if self.root!=None: 36 | self._print_tree(self.root) 37 | 38 | def _print_tree(self,cur_node): 39 | if cur_node!=None: 40 | self._print_tree(cur_node.left_child) 41 | print (str(cur_node.value)) 42 | self._print_tree(cur_node.right_child) 43 | 44 | def height(self): 45 | if self.root!=None: 46 | return self._height(self.root,0) 47 | else: 48 | return 0 49 | 50 | def _height(self,cur_node,cur_height): 51 | if cur_node==None: return cur_height 52 | left_height=self._height(cur_node.left_child,cur_height+1) 53 | right_height=self._height(cur_node.right_child,cur_height+1) 54 | return max(left_height,right_height) 55 | 56 | def find(self,value): 57 | if self.root!=None: 58 | return self._find(value,self.root) 59 | else: 60 | return None 61 | 62 | def _find(self,value,cur_node): 63 | if value==cur_node.value: 64 | return cur_node 65 | elif valuecur_node.value and cur_node.right_child!=None: 68 | return self._find(value,cur_node.right_child) 69 | 70 | def delete_value(self,value): 71 | return self.delete_node(self.find(value)) 72 | 73 | def delete_node(self,node): 74 | 75 | ## ----- 76 | # Improvements since prior lesson 77 | 78 | # Protect against deleting a node not found in the tree 79 | if node==None or self.find(node.value)==None: 80 | print("Node to be deleted not found in the tree!") 81 | return None 82 | ## ----- 83 | 84 | # returns the node with min value in tree rooted at input node 85 | def min_value_node(n): 86 | current=n 87 | while current.left_child!=None: 88 | current=current.left_child 89 | return current 90 | 91 | # returns the number of children for the specified node 92 | def num_children(n): 93 | num_children=0 94 | if n.left_child!=None: num_children+=1 95 | if n.right_child!=None: num_children+=1 96 | return num_children 97 | 98 | # get the parent of the node to be deleted 99 | node_parent=node.parent 100 | 101 | # get the number of children of the node to be deleted 102 | node_children=num_children(node) 103 | 104 | # break operation into different cases based on the 105 | # structure of the tree & node to be deleted 106 | 107 | # CASE 1 (node has no children) 108 | if node_children==0: 109 | 110 | # Added this if statement post-video, previously if you 111 | # deleted the root node it would delete entire tree. 112 | if node_parent!=None: 113 | # remove reference to the node from the parent 114 | if node_parent.left_child==node: 115 | node_parent.left_child=None 116 | else: 117 | node_parent.right_child=None 118 | else: 119 | self.root=None 120 | 121 | # CASE 2 (node has a single child) 122 | if node_children==1: 123 | 124 | # get the single child node 125 | if node.left_child!=None: 126 | child=node.left_child 127 | else: 128 | child=node.right_child 129 | 130 | # Added this if statement post-video, previously if you 131 | # deleted the root node it would delete entire tree. 132 | if node_parent!=None: 133 | # replace the node to be deleted with its child 134 | if node_parent.left_child==node: 135 | node_parent.left_child=child 136 | else: 137 | node_parent.right_child=child 138 | else: 139 | self.root=child 140 | 141 | # correct the parent pointer in node 142 | child.parent=node_parent 143 | 144 | # CASE 3 (node has two children) 145 | if node_children==2: 146 | 147 | # get the inorder successor of the deleted node 148 | successor=min_value_node(node.right_child) 149 | 150 | # copy the inorder successor's value to the node formerly 151 | # holding the value we wished to delete 152 | node.value=successor.value 153 | 154 | # delete the inorder successor now that it's value was 155 | # copied into the other node 156 | self.delete_node(successor) 157 | 158 | def search(self,value): 159 | if self.root!=None: 160 | return self._search(value,self.root) 161 | else: 162 | return False 163 | 164 | def _search(self,value,cur_node): 165 | if value==cur_node.value: 166 | return True 167 | elif valuecur_node.value and cur_node.right_child!=None: 170 | return self._search(value,cur_node.right_child) 171 | return False -------------------------------------------------------------------------------- /Linked_List/main.py: -------------------------------------------------------------------------------- 1 | 2 | class node: 3 | def __init__(self,data=None): 4 | self.data=data 5 | self.next=None 6 | 7 | class linked_list: 8 | def __init__(self): 9 | self.head=node() 10 | 11 | # Adds new node containing 'data' to the end of the linked list. 12 | def append(self,data): 13 | new_node=node(data) 14 | cur=self.head 15 | while cur.next!=None: 16 | cur=cur.next 17 | cur.next=new_node 18 | 19 | # Returns the length (integer) of the linked list. 20 | def length(self): 21 | cur=self.head 22 | total=0 23 | while cur.next!=None: 24 | total+=1 25 | cur=cur.next 26 | return total 27 | 28 | # Prints out the linked list in traditional Python list format. 29 | def display(self): 30 | elems=[] 31 | cur_node=self.head 32 | while cur_node.next!=None: 33 | cur_node=cur_node.next 34 | elems.append(cur_node.data) 35 | print(elems) 36 | 37 | # Returns the value of the node at 'index'. 38 | def get(self,index): 39 | if index>=self.length() or index<0: # added 'index<0' post-video 40 | print("ERROR: 'Get' Index out of range!") 41 | return None 42 | cur_idx=0 43 | cur_node=self.head 44 | while True: 45 | cur_node=cur_node.next 46 | if cur_idx==index: return cur_node.data 47 | cur_idx+=1 48 | 49 | # Deletes the node at index 'index'. 50 | def erase(self,index): 51 | if index>=self.length() or index<0: # added 'index<0' post-video 52 | print("ERROR: 'Erase' Index out of range!") 53 | return 54 | cur_idx=0 55 | cur_node=self.head 56 | while True: 57 | last_node=cur_node 58 | cur_node=cur_node.next 59 | if cur_idx==index: 60 | last_node.next=cur_node.next 61 | return 62 | cur_idx+=1 63 | 64 | # Allows for bracket operator syntax (i.e. a[0] to return first item). 65 | def __getitem__(self,index): 66 | return self.get(index) 67 | 68 | 69 | ####################################################### 70 | # Functions added after video tutorial 71 | 72 | # Inserts a new node at index 'index' containing data 'data'. 73 | # Indices begin at 0. If the provided index is greater than or 74 | # equal to the length of the linked list the 'data' will be appended. 75 | def insert(self,index,data): 76 | if index>=self.length() or index<0: 77 | return self.append(data) 78 | cur_node=self.head 79 | prior_node=self.head 80 | cur_idx=0 81 | while True: 82 | cur_node=cur_node.next 83 | if cur_idx==index: 84 | new_node=node(data) 85 | prior_node.next=new_node 86 | new_node.next=cur_node 87 | return 88 | prior_node=cur_node 89 | cur_idx+=1 90 | 91 | # Inserts the node 'node' at index 'index'. Indices begin at 0. 92 | # If the 'index' is greater than or equal to the length of the linked 93 | # list the 'node' will be appended. 94 | def insert_node(self,index,node): 95 | if index<0: 96 | print("ERROR: 'Erase' Index cannot be negative!") 97 | return 98 | if index>=self.length(): # append the node 99 | cur_node=self.head 100 | while cur_node.next!=None: 101 | cur_node=cur_node.next 102 | cur_node.next=node 103 | return 104 | cur_node=self.head 105 | prior_node=self.head 106 | cur_idx=0 107 | while True: 108 | cur_node=cur_node.next 109 | if cur_idx==index: 110 | prior_node.next=node 111 | return 112 | prior_node=cur_node 113 | cur_idx+=1 114 | 115 | # Sets the data at index 'index' equal to 'data'. 116 | # Indices begin at 0. If the 'index' is greater than or equal 117 | # to the length of the linked list a warning will be printed 118 | # to the user. 119 | def set(self,index,data): 120 | if index>=self.length() or index<0: 121 | print("ERROR: 'Set' Index out of range!") 122 | return 123 | cur_node=self.head 124 | cur_idx=0 125 | while True: 126 | cur_node=cur_node.next 127 | if cur_idx==index: 128 | cur_node.data=data 129 | return 130 | cur_idx+=1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python3_Data_Structures 2 | Code from [Youtube Tutorial Series](https://www.youtube.com/playlist?list=PLEJyjB1oGzx3iTZvOVedkT8nZ2cG105U7). Each lesson begins by introducing the idea behind the data structure then, after explaining some basic concepts, moves over to coding the actual Python class. The code in this repository is all implemented in Python 3, for Python 2 see ['Python_Data_Structures'](https://github.com/bfaure/Python_Data_Structures). 3 | 4 | ## [AVL Tree (AVL_Tree)](https://www.youtube.com/watch?v=lxHF-mVdwK8) 5 | The AVL Tree is an improvement upon the traditional Binary Search Tree (BST) that implements an auto-balancing feature, with the hopes to keep tree operations closer to O(logn) rather than O(n). After insertions and deletions that cause the tree to become unbalanced, special functions are called to manage the situation by rebalancing any nodes they find to be unbalanced. If you don't have experience with traditional BSTs you should start with the tutorial covering those. 6 | 7 | ## [Binary Search Tree Validator (BST_Validator)](https://www.youtube.com/watch?v=azupT01iC78) 8 | The BST validator is a function whose purpose is to check whether the input BST adheres to the rules of a binary search tree, namely that for every node, the subtree rooted at its left child contains only smaller values, and the subtree rooted at its right child contains only larger values. The code for this lesson is simple once explained but is not that easy to come up with from scratch. 9 | 10 | ## [Binary_Search_Tree](https://www.youtube.com/watch?v=f5dU3xoE6ms) 11 | The most popular data structure, the binary search tree is an intuitive way of storing sortable data that provides faster-than-linear search capabilities. Values in a BST are wrapped in a 'node' class used to link the tree together. Given a certain node 'n' with value 'v', all nodes to the right of 'n' contain values larger than 'v', and all nodes to the left contain values smaller than 'v'. 12 | 13 | ## [Linked_List](https://www.youtube.com/watch?v=JlMyYuY1aXU) 14 | The linked list is a useful data structure providing similar functionality to an array, but in a dynamic package. Albeit, not as useful in Python as in statically typed languages, the linked list is still an interesting project to undertake. Similar to BST, values in a linked list are wrapped in an instance of a 'node' class containing a pointer, allowing the whole list to be linked together by distributed references. 15 | 16 | --------------------------------------------------------------------------------