diff --git a/Data_Structures_Questions.md b/Data_Structures_Questions.md index 4446a05144..2de933abec 100644 --- a/Data_Structures_Questions.md +++ b/Data_Structures_Questions.md @@ -2,7 +2,7 @@ Answer the following questions for each of the data structures you implemented a ## Stack -1. What is the runtime complexity of `push` using a list? +1. What is the runtime complexity of `push` using a list? 2. What is the runtime complexity of `push` using a linked list? diff --git a/binary_search_tree/binary_search_tree.py b/binary_search_tree/binary_search_tree.py index d80d9f6282..117fececfd 100644 --- a/binary_search_tree/binary_search_tree.py +++ b/binary_search_tree/binary_search_tree.py @@ -1,14 +1,16 @@ """ -Binary search trees are a data structure that enforce an ordering over -the data they store. That ordering in turn makes it a lot more efficient -at searching for a particular piece of data in the tree. - +Binary search trees are a data structure that enforce an ordering over +the data they store. That ordering in turn makes it a lot more efficient +at searching for a particular piece of data in the tree. This part of the project comprises two days: 1. Implement the methods `insert`, `contains`, `get_max`, and `for_each` on the BSTNode class. 2. Implement the `in_order_print`, `bft_print`, and `dft_print` methods on the BSTNode class. """ +from queue import Queue + + class BSTNode: def __init__(self, value): self.value = value @@ -17,20 +19,63 @@ def __init__(self, value): # Insert the given value into the tree def insert(self, value): - pass - + # Empty tree use case + if (self.left is None) & (self.right is None): + if value >= self.value: + self.right = BSTNode(value) + else: + self.left = BSTNode(value) + elif (value < self.value): + # value goes to left branch of root + if self.left is None: + self.left = BSTNode(value) + else: + self.left.insert(value) + else: + # Value goes to the right branch of root + if self.right is None: + self.right = BSTNode(value) + else: + self.right.insert(value) # Return True if the tree contains the value # False if it does not + def contains(self, target): - pass + if self.value == target: + return True + elif target < self.value: + if self.left is None: + return False + else: + return self.left.contains(target) + else: + if self.right is None: + return False + else: + return self.right.contains(target) # Return the maximum value found in the tree def get_max(self): - pass + # go right until you cannot anymore + # return value + if self.right is None: + return self.value + else: + self.right.get_max() # Call the function `fn` on the value of each node def for_each(self, fn): - pass + fn(self.value) + # base case - no children + if self.left is None and self.right is None: + return + # recursive case - 1 or more children + # go left, call fn(value) for each node + if self.left: + self.left.for_each(fn) + # go right, call fn(value) for each node + if self.right: + self.right.for_each(fn) # Part 2 ----------------------- @@ -42,12 +87,47 @@ def in_order_print(self): # Print the value of every node, starting with the given node, # in an iterative breadth first traversal def bft_print(self): - pass + # instantiate a Queue + q = Queue() + # insert the value + q.enqueue(self) + + # while length of q is greater than 0 + while q.size > 0: + # pop off the top + top = q.dequeue() + # print it + print(top.value) + # if there is a left child + if top.left: + # add left child to queue + q.enqueue(top.left) + # if there is a right child + if top.right: + # add right child to queue + q.enqueue(top.right) # Print the value of every node, starting with the given node, # in an iterative depth first traversal def dft_print(self): - pass + # create a stack to keep track of nodes we are processing + # push self into stack + # if a tree exists + if self: + # print the current value (as it's the first traversal) + print(self.value) + # if there is a left child + if self.left: + # re-run function with left child as root of tree + self.left.dft_print() + # if there is a right child + if self.right: + # re-run function with right child as root of tree + self.right.dft_print() + # while something still in the stack (not done processing all nodes) + # use existing 'for_each' as a reference for the traversal logic + # push when we START, pop when a node is DONE + # and don't forget to call print() # Stretch Goals ------------------------- # Note: Research may be required @@ -60,9 +140,9 @@ def pre_order_dft(self): def post_order_dft(self): pass -""" + """ This code is necessary for testing the `print` methods -""" + """ bst = BSTNode(1) bst.insert(8) @@ -82,4 +162,4 @@ def post_order_dft(self): print("in order") bst.in_order_dft() print("post order") -bst.post_order_dft() +bst.post_order_dft() diff --git a/doubly_linked_list/doubly_linked_list.py b/doubly_linked_list/doubly_linked_list.py index 6f91b43a9b..42b7036c22 100644 --- a/doubly_linked_list/doubly_linked_list.py +++ b/doubly_linked_list/doubly_linked_list.py @@ -3,10 +3,16 @@ as well as its next node in the List. """ class ListNode: - def __init__(self, value, prev=None, next=None): - self.prev = prev + def __init__(self, value, prev_node=None, next_node=None): + self.prev = prev_node self.value = value - self.next = next + self.next = next_node + + def delete(self): + if self.prev: + self.prev.next = self.next + if self.next: + self.next.prev = self.prev """ Our doubly-linked list class. It holds references to @@ -27,56 +33,140 @@ def __len__(self): the old head node's previous pointer accordingly. """ def add_to_head(self, value): - pass - + # create new_node + new_node = ListNode(value) + # 1. add to empty + if self.head is None: + self.head = new_node + self.tail = new_node + # 2. add to nonempty + else: + new_node.next = self.head + self.head.prev = new_node + self.head = new_node + # update length + self.length += 1 + """ Removes the List's current head node, making the current head's next node the new head of the List. Returns the value of the removed Node. """ def remove_from_head(self): - pass - + # save value to return + value = self.head.value + self.delete(self.head) + return value + """ Wraps the given value in a ListNode and inserts it as the new tail of the list. Don't forget to handle the old tail node's next pointer accordingly. """ def add_to_tail(self, value): - pass - + new_node = ListNode(value) + # empty list + if self.head is None and self.tail is None: + # set new_node to head and tail + self.head = new_node + self.tail = new_node + else: + # have the current tail's 'next' pointing to the new node + self.tail.next = new_node + # then set the new node to now be the tail + self.tail = new_node + self.length += 1 + """ Removes the List's current tail node, making the current tail's previous node the new tail of the List. Returns the value of the removed Node. """ def remove_from_tail(self): - pass + self.length -= 1 + # empty list + if self.head is None and self.tail is None: + return + else: + # store tail value before removal + value = self.tail.value + # 1 element + if self.tail == self.head: + self.tail = None + self.head = None + self.length = 0 + return value + self.tail = self.tail.prev + if self.tail: + self.tail.next = None + self.length -= 1 + return value """ Removes the input node from its current spot in the List and inserts it as the new head node of the List. """ def move_to_front(self, node): - pass - + # 1. delete() + if node is self.head: + return + self.delete(node) + # 2. add_to_head() + self.add_to_head(node.value) + + """ Removes the input node from its current spot in the List and inserts it as the new tail node of the List. """ def move_to_end(self, node): - pass + value = node.value + if self.head == node: + self.head = self.head.next + self.head.prev = None + node.delete() + self.length -= 1 + self.add_to_tail(value) """ Deletes the input node from the List, preserving the order of the other elements of the List. """ def delete(self, node): - pass + # don't need to return value + # DO need to update head and tail + if self.head is None: + return None + elif self.head is self.tail: + self.head = None + self.tail = None + elif node is self.head: # list has +2 nodes + self.head = node.next + node.delete() # updating prev and/or next pointers + elif node is self.tail: + self.tail = node.prev + node.delete() + else: + node.delete() + + self.length -= 1 """ Finds and returns the maximum value of all the nodes in the List. """ def get_max(self): - pass \ No newline at end of file + # empty list + if self.head is None: + return None + # keep track of current node + # keep track of max + cur_node = self.head + max_value = self.head.value + # loop through DLL + while cur_node: # same as saying is not none + # comparing with cur_max + if cur_node.value > max_value: + max_value = cur_node.value + cur_node = cur_node.next + return max_value \ No newline at end of file diff --git a/queue/queue.py b/queue/queue.py index 0d2599ded7..db7088411f 100644 --- a/queue/queue.py +++ b/queue/queue.py @@ -16,13 +16,20 @@ class Queue: def __init__(self): self.size = 0 - # self.storage = ? + self.storage = [] + def __len__(self): - pass + return self.size def enqueue(self, value): - pass + self.storage.insert(0, value) + self.size += 1 def dequeue(self): - pass + if self.size == 0: + return None + else: + self.size -= 1 + return self.storage.pop() +# first in first out diff --git a/singly_linked_list/singly_linked_list.py b/singly_linked_list/singly_linked_list.py index e69de29bb2..46b8b0b548 100644 --- a/singly_linked_list/singly_linked_list.py +++ b/singly_linked_list/singly_linked_list.py @@ -0,0 +1,127 @@ + # a class that represents the individual elements in our LL + + +class Node: + def __init__(self, value=None, next_node=None): + self.value = value + self.next_node = next_node + + def get_value(self): + return self.value + + def get_next_node(self): + return self.next_node + + def set_next_node(self, new_next): + self.next_node = new_next + + +class LinkedList: + def __init__(self): + # what attributes do we need? + self.head = None + self.tail = None + + def add_to_head(self, value): + # create a new Node + new_node = Node(value) + if self.head is None: + # update head and tail attributes + self.head = new_node + self.tail = new_node + else: + # set next_node of my new Node to the head + new_node.set_next_node(self.head) + # update head attribute + self.head = new_node + + def add_to_tail(self, value): + # create a new Node + new_node = Node(value) + # 1. LL is empty + if self.head is None: + # update head & tail attributes + self.head = new_node + self.tail = new_node + + # 2 LL is NOT empty + else: + # update next_node of our tail + self.tail.set_next_node(new_node) + # update self.tail + self.tail = new_node + + def remove_head(self): + # empty list + if self.head is None: + return None + # else, return Value of the old head + else: + ret_value = self.head.get_value() + # list with 1 element + if self.head == self.tail: + self.head = None + self.tail = None + # list with +2 elements + else: + self.head = self.head.get_next_node() + return ret_value + + def remove_tail(self): + # empty list + # return None + if self.head is None: + return None + # list with 1 element + # save the value... update head and tail attr = None + # then return value + elif self.head == self.tail: + ret_value = self.tail.get_value() + self.head = None + self.tail = None + return ret_value + # list with +2 elements? + # save value of tail + # ref a temp node + # while node is not the tail + # keep going + # update pointer of temp node (prev_tail) to None + # return value + else: + ret_value = self.tail.get_value() + + cur_node = self.head + while cur_node.get_next_node() is not self.tail: + cur_node = cur_node.get_next_node() + + cur_node.set_next_node(None) + self.tail = cur_node + return ret_value + + def contains(self, value): + # loop through LL until pointer is None + cur_node = self.head + while cur_node is not None: + # if we find 'value' + if cur_node.get_value() == value: + return True + # next_node + cur_node = cur_node.get_next_node() + return False + + def get_max(self): + # empty list + if self.head is None: + return None + # iterate through all elements + cur_node = self.head + # set max to first node + cur_max = self.head.get_value() + while cur_node is not None: + # search for higher values + if cur_node.get_value() > cur_max: + # set new max + cur_max = cur_node.get_value() + # move on to next node + cur_node = cur_node.get_next_node() + return cur_max diff --git a/stack/stack.py b/stack/stack.py index 6e6d660ac7..cc384ab515 100644 --- a/stack/stack.py +++ b/stack/stack.py @@ -13,13 +13,17 @@ class Stack: def __init__(self): self.size = 0 - # self.storage = ? + self.storage = [] def __len__(self): - pass + return self.size def push(self, value): - pass + self.storage.append(value) + self.size += 1 def pop(self): - pass + if self.size > 0: + self.size -= 1 + return self.storage.pop() +# last in first out \ No newline at end of file