diff --git a/Data_Structures_ChatGPT.txt b/Data_Structures_ChatGPT.txt new file mode 100644 index 0000000..4b30c59 --- /dev/null +++ b/Data_Structures_ChatGPT.txt @@ -0,0 +1 @@ +https://chatgpt.com/share/67538240-b1e0-8007-9b72-3fff133c78f5 diff --git a/data_structures/10_Graph/my_graph.py b/data_structures/10_Graph/my_graph.py new file mode 100644 index 0000000..3d0cd41 --- /dev/null +++ b/data_structures/10_Graph/my_graph.py @@ -0,0 +1,73 @@ +class Graph(): + def __init__(self, elements): + self.elements = elements + self.graph_dict = {} + + for start, end in elements: + if start in self.graph_dict: + self.graph_dict[start].append(end) + else: + self.graph_dict[start] = [end] + + print("Graph dict: ", self.graph_dict) + + def get_paths(self, start, end, path=[]): + path = path + [start] + + if start == end: + return [path] + + if start not in self.graph_dict: + return [] + + paths = [] + for node in self.graph_dict[start]: + if node not in path: + new_paths = self.get_paths(node, end, path) + for p in new_paths: + paths.append(p) + + return paths + + def get_shortest_path(self, start, end, path=[]): + path = path + [start] + + if start == end: + return path + + if start not in self.graph_dict: + return None + + shortest_path = None + for node in self.graph_dict[start]: + if node not in path: + sp = self.get_shortest_path(node, end, path) + if sp: + if shortest_path is None or len(sp) < len(shortest_path): + shortest_path = sp + return shortest_path + +if __name__ == '__main__': + + routes = [ + ("Mumbai", "Paris"), + ("Mumbai", "Dubai"), + ("Paris", "Dubai"), + ("Paris", "New York"), + ("Dubai", "New York"), + ("New York", "Toronto"), + ] + + route_graph = Graph(routes) + + start = "Mumbai" + end = "Mumbai" + + print(f"\nAll paths between: {start} and {end}:\n" + '\n'.join([' -> '.join(path) for path in route_graph.get_paths(start, end)])) + print(f"\nShortest path between {start} and {end}:\n" + ' -> '.join(route_graph.get_shortest_path(start,end))) + + start = "Mumbai" + end = "New York" + + print(f"\nAll paths between: {start} and {end}:\n" + '\n'.join([' -> '.join(path) for path in route_graph.get_paths(start, end)])) + print(f"\nShortest path between {start} and {end}:\n" + ' -> '.join(route_graph.get_shortest_path(start,end))) diff --git a/data_structures/10_Graph/test.ipynb b/data_structures/10_Graph/test.ipynb new file mode 100644 index 0000000..8b253b0 --- /dev/null +++ b/data_structures/10_Graph/test.ipynb @@ -0,0 +1,74 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n", + "2\n", + "0\n", + "1\n" + ] + } + ], + "source": [ + "a=6\n", + "b=5\n", + "print(a//2)\n", + "print(b//2)\n", + "print(a%2)\n", + "print(b%2)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[9, 8, 7, 6, 5, 4, 3]\n" + ] + } + ], + "source": [ + "print([i for i in range(10)[:2:-1]])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "data_analysis", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/data_structures/11_Heap/heap.py b/data_structures/11_Heap/heap.py new file mode 100644 index 0000000..29cfa70 --- /dev/null +++ b/data_structures/11_Heap/heap.py @@ -0,0 +1,196 @@ +''' +# Heap + +### What is a Heap? +- A heap is a complete binary tree that satisfies the heap property. +- The heap property states that the key stored in each node is either greater than or equal to (≥) - for a maxheap - or + less than or equal to (≤) - for a min heap - the keys in the node's children, according to some total order. + +### Python Library: +- Python has a built-in library called heapq from that provides heap data structure. Ex.: +import heapq + +### Time Complexity: + + - insert: O(log n) + - get_min: O(1) + - extract_min: O(log n) + - update: O(log n) if we have the index, O(n) if we don't + - build: O(n) + +''' + +class MinHeap: + def __init__(self, arr=None): # Heapifies the array in O(n) time + self.heap = [] + if type(arr) is list: + self.heap = arr.copy() + for i in range(len(self.heap)//2-1, -1, -1): + self._siftdown(i) + + def _siftup(self, i): + parent = (i-1)//2 + while i != 0 and self.heap[i] < self.heap[parent]: + self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i] + i = parent + parent = (i-1)//2 + + def _siftdown(self, i): + left = 2*i + 1 + right = 2*i + 2 + while (left < len(self.heap) and self.heap[i] > self.heap[left]) or (right < len(self.heap) and self.heap[i] > self.heap[right]): + smallest = left if (right >= len(self.heap) or self.heap[left] < self.heap[right]) else right + self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i] + i = smallest + left = 2*i + 1 + right = 2*i + 2 + + def insert(self, element): + self.heap.append(element) + self._siftup(len(self.heap)-1) + + def get_min(self): + return self.heap[0] if len(self.heap) > 0 else None + + def extract_min(self): + if len(self.heap) == 0: + return None + minval = self.heap[0] + self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0] + self.heap.pop() + self._siftdown(0) + return minval + + def update_by_index(self, i, new): + old = self.heap[i] + self.heap[i] = new + if new < old: + self._siftup(i) + else: + self._siftdown(i) + + def update(self, old, new): + if old in self.heap: + self.update_by_index(self.heap.index(old), new) + +class MaxHeap: + def __init__(self, arr=None): + self.heap = [] + if type(arr) is list: + self.heap = arr.copy() + for i in range(len(self.heap))[::-1]: + self._siftdown(i) + + def _siftup(self, i): + parent = (i-1)//2 + while i != 0 and self.heap[i] > self.heap[parent]: + self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i] + i = parent + parent = (i-1)//2 + + def _siftdown(self, i): + left = 2*i + 1 + right = 2*i + 2 + while (left < len(self.heap) and self.heap[i] < self.heap[left]) or (right < len(self.heap) and self.heap[i] < self.heap[right]): + biggest = left if (right >= len(self.heap) or self.heap[left] > self.heap[right]) else right + self.heap[i], self.heap[biggest] = self.heap[biggest], self.heap[i] + i = biggest + left = 2*i + 1 + right = 2*i + 2 + + def insert(self, element): + self.heap.append(element) + self._siftup(len(self.heap)-1) + + def get_max(self): + return self.heap[0] if len(self.heap) > 0 else None + + def extract_max(self): + if len(self.heap) == 0: + return None + maxval = self.heap[0] + self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0] + self.heap.pop() + self._siftdown(0) + return maxval + + def update_by_index(self, i, new): + old = self.heap[i] + self.heap[i] = new + if new > old: + self._siftup(i) + else: + self._siftdown(i) + + def update(self, old, new): + if old in self.heap: + self.update_by_index(self.heap.index(old), new) + + +def heapsort(arr): + heap = MinHeap(arr) + return [heap.extract_min() for i in range(len(heap.heap))] + + +class PriorityQueue: + def __init__(self): + self.queue = MaxHeap() + + def enqueue(self, element): # Time complexity is O(log n) + self.queue.insert(element) + + def peek(self): # Time complexity is O(1) + return self.queue.get_max() + + def dequeue(self, element): # Time complexity is O(log n) + return self.queue.extract_max() + + def is_empty(self): # Time complexity is O(1) + return len(self.queue.heap) == 0 + + def change_priority_by_index(self, i, new): # Time complexity is O(log n) if we have the index + self.queue.update_by_index(i, new) + + def change_priority(self, old, new): # Time complexity is O(n) if we don't have the index + self.queue.update(old, new) + +import heapq + +if __name__ == '__main__': + arr = [-4, 3, 1, 0, 2, 5, 10, 8, 12, 9] + + # Using the custom MinHeap class + minheap = MinHeap(arr) + print('Heapmin using the custom MinHeap class:\n', minheap.heap) + minn = minheap.extract_min() + print('Min value', minn) + + print() + # Using the built-in heapq library + arr2 = arr.copy() + heapq.heapify(arr2) + print('Heapmin using heapq:\n', arr2) + minnim = heapq.heappop(arr2) + print('Min value: ', minnim) + sorted_arr = [heapq.heappop(arr2) for i in range(len(arr2))] + print('Sorted array using heapify: ', sorted_arr) + + print() + # Using the custom MaxHeap class + maxheap = MaxHeap(arr) + print('Heapmax using the custom MaxHeap class:\n', maxheap.heap) + maxn = maxheap.extract_max() + print('Max value: ', maxn) + + print() + # Using the built-in heapq library + arr3 = [-x for x in arr] + heapq.heapify(arr3) + print('Heapmax with neg sign using heapq:\n', arr3) + maxn = -heapq.heappop(arr3) + print('Max value: ', maxn) + sorted_arr = [-heapq.heappop(arr3) for i in range(len(arr3))] + + + + diff --git a/data_structures/3_LinkedList/3_doubly_linked_list.py b/data_structures/3_LinkedList/3_doubly_linked_list.py new file mode 100644 index 0000000..f154942 --- /dev/null +++ b/data_structures/3_LinkedList/3_doubly_linked_list.py @@ -0,0 +1,177 @@ +class Node: + def __init__(self, data=None, next=None, prev=None): + self.data = data + self.next = next + self.prev = prev + +class DoublyLinkedList: + def __init__(self): + self.head = None + + def get_length(self): + count = 0 + itr = self.head + while itr: + count+=1 + itr = itr.next + + return count + + def get_last_node(self): + itr = self.head + while itr.next: + itr = itr.next + + return itr + + def insert_at_beginning(self, data): + if self.head == None: + node = Node(data, self.head, None) + self.head = node + else: + node = Node(data, self.head, None) + self.head.prev = node + self.head = node + + + def insert_at_end(self, data): + if self.head is None: + self.head = Node(data, None, None) + return + + itr = self.head + + while itr.next: + itr = itr.next + + itr.next = Node(data, None, itr) + + + + def insert_at(self, index, data): + lenght = self.get_length() + if index<0 or index > lenght: + raise Exception("Invalid Index") + + if index == 0: + self.insert_at_beginning(data) + return + + count = 0 + itr = self.head + while itr: + if count == index - 1: + node = Node(data, itr.next, itr) + if node.next: + itr.next.prev = node + itr.next = node + break + + itr = itr.next + count += 1 + + def remove_at(self, index): + if index<0 or index >= self.get_length(): + return + + if count == 0: + self.head = self.head.next + self.head.prev = None + return + + count = 0 + itr = self.head + while itr: + if count == index: + itr.prev.next = itr.next + if itr.next: + itr.next.prev = itr.prev + break + + itr = itr.next + count += 1 + + def insert_values(self, data_list): + self.head = None + for data in data_list: + self.insert_at_end(data) + + + def insert_after_value(self, data_after, data_to_insert): + if self.head is None: + return + + if self.head.data==data_after: + node = Node(data_to_insert, self.head.next, self.head) + self.head.next = node + node.next.prev = node + return + + itr = self.head + while itr: + if itr.data == data_after: + node = Node(data_to_insert, self.head.next, self.head) + itr.next = node + node.next.prev = node + break + + itr = itr.next + + def remove_by_value(self, data): + if self.head is None: + return + + if self.head.data == data: + self.head = self.head.next + self.head.prev = None + return + + itr = self.head + while itr.next: + if itr.next.data == data: + itr.next = itr.next.next + itr.next.prev = itr + break + itr = itr.next + + + def print_forward(self): + if self.head is None: + print("Linked list is empty") + return + + dllist = '' + itr = self.head + while itr: + dllist += str(itr.data) + '-->' + itr = itr.next + print(dllist) + + + def print_backward(self): + if self.head is None: + print("Linked list is empty") + return + + last_node = self.get_last_node() + itr = last_node + llstr = '' + while itr: + llstr += itr.data + '-->' + itr = itr.prev + print("Link list in reverse: ", llstr) + + +if __name__ == '__main__': + ll = DoublyLinkedList() + ll.insert_values(["banana","mango","grapes","orange"]) + ll.print_forward() + ll.print_backward() + ll.insert_at_end("figs") + ll.print_forward() + ll.insert_at(0,"jackfruit") + ll.print_forward() + ll.insert_at(6,"dates") + ll.print_forward() + ll.insert_at(2,"kiwi") + ll.print_forward() \ No newline at end of file diff --git a/data_structures/3_LinkedList/3_linked_list.py b/data_structures/3_LinkedList/3_linked_list.py index a6d9466..b18d9b1 100644 --- a/data_structures/3_LinkedList/3_linked_list.py +++ b/data_structures/3_LinkedList/3_linked_list.py @@ -11,10 +11,11 @@ def print(self): if self.head is None: print("Linked list is empty") return + itr = self.head llstr = '' while itr: - llstr += str(itr.data)+' --> ' if itr.next else str(itr.data) + llstr += str(itr.data) + ' --> ' itr = itr.next print(llstr) @@ -86,14 +87,53 @@ def insert_values(self, data_list): self.insert_at_end(data) + def insert_after_value(self, data_after, data_to_insert): + if self.head is None: + return + + if self.head.data==data_after: + self.head.next = Node(data_to_insert,self.head.next) + return + + itr = self.head + while itr: + if itr.data == data_after: + itr.next = Node(data_to_insert, itr.next) + break + + itr = itr.next + + def remove_by_value(self, data): + if self.head is None: + return + + if self.head.data == data: + self.head = self.head.next + return + + itr = self.head + while itr.next: + if itr.next.data == data: + itr.next = itr.next.next + break + itr = itr.next + if __name__ == '__main__': ll = LinkedList() ll.insert_values(["banana","mango","grapes","orange"]) - ll.insert_at(1,"blueberry") - ll.remove_at(2) ll.print() - - ll.insert_values([45,7,12,567,99]) - ll.insert_at_end(67) + ll.insert_after_value("mango","apple") + ll.print() + ll.remove_by_value("orange") + ll.print() + ll.remove_by_value("figs") + ll.print() + ll.remove_by_value("banana") + ll.remove_by_value("mango") + ll.remove_by_value("apple") + ll.remove_by_value("grapes") ll.print() + # ll.insert_values([45,7,12,567,99]) + # ll.insert_at_end(67) + # ll.print() diff --git a/data_structures/5_Stack/Exercise/balance_paran.py b/data_structures/5_Stack/Solution/balance_param_solution.py similarity index 100% rename from data_structures/5_Stack/Exercise/balance_paran.py rename to data_structures/5_Stack/Solution/balance_param_solution.py diff --git a/data_structures/5_Stack/Exercise/reverse_string.py b/data_structures/5_Stack/Solution/reverse_string_solution.py similarity index 100% rename from data_structures/5_Stack/Exercise/reverse_string.py rename to data_structures/5_Stack/Solution/reverse_string_solution.py diff --git a/data_structures/5_Stack/balance_param_exercise.py b/data_structures/5_Stack/balance_param_exercise.py new file mode 100644 index 0000000..8d3646f --- /dev/null +++ b/data_structures/5_Stack/balance_param_exercise.py @@ -0,0 +1,53 @@ +from collections import deque + +class Stack(): + def __init__(self): + self.container = deque() + + def push(self, data): + self.container.append(data) + + def pop(self): + return self.container.pop() + + def peak(self): + return self.container[-1] + + def is_empty(self): + return len(self.container) == 0 + + def size(self): + return len(self.container) + +# Not optimal (below) -- see solution +def is_balanced(string): + stack = Stack() + + pairs = [('(', ')'), ('[', ']'), ('{', '}')] + + for c in string: + for start, end in pairs: + if c == start: + stack.push(c) + break + if c == end: + if stack.is_empty(): + return False + else: + if stack.peak() == start: + stack.pop() + else: + return False + + + if not stack.is_empty(): + return False + + return True + +if __name__ == '__main__': + print(is_balanced("({a+b})")) + print(is_balanced("))((a+b}{")) + print(is_balanced("((a+b))")) + print(is_balanced("))") ) + print(is_balanced("[a+b]*(x+2y)*{gg+kk}")) \ No newline at end of file diff --git a/data_structures/5_Stack/reverse_str_exercise.py b/data_structures/5_Stack/reverse_str_exercise.py new file mode 100644 index 0000000..a0941fb --- /dev/null +++ b/data_structures/5_Stack/reverse_str_exercise.py @@ -0,0 +1,38 @@ +from collections import deque + +class Stack(): + def __init__(self): + self.container = deque() + + def push(self, data): + self.container.append(data) + + def pop(self): + return self.container.pop() + + def peak(self): + return self.container[-1] + + def is_empty(self): + return len(self.container) == 0 + + def size(self): + return len(self.container) + + +def reverse_string(string): + stack = Stack() + for c in string: + stack.push(c) + + rev_str = '' + while not stack.is_empty(): + rev_str += stack.pop() + + return rev_str + + +if __name__ == '__main__': + string = "We will conquer COVID-19" + print(reverse_string(string)) + diff --git a/data_structures/6_Queue/Exercise/binary_numbers.py b/data_structures/6_Queue/Solution/binary_numbers.py similarity index 100% rename from data_structures/6_Queue/Exercise/binary_numbers.py rename to data_structures/6_Queue/Solution/binary_numbers.py diff --git a/data_structures/6_Queue/Exercise/food_ordering_system.py b/data_structures/6_Queue/Solution/food_ordering_system.py similarity index 100% rename from data_structures/6_Queue/Exercise/food_ordering_system.py rename to data_structures/6_Queue/Solution/food_ordering_system.py diff --git a/data_structures/6_Queue/binary_numbers_exercise.py b/data_structures/6_Queue/binary_numbers_exercise.py new file mode 100644 index 0000000..72a9acb --- /dev/null +++ b/data_structures/6_Queue/binary_numbers_exercise.py @@ -0,0 +1,58 @@ + +from collections import deque + +class dQueue: + def __init__(self): + self.buffer = deque() + + def enqueue(self, val): + self.buffer.appendleft(val) + + def dequeue(self): + if len(self.buffer)==0: + print("Queue is empty") + return + + return self.buffer.pop() + + def front(self): + if len(self.buffer) != 0: + return self.buffer[-1] + + def is_empty(self): + return len(self.buffer) == 0 + + def size(self): + return len(self.buffer) + + +def print_binary(end): + queue = dQueue() + queue.enqueue('1') + + for i in range(end): + binary_number = queue.dequeue() + print(' ' + binary_number) + + queue.enqueue(binary_number + '0') + queue.enqueue(binary_number + '1') + +# Using queue python library +from queue import Queue +def print_binary_2(end): + queue = Queue() + queue.put('1') + + for i in range(end): + binary_number = queue.get() + print(' ' + binary_number) + + queue.put(binary_number + '0') + queue.put(binary_number + '1') + + +if __name__ == '__main__': + + print_binary(12) + print() + print_binary_2(12) \ No newline at end of file diff --git a/data_structures/6_Queue/food_ordering_system.py b/data_structures/6_Queue/food_ordering_system.py new file mode 100644 index 0000000..b6787c6 --- /dev/null +++ b/data_structures/6_Queue/food_ordering_system.py @@ -0,0 +1,53 @@ +from collections import deque +import threading +import time +import sys + +class Queue(): + def __init__(self): + self.buffer = deque() + + def enqueue(self, data): + self.buffer.appendleft(data) + + def dequeue(self): + return self.buffer.pop() + + def is_empty(self): + return len(self.buffer) == 0 + + def size(self): + return len(self.buffer) + + +def place_order(orders): + global queue + for order in orders: + print('Placing order for: ', order) + queue.enqueue(order) + time.sleep(0.5) + +def serve_order(): + global queue + time.sleep(1) + while not queue.is_empty(): + print(queue.dequeue()) + time.sleep(2) + + + +if __name__ == '__main__': + + queue = Queue() + + orders = ['pizza','samosa','pasta','biryani','burger'] + + place_order_thread = threading.Thread(target=place_order, args=(orders,)) + serve_order_thread = threading.Thread(target=serve_order) + + place_order_thread.start() + serve_order_thread.start() + + place_order_thread.join() + serve_order_thread.join() + sys.exit() \ No newline at end of file diff --git a/data_structures/8_Binary_Tree_1/Exercise/binary_tree_part_1_exercise.py b/data_structures/8_Binary_Tree_1/Exercise/binary_tree_part_1_solution.py similarity index 100% rename from data_structures/8_Binary_Tree_1/Exercise/binary_tree_part_1_exercise.py rename to data_structures/8_Binary_Tree_1/Exercise/binary_tree_part_1_solution.py diff --git a/data_structures/8_Binary_Tree_1/binary_tree_part_1_exercise.py b/data_structures/8_Binary_Tree_1/binary_tree_part_1_exercise.py new file mode 100644 index 0000000..f141eac --- /dev/null +++ b/data_structures/8_Binary_Tree_1/binary_tree_part_1_exercise.py @@ -0,0 +1,112 @@ +class BinarySearchTreeNode(): + def __init__(self, data): + self.data = data + self.left = None + self.right = None + + def add_child(self, data): + if data == self.data: + return + + if data < self.data: + if self.left: + self.left.add_child(data) + else: + self.left = BinarySearchTreeNode(data) + + else: + if self.right: + self.right.add_child(data) + else: + self.right = BinarySearchTreeNode(data) + + def in_order_traversal(self): + elements = [] + + if self.left: + elements += self.left.in_order_traversal() + + elements.append(self.data) + + if self.right: + elements += self.right.in_order_traversal() + + return elements + + def pre_order_traversal(self): + elements = [] + + elements.append(self.data) + + if self.left: + elements += self.left.pre_order_traversal() + + if self.right: + elements += self.right.pre_order_traversal() + + return elements + + def post_order_traversal(self): + elements = [] + + if self.left: + elements += self.left.post_order_traversal() + + if self.right: + elements += self.right.post_order_traversal() + + elements.append(self.data) + + return elements + + def search(self, data): + if self.data == data: + return True + + if data < self.data: + if self.left: + return self.left.search(data) + else: + return False + + else: + if self.right: + return self.right.search(data) + else: + return False + + def find_max(self): + if self.right is None: + return self.data + return self.right.find_max() + + def find_min(self): + if self.left is None: + return self.data + return self.left.find_min() + + def calculate_sum(self): + return sum(self.in_order_traversal()) + +def build_tree(elements): + root = BinarySearchTreeNode(elements[0]) + for i in range(1, len(elements)): + root.add_child(elements[i]) + + return root + + +if __name__ == '__main__': + + numbers = [17, 4, 1, 20, 9, 23, 18, 34] + + numbers = [15,12,7,14,27,20,23,88 ] + + numbers_tree = build_tree(numbers) + print("Input numbers:",numbers) + print("Min:",numbers_tree.find_min()) + print("Max:",numbers_tree.find_max()) + print("Sum:", numbers_tree.calculate_sum()) + print("In order traversal:", numbers_tree.in_order_traversal()) + print("Pre order traversal:", numbers_tree.pre_order_traversal()) + print("Post order traversal:", numbers_tree.post_order_traversal()) diff --git a/data_structures/8_Binary_Tree_1/binary_tree_terms.ipynb b/data_structures/8_Binary_Tree_1/binary_tree_terms.ipynb new file mode 100644 index 0000000..44b0c43 --- /dev/null +++ b/data_structures/8_Binary_Tree_1/binary_tree_terms.ipynb @@ -0,0 +1,63 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary Tree\n", + "\n", + "### Terminology\n", + "\n", + "- **Root**: The top node in a tree.\n", + "- **Parent**: A node that has one or more child nodes.\n", + "- **Child**: A node that has a parent node.\n", + "- **Leaf**: A node that has no children.\n", + "- **Height of a tree**: The number of edges on the longest path between the root and a leaf.\n", + " - Height of an empty tree is -1.\n", + "- **Depth of a node**: The number of edges on the path between the root and the node.\n", + " - Depth of the root is 0.\n", + "\n", + "- **Complete Binary Tree**: A binary tree in which every level, except possibly the last, is completely filled, and all nodes are as far left as possible.\n", + "- **Perfect Binary Tree**: A binary tree in which all interior nodes have two children and all leaves have the same depth.\n", + "- **Balanced Binary Tree**: A binary tree in which the depth of the two subtrees of every node never differ by more than 1.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "### Properties\n", + "\n", + "\n", + "- The maximum number of nodes at level $l$ of a binary tree is $2^l$.\n", + "- The maximum number of nodes in a binary tree of height $h$ is $2^{h+1}-1$.\n", + "- In a binary tree with $n$ nodes:\n", + " - minimum height (minimum depth) is $\\lceil \\log_2(n+1) \\rceil - 1$\n", + " - maximum height (maximum depth) is $n - 1$\n", + "- For a complete binary tree with $n$ nodes:\n", + " - height (minimum depth) is $\\lfloor \\log_2(n) \\rfloor = log_2(n+1) - 1$\n", + " - number of leaf nodes is $\\lceil n/2 \\rceil$\n", + "\n", + "- For a complete binary tree with $l$ leaf nodes:\n", + " - number of nodes is $2l - 1$\n", + " - height (minimum depth) is $log_2(l)$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/data_structures/9_Binary_Tree_2/Exercise/binary_tree_part_2_exercise.py b/data_structures/9_Binary_Tree_2/Exercise/binary_tree_part_2_solution.py similarity index 100% rename from data_structures/9_Binary_Tree_2/Exercise/binary_tree_part_2_exercise.py rename to data_structures/9_Binary_Tree_2/Exercise/binary_tree_part_2_solution.py diff --git a/data_structures/9_Binary_Tree_2/binary_tree_part_2_exercise.py b/data_structures/9_Binary_Tree_2/binary_tree_part_2_exercise.py new file mode 100644 index 0000000..03f5bca --- /dev/null +++ b/data_structures/9_Binary_Tree_2/binary_tree_part_2_exercise.py @@ -0,0 +1,176 @@ +from collections import deque + +class BinarySearchTreeNode(): + def __init__(self, data): + self.data = data + self.left = None + self.right = None + + def add_child(self, data): + if data == self.data: + return + + if data < self.data: + if self.left: + self.left.add_child(data) + else: + self.left = BinarySearchTreeNode(data) + + else: + if self.right: + self.right.add_child(data) + else: + self.right = BinarySearchTreeNode(data) + + def dfs_in_order_traversal(self): + elements = [] + + if self.left: + elements += self.left.dfs_in_order_traversal() + + elements.append(self.data) + + if self.right: + elements += self.right.dfs_in_order_traversal() + + return elements + + def dfs_pre_order_traversal(self): + elements = [] + + elements.append(self.data) + + if self.left: + elements += self.left.dfs_pre_order_traversal() + + if self.right: + elements += self.right.dfs_pre_order_traversal() + + return elements + + def dfs_post_order_traversal(self): + elements = [] + + if self.left: + elements += self.left.dfs_post_order_traversal() + + if self.right: + elements += self.right.dfs_post_order_traversal() + + elements.append(self.data) + + return elements + + def bfs_traversal(self): + elements = [] + queue = deque() + queue.append(self) + + while len(queue) > 0: + current_node = queue.popleft() + elements.append(current_node.data) + + if current_node.left: + queue.append(current_node.left) + + if current_node.right: + queue.append(current_node.right) + + return elements + + + def search(self, data): + if self.data == data: + return True + + if data < self.data: + if self.left: + return self.left.search(data) + else: + return False + + else: + if self.right: + return self.right.search(data) + else: + return False + + def find_max(self): + if self.right is None: + return self.data + return self.right.find_max() + + def find_min(self): + if self.left is None: + return self.data + return self.left.find_max() + + def calculate_sum(self): + return sum(self.dfs_in_order_traversal()) + + def delete(self, val): + if val < self.data: + if self.left: + self.left = self.left.delete(val) + elif val > self.data: + if self.right: + self.right = self.right.delete(val) + else: + if self.left is None and self.right is None: + return None + elif self.left is None: + return self.right + elif self.right is None: + return self.left + + min_val = self.right.find_min() + self.data = min_val + self.right = self.right.delete(min_val) + + return self + + def delete_alt(self, val): + if val < self.data: + if self.left: + self.left = self.left.delete(val) + elif val > self.data: + if self.right: + self.right = self.right.delete(val) + else: + if self.left is None and self.right is None: + return None + elif self.left is None: + return self.right + elif self.right is None: + return self.left + + max_val = self.left.find_max() + self.data = max_val + self.left = self.right.delete(max_val) + + return self + +def build_tree(elements): + root = BinarySearchTreeNode(elements[0]) + for i in range(1, len(elements)): + root.add_child(elements[i]) + + return root + + +if __name__ == '__main__': + + numbers = [17, 4, 1, 20, 9, 23, 18, 34] + + numbers = [15,12,7,14,27,20,23,88 ] + + numbers_tree = build_tree(numbers) + print("Input numbers:",numbers) + print("DFS - in order traversal:", numbers_tree.dfs_in_order_traversal()) + print("BFS - traversal:", numbers_tree.bfs_traversal()) + numbers_tree.delete_alt(20) + print("After deleting 20: ", numbers_tree.dfs_in_order_traversal()) + + + +