Repository: ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python Branch: master Commit: 7ecd98a4537a Files: 128 Total size: 157.9 KB Directory structure: gitextract_x7v3a_w5/ ├── .gitignore ├── LICENSE ├── README.md ├── analysis/ │ ├── anagrams-linear-solution.py │ ├── anagrams-loglinear-solution.py │ ├── anagrams-quadratic-solution.py │ ├── time-iterative-approach.py │ └── time-noniterative-approach.py ├── deque/ │ ├── circular-deque.py │ ├── deque.py │ └── deque_linked_list_impl.py ├── graphs/ │ ├── bellman-ford/ │ │ └── graph.py │ ├── bellman-ford-negative-weight-cycle/ │ │ └── graph.py │ ├── breadth-first-search/ │ │ ├── graph.py │ │ ├── main.py │ │ └── queue.py │ ├── cycle-detection/ │ │ ├── Cycle.md │ │ ├── cycle-directed-graph/ │ │ │ ├── graph.py │ │ │ └── main.py │ │ └── cycle-undirected-graph/ │ │ ├── graph.py │ │ └── main.py │ ├── depth-first-search/ │ │ ├── depth-first-search/ │ │ │ ├── graph.py │ │ │ ├── main.py │ │ │ └── stack.py │ │ └── depth-first-search-recursive/ │ │ ├── graph.py │ │ └── main.py │ ├── dijkstra/ │ │ ├── adjacency-list-impl/ │ │ │ ├── main.py │ │ │ └── vertex.py │ │ ├── matrix-impl/ │ │ │ ├── graph.py │ │ │ ├── main.py │ │ │ └── vertex.py │ │ └── priority-queue-impl-adjacency-map/ │ │ ├── graph.py │ │ ├── main.py │ │ ├── priorityqueue.py │ │ └── vertex.py │ ├── is-graph-bipartite/ │ │ ├── graph.py │ │ ├── main.py │ │ └── queue.py │ ├── kosarajus-algorithm/ │ │ ├── graph.py │ │ ├── main.py │ │ └── stack.py │ ├── minimum-spanning-tree/ │ │ ├── breadth-first-search/ │ │ │ ├── graph.py │ │ │ ├── main.py │ │ │ └── queue.py │ │ ├── kruskals-algorithm/ │ │ │ ├── graph.py │ │ │ └── main.py │ │ └── prims-algorithm/ │ │ ├── graph.py │ │ ├── main.py │ │ ├── priorityqueue.py │ │ └── vertex.py │ ├── topological-sorting/ │ │ ├── graph.py │ │ └── main.py │ └── union-find/ │ ├── number-of-connected-components/ │ │ └── graph.py │ └── union-find-path-compression/ │ ├── graph.py │ ├── main.py │ └── vertex.py ├── hash-table/ │ ├── chaining.py │ └── linear-probing.py ├── linked-lists/ │ ├── circular-doubly-linked-list/ │ │ ├── list.py │ │ └── node.py │ ├── circular-singly-linked-list/ │ │ ├── list.py │ │ └── node.py │ ├── doubly-linked-list/ │ │ ├── list.py │ │ └── node.py │ └── singly-linked-list/ │ ├── list.py │ └── node.py ├── queue/ │ ├── circular-queue-fixed-size-array-impl.py │ ├── queue-array-impl.py │ ├── queue-fixed-size-array-impl.py │ ├── queue-linked-list-impl.py │ └── queue-two-stacks-impl.py ├── recursion/ │ ├── convert-number-iterative.py │ ├── convert-number.py │ ├── factorial.py │ ├── fibonacci-iterative.py │ ├── fibonacci-memoization.py │ ├── fibonacci-recursive-worst-solution.py │ ├── fibonacci-recursive.py │ ├── fibonacci-sum-iterative.py │ ├── fibonacci-sum-recursive.py │ ├── maze.py │ ├── palindrome.py │ ├── reverse-linked-list-iterative-stack.py │ ├── reverse-linked-list-iterative.py │ ├── reverse-linked-list.py │ ├── reverse-list.py │ ├── reverse-string.py │ ├── stack.py │ ├── sum-numbers-binary-recursion.py │ ├── sum-numbers-pointer.py │ ├── sum-numbers-slicing.py │ └── towers-of-hanoi.py ├── searching/ │ ├── binary-search-iterative.py │ ├── binary-search-recursive-pointers.py │ ├── binary-search-recursive.py │ ├── sequential-search-ordered-list.py │ └── sequential-search-unordered-list.py ├── sorting/ │ ├── bubble-sort.py │ ├── insertion-sort.py │ ├── merge-sort.py │ ├── quicksort-return-new-array.py │ ├── quicksort.py │ ├── selection-sort.py │ └── short-bubble.py ├── stack/ │ ├── examples/ │ │ ├── balanced-brackets.py │ │ ├── number_converter.py │ │ └── stack.py │ ├── stack-array-impl-less-efficient.py │ ├── stack-array-impl.py │ ├── stack-fixed-size-array-impl.py │ ├── stack-linked-list-impl.py │ └── stack_two_queues.py ├── stack_two_queues.py ├── substring-search/ │ └── brute_force.py ├── trees/ │ ├── avl-tree.py │ ├── binary-heap.py │ ├── binary-search-tree.py │ ├── class-representation.py │ ├── list-representation.py │ ├── parse-tree.py │ ├── stack.py │ └── tree-traversal/ │ ├── functions.py │ ├── inorder-traversal-example.py │ ├── postorder-traversal-example.py │ ├── preorder-traversal-example.py │ ├── stack.py │ └── treenode.py └── trie/ └── trie.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /__pycache__/ /venv/ /.idea/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Ivan Markovic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Problem-Solving-with-Algorithms-and-Data-Structures-using-Python I started the project by learning data structures and algorithms from a book *Problem Solving with Algorithms and Data Structures using Python*. It does not contain everything from the book, nor is everything implemented in the same way, but it also contains other data structures, algorithms and problems. [Book website](https://runestone.academy/runestone/books/published/pythonds/index.html) [Videos](https://teklern.blogspot.com/p/blog-page.html) ### [Algorithm analysis](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/analysis) - [Anagrams - quadratic solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-quadratic-solution.py) - [Anagrams - log linear solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-loglinear-solution.py) - [Anagrams - linear solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/anagrams-linear-solution.py) - [Time - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/time-iterative-approach.py) - [Time - non-iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/analysis/time-noniterative-approach.py) ### [Stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/stack) - [Stack - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-array-impl.py) - [Stack - less efficient array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-array-impl-less-efficient.py) - [Stack - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-fixed-size-array-impl.py) - [Stack two queues](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack_two_queues.py) - [Stack - linked list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/stack-linked-list-impl.py) - [Examples](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/stack/examples) - [Balanced brackets](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/examples/balanced-brackets.py) - [Number converter](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/stack/examples/number_converter.py) ### [Queue](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/queue) - [Queue - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-array-impl.py) - [Queue - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-fixed-size-array-impl.py) - [Queue - linked list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-linked-list-impl.py) - [Circular queue - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/circular-queue-fixed-size-array-impl.py) - [Queue - two stacks implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/queue/queue-two-stacks-impl.py) ### [Deque](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/deque) - [Deque - array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/deque.py) - [Circular deque - fixed size array implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/circular-deque.py) - [Deque - linked list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/deque/deque_linked_list_impl.py) ### [Linked lists](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists) - [Singly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/singly-linked-list) - [Doubly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/doubly-linked-list) - [Circular singly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/circular-singly-linked-list) - [Circular doubly linked list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/linked-lists/circular-doubly-linked-list) ### [Recursion](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/recursion) - [Convert number - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/convert-number.py) - [Convert number - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/convert-number-iterative.py) - [Factorial](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/factorial.py) - [Fibonacci - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-iterative.py) - [Fibonacci - recursive worst solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-recursive-worst-solution.py) - [Fibonacci - memoization](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-memoization.py) - [Fibonacci - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-recursive.py) - [Fibonacci sum recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-sum-recursive.py) - [Fibonacci sum iterative](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/fibonacci-sum-iterative.py) - [Maze - path finder](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/maze.py) - [Palindrome](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/palindrome.py) - [Reverse linked list - recursive solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-linked-list.py) - [Reverse linked list - iterative solution](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-linked-list-iterative.py) - [Reverse linked list - iterative solution stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-linked-list-iterative-stack.py) - [Reverse list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-list.py) - [Reverse string](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/reverse-string.py) - [Sum numbers - binary recursion](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/sum-numbers-binary-recursion.py) - [Sum numbers - recursion with pointer](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/sum-numbers-pointer.py) - [Sum numbers - recursion with slicing](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/sum-numbers-slicing.py) - [Towers of Hanoi](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/recursion/towers-of-hanoi.py) ### [Searching](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/searching) - [Sequential search - unordered list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/sequential-search-unordered-list.py) - [Sequential search - ordered list](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/sequential-search-ordered-list.py) - [Binary search iterative](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-iterative.py) - [Binary search recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-recursive.py) - [Binary search recursive pointers](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/searching/binary-search-recursive-pointers.py) ### [Hash table](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/hash-table) - [Linear probing](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/hash-table/linear-probing.py) - [Chaining](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/hash-table/chaining.py) ### [Trees](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trees) - [List of lists representation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/list-representation.py) - [Class representation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/class-representation.py) - [Parse tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/parse-tree.py) - [Tree traversal](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trees/tree-traversal) - [Methods](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/treenode.py) - [Functions](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/functions.py) - [Preorder traversal example](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/preorder-traversal-example.py) - [Inorder traversal example](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/inorder-traversal-example.py) - [Postorder traversal example](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/tree-traversal/postorder-traversal-example.py) - [Binary Heap - min heap implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/binary-heap.py) - [Binary Search Tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/binary-search-tree.py) - [AVL tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/trees/avl-tree.py) ### [Sorting](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/sorting) - [Bubble sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/bubble-sort.py) - [Short bubble](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/short-bubble.py) - [Insertion sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/insertion-sort.py) - [Selection sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/selection-sort.py) - [Merge sort](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/merge-sort.py) - [Quicksort in place](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/quicksort.py) - [Quicksort - return new array](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/sorting/quicksort-return-new-array.py) ### [Graphs](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs) - [Breadth first search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/breadth-first-search) - [Depth first search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search) - [Depth first search - stack](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search/depth-first-search) - [Depth first search - recursive](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/depth-first-search/depth-first-search-recursive) - [Cycle detection](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection) - [Cycle detection directed graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection/cycle-directed-graph) - [Cycle detection undirected graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/cycle-detection/cycle-undirected-graph) - [Topological sorting](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/topological-sorting) - [Dijkstra algorithm - Shortest path](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra) - [Priority Queue implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/priority-queue-impl-adjacency-map) - [Matrix implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/matrix-impl) - [Adjacency list implementation](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/dijkstra/adjacency-list-impl) - [Bellman-Ford algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/bellman-ford) - [Bellman-Ford algorithm - negative weight cycle](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/bellman-ford-negative-weight-cycle) - [Minimum Spanning Tree](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree) - [Minimum Spanning Tree in undirected graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree/breadth-first-search) - [Prim's algorithm - Minimum Spanning Tree in undirected weighted graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree/prims-algorithm) - [Kruskal's algorithm - Minimum Spanning Tree in undirected weighted graph](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/minimum-spanning-tree/kruskals-algorithm) - [Is graph bipartite](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/is-graph-bipartite) - [Union find](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find) - [Number of connected components](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find/number-of-connected-components) - [Union find path compression](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/union-find/union-find-path-compression) - [Kosaraju's algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/graphs/kosarajus-algorithm) ### [Trie](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trie) - [Trie](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/trie) ### [Substring search](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/tree/master/substring-search) - [Brute force algorithm](https://github.com/ivanmmarkovic/Problem-Solving-with-Algorithms-and-Data-Structures-using-Python/blob/master/substring-search/brute_force.py) ================================================ FILE: analysis/anagrams-linear-solution.py ================================================ # O(n) def anagrams(string1: str, string2: str) -> bool: if len(string1) != len(string2): return False is_anagram: bool = True list1: list = [0] * 26 list2: list = [0] * 26 count_characters(string1, list1) count_characters(string2, list2) pos: int = 0 while pos < len(list1) and is_anagram: # 26 steps max if list1[pos] != list2[pos]: is_anagram = False else: pos += 1 return is_anagram def count_characters(string: str, arr: list): for character in string: index: int = ord(character) - ord('a') arr[index] += 1 print(anagrams("python", "typhon")) print(anagrams("abba", "baba")) print(anagrams("abba", "abbb")) ================================================ FILE: analysis/anagrams-loglinear-solution.py ================================================ # O(nlogn) def anagrams(string1: str, string2: str) -> bool: if len(string1) != len(string2): return False is_anagram: bool = True list1: list = list(string1) list2: list = list(string2) list1.sort() # sorting is O(nlogn) list2.sort() # sorting is O(nlogn) pos: int = 0 # loop is O(n) while pos < len(list1) and is_anagram: if list1[pos] != list2[pos]: is_anagram = False else: pos += 1 return is_anagram print(anagrams("python", "typhon")) print(anagrams("abba", "baba")) print(anagrams("abba", "abbb")) ================================================ FILE: analysis/anagrams-quadratic-solution.py ================================================ # O(n2) def anagrams(string1: str, string2: str) -> bool: if len(string1) != len(string2): return False is_anagram: bool = True list2: list = list(string2) pos1: int = 0 while pos1 < len(string1) and is_anagram: character = string1[pos1] character_found: bool = False pos2: int = 0 while pos2 < len(list2) and not character_found: if list2[pos2] == character: list2[pos2] = None character_found = True else: pos2 += 1 if not character_found: is_anagram = False else: pos1 += 1 return is_anagram print(anagrams("python", "typhon")) print(anagrams("abba", "baba")) print(anagrams("abba", "abbb")) ================================================ FILE: analysis/time-iterative-approach.py ================================================ import time def sum_nums(n: int) -> int: start = time.time() total: int = 0 for i in range(n + 1): total += i end = time.time() print("For ", n, " numbers time is", end - start) return total nums: list = [100, 10000, 100000, 1000000] for n in nums: print(sum_nums(n)) ================================================ FILE: analysis/time-noniterative-approach.py ================================================ import time def sum_nums(n: int) -> int: start = time.time() total: int = n * (n + 1) / 2 end = time.time() print("For ", n, " numbers time is", end - start) return total nums: list = [100, 10000, 100000, 1000000] for n in nums: print(sum_nums(n)) ================================================ FILE: deque/circular-deque.py ================================================ from typing import Any, List class Deque: def __init__(self, capacity: int = 10) -> None: self.capacity: int = capacity self.length: int = 0 self._deque: List[Any] = [None] * self.capacity self.front: int = -1 self.rear: int = -1 def is_empty(self) -> bool: return self.front == -1 def is_full(self) -> bool: return (self.rear + 1) % self.capacity == self.front def size(self) -> int: return self.length def add_front(self, item: Any): if self.is_full(): raise Exception('Deque is full') if self.is_empty(): # self.rear == -1 self.front = self.rear = 0 elif self.front == 0: self.front = self.capacity - 1 else: self.front -= 1 self._deque[self.front] = item self.length += 1 def add_rear(self, item: Any): if self.is_full(): raise Exception('Deque is full') if self.is_empty(): # self.rear == -1 self.front = self.rear = 0 else: self.rear = (self.rear + 1) % self.capacity self._deque[self.rear] = item self.length += 1 def remove_front(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') item: Any = self._deque[self.front] if self.front == self.rear: self.front = self.rear = -1 else: self.front = (self.front + 1) % self.capacity self.length -= 1 return item def remove_rear(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') item: Any = self._deque[self.rear] if self.rear == self.front: self.rear = self.front = -1 elif self.rear == 0: self.rear = self.capacity - 1 else: self.rear -= 1 self.length -= 1 return item def get_front(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') return self._deque[self.front] def get_rear(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') return self._deque[self.rear] def is_palindrome(string:str='') -> bool: d: Deque = Deque() for character in string: d.add_rear(character) palindrome: bool = True while d.size() > 1 and palindrome: if d.remove_front() != d.remove_rear(): palindrome = False return palindrome print(is_palindrome('radar')) print(is_palindrome('radr')) print(is_palindrome('r')) print(is_palindrome('')) ================================================ FILE: deque/deque.py ================================================ from typing import Any, List class Deque: def __init__(self) -> None: self._deque: List[Any] = [] def is_empty(self) -> bool: return len(self._deque) == 0 def size(self) -> int: return len(self._deque) def add_front(self, item: Any): self._deque.insert(0, item) def add_rear(self, item: Any): self._deque.append(item) def remove_front(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') return self._deque.pop(0) def remove_rear(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') return self._deque.pop() def is_palindrome(string:str='') -> bool: d: Deque = Deque() for character in string: d.add_rear(character) palindrome: bool = True while d.size() > 1 and palindrome: if d.remove_front() != d.remove_rear(): palindrome = False return palindrome print(is_palindrome('radar')) print(is_palindrome('radr')) print(is_palindrome('r')) print(is_palindrome('')) ================================================ FILE: deque/deque_linked_list_impl.py ================================================ from typing import List, Any class ListNode: def __init__(self, key:Any = None, prev:'ListNode' = None, next:'ListNode' = None): self.key:Any = key self.prev:'ListNode' = prev self.next:'ListNode' = next class Deque: def __init__(self): self._head:ListNode = None self._tail:ListNode = None self._length:int = 0 def size(self) -> int: return self._length def is_empty(self) -> bool: return self._length == 0 def add_front(self, e:Any) -> None: if self.is_empty(): self._head = self._tail = ListNode(e) else: self._head = ListNode(e, None, self._head) self._head.next.prev = self._head self._length += 1 def add_rear(self, e:Any) -> None: if self.is_empty(): self._head = self._tail = ListNode(e) else: self._tail.next = ListNode(e, self._tail, None) self._tail = self._tail.next self._length += 1 def remove_front(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') e:Any = self._head.key if self._head == self._tail: self._head = self._tail = None else: self._head = self._head.next self._head.prev = None self._length -= 1 return e def remove_rear(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') e:Any = self._tail.key if self._head == self._tail: self._head = self._tail = None else: self._tail = self._tail.prev self._tail.next = None self._length -= 1 return e def get_front(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') return self._head.key def remove_rear(self) -> Any: if self.is_empty(): raise Exception('Deque is empty') return self._tail.key ================================================ FILE: graphs/bellman-ford/graph.py ================================================ # Bellman-Ford algorithm # Find shortest paths from one vertex, # to all other vertices in weighted graph. # Runtime O(V*E) from typing import Set, Dict, List, Tuple class Graph: def __init__(self) -> None: self.vertices:Set[str] = set() self.edges:List[Tuple[str, str, int]] = list() self.prev:Dict[str, str] = dict() self.distances:Dict[str, int] = dict() def add_vertex(self, label:str) -> None: self.vertices.add(label) self.prev[label] = None self.distances[label] = None def add_edge(self, v1:str, v2:str, distance:int) -> None: self.edges.append((v1, v2, distance)) def bellman_ford(self, label:str) -> None: self.distances[label] = 0 for _ in range(len(self.vertices) - 1): for v1, v2, distance in self.edges: if self.distances[v1] is None: continue if self.distances[v2] is None or self.distances[v2] > self.distances[v1] + distance: self.distances[v2] = self.distances[v1] + distance self.prev[v2] = v1 # Check for negative-weight cycles for v1, v2, distance in self.edges: if self.distances[v1] is not None and self.distances[v2] > self.distances[v1] + distance: raise ValueError("Graph contains a negative-weight cycle") self._print_paths(label) def _print_paths(self, label:str) -> None: for v in self.vertices: if v == label: continue if self.distances[v] is not None: print(f'Path from {label} to {v} is {self._return_path(v)} and distance is {self.distances[v]}') else: print(f'No path from {label} to {v}') def _return_path(self, label:str) -> str: if self.prev[label] is None: return label return self._return_path(self.prev[label]) + ' -> ' + label g = Graph() for v in ['A', 'B', 'C', 'D']: g.add_vertex(v) g.add_edge('A', 'B', 1) g.add_edge('B', 'C', 3) g.add_edge('A', 'C', 10) g.add_edge('C', 'D', 2) g.add_edge('D', 'B', 4) g.bellman_ford('A') ================================================ FILE: graphs/bellman-ford-negative-weight-cycle/graph.py ================================================ # Bellman-Ford algorithm # Find shortest paths from one vertex, # to all other vertices in weighted graph. # Runtime O(V*E) # Negative weight cycle will be found with one more loop through edges # If there is a need to update distance than it is a negative weight cycle class Graph: def __init__(self): self.vertices: list = [] self.edges: list = [] self.distance: dict = {} self.prev: dict = {} def add_vertex(self, label: str): self.vertices.append(label) self.distance[label] = None self.prev[label] = None def add_edge(self, label1: str, label2: str, weight: int): self.edges.append([label1, label2, weight]) def bellman_ford(self, source: str): self.distance[source] = 0 for _ in range(len(self.vertices)): for edge in self.edges: label1: str = edge[0] label2: str = edge[1] weight: int = edge[2] if self.distance[label1] is None: continue if self.distance[label2] is None: self.distance[label2] = self.distance[label1] + weight self.prev[label2] = label1 continue if self.distance[label1] + weight < self.distance[label2]: self.distance[label2] = self.distance[label1] + weight self.prev[label2] = label1 continue for edge in self.edges: label1: str = edge[0] label2: str = edge[1] weight: int = edge[2] if self.distance[label1] is None: continue if self.distance[label2] is None: continue if self.distance[label1] + weight < self.distance[label2]: print(f'Negative weight cycle from {label1} to {label2}') def print_distances(self, source: str): for v in self.vertices: if v != source: distance: int = self.distance[v] print(f'Distance from {source} to {v} is {distance}') ================================================ FILE: graphs/breadth-first-search/graph.py ================================================ from queue import Queue class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.prev: dict = {} self.distance: dict = {} self.colors: dict = {} def add_vertex(self, label: str): self.vertices.append(label) self.adjacency_list[label]: list = [] self.prev[label] = None self.distance[label] = 0 self.colors[label] = "white" def add_edge(self, label1: str, label2: str): self.adjacency_list[label1].append(label2) self.adjacency_list[label2].append(label1) def bfs(self, label: str): q: Queue = Queue() q.enqueue(label) self.colors[label] = "gray" while not q.is_empty(): tmp: str = q.dequeue() for neighbour in self.adjacency_list[tmp]: if self.colors[neighbour] == "white": self.prev[neighbour] = tmp self.distance[neighbour] = self.distance[tmp] + 1 self.colors[neighbour] = "gray" q.enqueue(neighbour) self.colors[tmp] = "black" def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label ================================================ FILE: graphs/breadth-first-search/main.py ================================================ from graph import Graph graph = Graph() my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] # add vertices for i in range(len(my_vertices)): graph.add_vertex(my_vertices[i]) graph.add_edge('A', 'B') graph.add_edge('A', 'C') graph.add_edge('A', 'D') graph.add_edge('C', 'D') graph.add_edge('C', 'G') graph.add_edge('D', 'G') graph.add_edge('D', 'H') graph.add_edge('B', 'E') graph.add_edge('B', 'F') graph.add_edge('E', 'I') graph.bfs("A") print(graph.return_path("H")) ================================================ FILE: graphs/breadth-first-search/queue.py ================================================ class Queue: def __init__(self): self.queue = [] def enqueue(self, item): self.queue.insert(0, item) def dequeue(self): return self.queue.pop() def size(self): return len(self.queue) def is_empty(self): return self.queue == [] ================================================ FILE: graphs/cycle-detection/Cycle.md ================================================ #Cycle - vertex is reachable from itself. ##Undirected graph ''' edge(u, v) ''' - v is in the stack, visited but not explored - v is ancestor of u, but not parent ##Directed graph ''' edge(u, v) ''' - v is in the stack, visited but not explored - v is ancestor of u ================================================ FILE: graphs/cycle-detection/cycle-directed-graph/graph.py ================================================ class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.prev: dict = {} self.distance: dict = {} self.colors: dict = {} self.entry: dict = {} self.exit: dict = {} self.time: int = 0 def add_vertex(self, label: str): self.vertices.append(label) self.adjacency_list[label]: list = [] self.prev[label] = None self.distance[label] = 0 self.colors[label] = "white" def add_edge(self, label1: str, label2: str): self.adjacency_list[label1].append(label2) def dfs(self, label: str): self.colors[label] = "gray" self.time += 1 self.entry[label] = self.time for neighbour in self.adjacency_list[label]: if self.colors[neighbour] == "white": self.prev[neighbour] = label self.distance[neighbour] = self.distance[label] + 1 self.dfs(neighbour) if self.colors[neighbour] == "gray": print("Cycle", label, " - ", neighbour) self.colors[label] = "black" self.time += 1 self.exit[label] = self.time def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label ================================================ FILE: graphs/cycle-detection/cycle-directed-graph/main.py ================================================ from graph import Graph graph: Graph = Graph() vertices = ["a", "b", "c", "d"] for vertex in vertices: graph.add_vertex(vertex) graph.add_edge("a", "b") graph.add_edge("b", "c") graph.add_edge("c", "d") graph.add_edge("d", "b") graph.dfs("a") ================================================ FILE: graphs/cycle-detection/cycle-undirected-graph/graph.py ================================================ class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.prev: dict = {} self.distance: dict = {} self.colors: dict = {} self.entry: dict = {} self.exit: dict = {} self.time: int = 0 def add_vertex(self, label: str): self.vertices.append(label) self.adjacency_list[label]: list = [] self.prev[label] = None self.distance[label] = 0 self.colors[label] = "white" def add_edge(self, label1: str, label2: str): self.adjacency_list[label1].append(label2) self.adjacency_list[label2].append(label1) def dfs(self, label: str): self.colors[label] = "gray" self.time += 1 self.entry[label] = self.time for neighbour in self.adjacency_list[label]: if self.colors[neighbour] == "white": self.prev[neighbour] = label self.distance[neighbour] = self.distance[label] + 1 self.dfs(neighbour) if self.colors[neighbour] == "gray" and self.prev[label] != neighbour: print("Cycle", label, " - ", neighbour) self.colors[label] = "black" self.time += 1 self.exit[label] = self.time def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label ================================================ FILE: graphs/cycle-detection/cycle-undirected-graph/main.py ================================================ from graph import Graph graph: Graph = Graph() vertices = ["a", "b", "c", "d"] for vertex in vertices: graph.add_vertex(vertex) graph.add_edge("a", "b") graph.add_edge("b", "c") graph.add_edge("c", "d") # graph.add_edge("d", "b") graph.dfs("a") ================================================ FILE: graphs/depth-first-search/depth-first-search/graph.py ================================================ from stack import Stack class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.prev: dict = {} self.distance: dict = {} self.colors: dict = {} self.entry: dict = {} self.exit: dict = {} self.time: int = 0 def add_vertex(self, label: str): self.vertices.append(label) self.adjacency_list[label]: list = [] self.prev[label] = None self.distance[label] = 0 self.colors[label] = "white" def add_edge(self, label1: str, label2: str): self.adjacency_list[label1].append(label2) self.adjacency_list[label2].append(label1) def dfs(self, label: str): s: Stack = Stack() s.push(label) self.colors[label] = "gray" self.time += 1 self.entry[label] = self.time while not s.is_empty(): tmp: str = s.peek() neighbour: str = self.find_unvisited_neighbour(tmp) if neighbour is not None: self.prev[neighbour] = tmp self.distance[neighbour] = self.distance[tmp] + 1 self.colors[neighbour] = "gray" self.time += 1 self.entry[neighbour] = self.time s.push(neighbour) else: s.pop() self.time += 1 self.exit[tmp] = self.time self.colors[tmp] = "black" def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label def find_unvisited_neighbour(self, tmp) -> str: for n in self.adjacency_list[tmp]: if self.colors[n] == "white": return n return None ================================================ FILE: graphs/depth-first-search/depth-first-search/main.py ================================================ from graph import Graph graph = Graph() my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] # add vertices for i in range(len(my_vertices)): graph.add_vertex(my_vertices[i]) graph.add_edge('A', 'B') graph.add_edge('A', 'C') graph.add_edge('A', 'D') graph.add_edge('C', 'D') graph.add_edge('C', 'G') graph.add_edge('D', 'G') graph.add_edge('D', 'H') graph.add_edge('B', 'E') graph.add_edge('B', 'F') graph.add_edge('E', 'I') graph.dfs("A") print(graph.return_path("H")) ================================================ FILE: graphs/depth-first-search/depth-first-search/stack.py ================================================ class Stack: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(self.items) - 1] def size(self): return len(self.items) ================================================ FILE: graphs/depth-first-search/depth-first-search-recursive/graph.py ================================================ class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.prev: dict = {} self.distance: dict = {} self.colors: dict = {} self.entry: dict = {} self.exit: dict = {} self.time: int = 0 def add_vertex(self, label: str): self.vertices.append(label) self.adjacency_list[label]: list = [] self.prev[label] = None self.distance[label] = 0 self.colors[label] = "white" def add_edge(self, label1: str, label2: str): self.adjacency_list[label1].append(label2) self.adjacency_list[label2].append(label1) def dfs(self, label: str): self.colors[label] = "gray" self.time += 1 self.entry[label] = self.time for neighbour in self.adjacency_list[label]: if self.colors[neighbour] == "white": self.prev[neighbour] = label self.distance[neighbour] = self.distance[label] + 1 self.dfs(neighbour) self.colors[label] = "black" self.time += 1 self.exit[label] = self.time def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label ================================================ FILE: graphs/depth-first-search/depth-first-search-recursive/main.py ================================================ from graph import Graph graph = Graph() my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] # add vertices for i in range(len(my_vertices)): graph.add_vertex(my_vertices[i]) graph.add_edge('A', 'B') graph.add_edge('A', 'C') graph.add_edge('A', 'D') graph.add_edge('C', 'D') graph.add_edge('C', 'G') graph.add_edge('D', 'G') graph.add_edge('D', 'H') graph.add_edge('B', 'E') graph.add_edge('B', 'F') graph.add_edge('E', 'I') graph.dfs("A") print(graph.return_path("H")) ================================================ FILE: graphs/dijkstra/adjacency-list-impl/main.py ================================================ from typing import List, Dict, Set from vertex import Vertex class Graph: def __init__(self, capacity :int =10): self.capacity :int = capacity self.vertices :Dict[str, Vertex] = dict() self.prev :Dict[str, str] = dict() self.adjacency_list :Dict[str, List[Vertex]] = dict() self.visited :Set[str] = set() def add_vertex(self, label :str, weight :int = float('inf')) -> None: v :Vertex = Vertex(label, weight) self.vertices[v.label] = v self.adjacency_list[label] = list() self.prev[label] = None def add_edge(self, label1 :str, label2 :str, weight :int) -> None: v :Vertex = Vertex(label2, weight) self.adjacency_list[label1].append(v) def dijkstra(self, label :str) -> None: v :Vertex = self.vertices[label] v.weight = 0 while v is not None: for n in self.adjacency_list[v.label]: o :Vertex = self.vertices[n.label] if v.weight + n.weight < o.weight: o.weight = v.weight + n.weight self.prev[o.label] = v.label self.visited.add(v.label) v = self._find_cheapest_vertex() def show_path(self, label :str) -> str: if self.prev[label] is None: return label return self.show_path(self.prev[label]) + '->' + label def _find_cheapest_vertex(self) -> Vertex: vertex :Vertex = None for v in self.vertices.values(): if v.label not in self.visited: if vertex is None: vertex = v elif vertex.weight > v.weight: vertex = v return vertex graph: Graph = Graph() graph.add_vertex("START") graph.add_vertex("A") graph.add_vertex("C") graph.add_vertex("B") graph.add_vertex("D") graph.add_vertex("END") graph.add_edge("START", "A", 0) graph.add_edge("START", "C", 2) graph.add_edge("A", "B", 18) graph.add_edge("A", "D", 15) graph.add_edge("C", "B", 3) graph.add_edge("C", "D", 10) graph.add_edge("B", "END", 150) graph.add_edge("D", "END", 15) graph.dijkstra("START") print(graph.show_path("END")) ================================================ FILE: graphs/dijkstra/adjacency-list-impl/vertex.py ================================================ class Vertex: def __init__(self, label :str, weight :int = float('inf')): self.label :str = label self.weight :int = weight ================================================ FILE: graphs/dijkstra/matrix-impl/graph.py ================================================ from vertex import Vertex class Graph: def __init__(self, size: int = 10): self.size: int = size self.index: int = 0 self.vertices_list: list = [None] * self.size self.vertices: dict = {} self.adjacency_matrix: list = [[None for i in range(self.size)] for j in range(self.size)] self.prev: dict = {} self.visited: dict = {} def add_vertex(self, label: str): if self.index == self.size: # matrix is full return vertex: Vertex = Vertex(label, float("inf"), self.index) self.vertices_list[self.index] = vertex self.vertices[vertex.label] = vertex self.index += 1 self.prev[vertex.label] = None self.visited[vertex.label] = False def add_edge(self, label1: str, label2: str, weight: int): index1: int = self.vertices[label1].index index2: int = self.vertices[label2].index self.adjacency_matrix[index1][index2] = weight def dijkstra(self, label: str): current_vertex: Vertex = self.vertices[label] current_vertex.weight = 0 while current_vertex is not None: self.visited[current_vertex.label] = True for i in range(self.index): if self.adjacency_matrix[current_vertex.index][i] is not None: weight: int = self.adjacency_matrix[current_vertex.index][i] neighbour: Vertex = self.vertices_list[i] if current_vertex.weight + weight < neighbour.weight: neighbour.weight = current_vertex.weight + weight self.prev[neighbour.label] = current_vertex.label current_vertex = self.find_minimum_weight_vertex() def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label def find_minimum_weight_vertex(self): vertex: Vertex = None for label in self.vertices: if not self.visited[label]: if vertex is None: vertex = self.vertices[label] else: if vertex.weight > self.vertices[label].weight: vertex = self.vertices[label] return vertex ================================================ FILE: graphs/dijkstra/matrix-impl/main.py ================================================ from graph import Graph graph: Graph = Graph() graph.add_vertex("START") graph.add_vertex("A") graph.add_vertex("C") graph.add_vertex("B") graph.add_vertex("D") graph.add_vertex("END") graph.add_edge("START", "A", 0) graph.add_edge("START", "C", 2) graph.add_edge("A", "B", 18) graph.add_edge("A", "D", 15) graph.add_edge("C", "B", 3) graph.add_edge("C", "D", 10) graph.add_edge("B", "END", 150) graph.add_edge("D", "END", 15) graph.dijkstra("START") print(graph.return_path("END")) ================================================ FILE: graphs/dijkstra/matrix-impl/vertex.py ================================================ class Vertex: def __init__(self, label: str = None, weight: int = float("inf"), index: int = None): self.label: str = label self.weight: int = weight self.index: int = index ================================================ FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/graph.py ================================================ from vertex import Vertex from priorityqueue import PriorityQueue class Graph: def __init__(self): self._vertices: dict = {} self._adjacency_map: dict = {} self._prev: dict = {} def add_vertex(self, label: str): v: Vertex = Vertex(label) self._vertices[label] = v self._adjacency_map[label]: list = [] self._prev[label] = None def add_edge(self, label1: str, label2: str, weight: int): self._adjacency_map[label1].append(Vertex(label2, weight)) def dijkstra(self, label: str): self._vertices[label].weight = 0 pq: PriorityQueue = PriorityQueue() for label in self._vertices: pq.insert(self._vertices[label]) while not pq.is_empty(): current: Vertex = pq.delete_min() for neighbour in self._adjacency_map[current.label]: v: Vertex = self._vertices[neighbour.label] if current.weight + neighbour.weight < v.weight: v.weight = current.weight + neighbour.weight self._prev[v.label] = current.label pq.decrease_key(v.key) def show_path(self, label: str) -> str: if self._prev[label] is None: return label else: return self.show_path(self._prev[label]) + " -> " + label ================================================ FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/main.py ================================================ from graph import Graph graph: Graph = Graph() graph.add_vertex("START") graph.add_vertex("A") graph.add_vertex("C") graph.add_vertex("B") graph.add_vertex("D") graph.add_vertex("END") graph.add_edge("START", "A", 0) graph.add_edge("START", "C", 2) graph.add_edge("A", "B", 18) graph.add_edge("A", "D", 15) graph.add_edge("C", "B", 3) graph.add_edge("C", "D", 10) graph.add_edge("B", "END", 150) graph.add_edge("D", "END", 15) graph.dijkstra("START") print(graph.show_path("END")) ================================================ FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/priorityqueue.py ================================================ from vertex import Vertex class PriorityQueue: def __init__(self): self.pq: list = [None] self._pointer: int = 0 def is_empty(self) -> bool: return self._pointer == 0 def insert(self, vertex: Vertex): self.pq.append(vertex) self._pointer += 1 vertex.key = self._pointer self._perc_up(self._pointer) def _perc_up(self, pointer: int): while pointer // 2 > 0: if self.pq[pointer // 2].weight > self.pq[pointer].weight: self.pq[pointer // 2], self.pq[pointer] = self.pq[pointer], self.pq[pointer // 2] self.pq[pointer // 2].key = pointer // 2 self.pq[pointer].key = pointer pointer = pointer // 2 def decrease_key(self, pointer: int): self._perc_up(pointer) def delete_min(self) -> Vertex: if self.is_empty(): raise Exception("Priority queue is empty") v: Vertex = self.pq[1] self.pq[1] = self.pq[self._pointer] self.pq[1].key = 1 self.pq.pop() self._pointer -= 1 self._perc_down(1) return v def _perc_down(self, pointer: int): while pointer * 2 <= self._pointer: min_index: int = self._find_swap_index(pointer) if self.pq[pointer].weight > self.pq[min_index].weight: self.pq[pointer], self.pq[min_index] = self.pq[min_index], self.pq[pointer] self.pq[pointer].key = pointer self.pq[min_index].key = min_index pointer = min_index def _find_swap_index(self, pointer: int) -> int: if pointer * 2 + 1 > self._pointer: return pointer * 2 else: if self.pq[pointer * 2].weight <= self.pq[pointer * 2 + 1].weight: return pointer * 2 else: return pointer * 2 + 1 ================================================ FILE: graphs/dijkstra/priority-queue-impl-adjacency-map/vertex.py ================================================ class Vertex: def __init__(self, label: str = None, weight: int = float("inf"), key: int = None): self.label: str = label self.weight: int = weight self.key: int = key ================================================ FILE: graphs/is-graph-bipartite/graph.py ================================================ from queue import Queue class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.color: dict = {} def add_vertex(self, label: str = None): self.vertices.append(label) self.adjacency_list[label]: list = [] self.color[label] = None def add_edge(self, label1: str = None, label2: str = None): self.adjacency_list[label1].append(label2) self.adjacency_list[label2].append(label1) def bipartite_check(self) -> bool: for vertex in self.vertices: if self.color[vertex] is not None: continue q: Queue = Queue() self.color[vertex] = "red" q.enqueue(vertex) while not q.is_empty(): tmp: str = q.dequeue() for neighbour in self.adjacency_list[tmp]: if self.color[neighbour] == self.color[tmp]: return False if self.color[neighbour] is None: if self.color[tmp] == "red": self.color[neighbour] = "blue" else: self.color[neighbour] = "red" q.enqueue(neighbour) return True ================================================ FILE: graphs/is-graph-bipartite/main.py ================================================ from graph import Graph g: Graph = Graph() g.add_vertex("a") g.add_vertex("b") g.add_vertex("c") g.add_vertex("d") # a, b ||---|| c, d g.add_edge("a", "c") g.add_edge("a", "d") g.add_edge("b", "c") g.add_edge("b", "d") # g.add_edge("a", "b") print(g.bipartite_check()) ================================================ FILE: graphs/is-graph-bipartite/queue.py ================================================ class Queue: def __init__(self): self._queue: list = [] def is_empty(self) -> bool: return len(self._queue) == 0 def enqueue(self, vertex: str): self._queue.append(vertex) def dequeue(self): if self.is_empty(): raise Exception("Queue is empty") return self._queue.pop(0) ================================================ FILE: graphs/kosarajus-algorithm/graph.py ================================================ from typing import Dict, List, Set from stack import Stack class Graph: def __init__(self) -> None: self.vertices:Set[str] = set() self.adjacency_list:Dict[str, Set[str]] = dict() self.adjacency_list_reversed:Dict[str, Set[str]] = dict() self.visited:Set[str] = set() self.stack:Stack = Stack() def add_vertex(self, label:str) -> None: self.vertices.add(label) self.adjacency_list[label] = set() self.adjacency_list_reversed[label] = set() def add_edge(self, label1:str, label2:str) -> None: if label1 not in self.vertices or label2 not in self.vertices: raise Exception('Vertices are not added') self.adjacency_list[label1].add(label2) self.adjacency_list_reversed[label2].add(label1) def kosaraju(self) -> List[List[str]]: for v in self.vertices: if v not in self.visited: self._dfs(v) self.visited.clear() connected_components:List[List[str]] = list() while not self.stack.is_empty(): v:str = self.stack.pop() if v not in self.visited: connected:List[str] = list() self._dfs_reversed(v, connected) if len(connected) > 0: connected_components.append(connected) return list(connected_components) def _dfs(self, label:str) -> None: self.visited.add(label) for n in self.adjacency_list[label]: if n not in self.visited: self._dfs(n) self.stack.push(label) def _dfs_reversed(self, v:str, connected:List[str]) -> None: connected.append(v) self.visited.add(v) for n in self.adjacency_list_reversed[v]: if n not in self.visited: self._dfs_reversed(n, connected) ================================================ FILE: graphs/kosarajus-algorithm/main.py ================================================ from graph import Graph g: Graph = Graph() for i in range(9): g.add_vertex(str(i)) g.add_edge('0', '1') g.add_edge('1', '2') g.add_edge('2', '3') g.add_edge('3', '0') g.add_edge('2', '4') g.add_edge('4', '5') g.add_edge('5', '6') g.add_edge('6', '4') g.add_edge('7', '6') g.add_edge('8', '7') print(g.kosaraju()) ================================================ FILE: graphs/kosarajus-algorithm/stack.py ================================================ class Stack: def __init__(self): self._stack: list = [] def is_empty(self) -> bool: return len(self._stack) == 0 def push(self, item): self._stack.append(item) def pop(self): if self.is_empty(): raise Exception("Stack is empty") return self._stack.pop() def peek(self): if self.is_empty(): raise Exception("Stack is empty") # return self._stack[-1] # python way return self._stack[len(self._stack) - 1] def size(self) -> int: return len(self._stack) ================================================ FILE: graphs/minimum-spanning-tree/breadth-first-search/graph.py ================================================ from queue import Queue class Graph: def __init__(self): self.vertices: list = [] self.adjacency_list: dict = {} self.prev: dict = {} self.distance: dict = {} self.colors: dict = {} def add_vertex(self, label: str): self.vertices.append(label) self.adjacency_list[label]: list = [] self.prev[label] = None self.distance[label] = 0 self.colors[label] = "white" def add_edge(self, label1: str, label2: str): self.adjacency_list[label1].append(label2) self.adjacency_list[label2].append(label1) def minimum_spanning_tree(self, label: str) -> list: # this is breadth first search min_edges: list = [] q: Queue = Queue() q.enqueue(label) self.colors[label] = "gray" while not q.is_empty(): tmp: str = q.dequeue() for neighbour in self.adjacency_list[tmp]: if self.colors[neighbour] == "white": min_edges.append([tmp, neighbour]) self.prev[neighbour] = tmp self.distance[neighbour] = self.distance[tmp] + 1 self.colors[neighbour] = "gray" q.enqueue(neighbour) self.colors[tmp] = "black" return min_edges def return_path(self, label: str) -> str: if self.prev[label] is None: return label else: return self.return_path(self.prev[label]) + " -> " + label ================================================ FILE: graphs/minimum-spanning-tree/breadth-first-search/main.py ================================================ from graph import Graph graph = Graph() my_vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] # add vertices for i in range(len(my_vertices)): graph.add_vertex(my_vertices[i]) graph.add_edge('A', 'B') graph.add_edge('A', 'C') graph.add_edge('A', 'D') graph.add_edge('C', 'D') graph.add_edge('C', 'G') graph.add_edge('D', 'G') graph.add_edge('D', 'H') graph.add_edge('B', 'E') graph.add_edge('B', 'F') graph.add_edge('E', 'I') edges: list = graph.minimum_spanning_tree("A") print(edges) print(graph.return_path("H")) ================================================ FILE: graphs/minimum-spanning-tree/breadth-first-search/queue.py ================================================ class Queue: def __init__(self): self.queue = [] def enqueue(self, item): self.queue.insert(0, item) def dequeue(self): return self.queue.pop() def size(self): return len(self.queue) def is_empty(self): return self.queue == [] ================================================ FILE: graphs/minimum-spanning-tree/kruskals-algorithm/graph.py ================================================ from typing import List, Set, Dict, Tuple class Graph: def __init__(self) -> None: self.vertices:Set[str] = set() self.roots:Dict[str, str] = dict() self.sizes:Dict[str, int] = dict() self.edges:List[Tuple[str, str, int]] = list() self.mst:List[Tuple[str, str, int]] = list() def add_vertex(self, label:str) -> None: self.vertices.add(label) self.roots[label] = label self.sizes[label] = 1 def add_edge(self, label1:str, label2:str, weight:int) -> None: if label1 not in self.vertices or label2 not in self.vertices: raise Exception("Vertices must be added before connecting them") self.edges.append((label1, label2, weight)) def kruskal(self) -> List[Tuple[str, str, int]]: self.mst.clear() self.edges.sort(key = lambda edge: edge[2]) for v1, v2, weight in self.edges: root1:str = self._find_root(v1) root2:str = self._find_root(v2) if root1 != root2: if self.sizes[root1] >= self.sizes[root2]: self.roots[root2] = root1 self.sizes[root1] += self.sizes[root2] else: self.roots[root1] = root2 self.sizes[root2] += self.sizes[root1] self.mst.append((v1, v2, weight)) return list(self.mst) def _find_root(self, label:str) -> str: if self.roots[label] != label: self.roots[label] = self._find_root(self.roots[label]) return self.roots[label] ================================================ FILE: graphs/minimum-spanning-tree/kruskals-algorithm/main.py ================================================ from graph import Graph g: Graph = Graph() g.add_vertex('a') g.add_vertex('b') g.add_vertex('d') g.add_vertex('c') g.add_vertex('e') g.add_vertex('f') g.add_edge('a', 'f', 4) g.add_edge('a', 'b', 23) g.add_edge('b', 'd', 17) g.add_edge('b', 'c', 3) g.add_edge('c', 'd', 41) g.add_edge('c', 'f', 2) g.add_edge('f', 'e', 1) print(g.kruskal()) ================================================ FILE: graphs/minimum-spanning-tree/prims-algorithm/graph.py ================================================ from typing import Dict, List from vertex import Vertex from priorityqueue import PriorityQueue class Graph: def __init__(self) -> None: self.vertices: Dict[str, Vertex] = dict() self.adjacency_map: Dict[str, List[Vertex]] = dict() self.prev:Dict[str, str] = dict() def add_vertex(self, label:str, weight:int=float('inf')): self.vertices[label] = Vertex(label) self.adjacency_map[label] = [] self.prev[label] = None def add_edge(self, label1:str, label2:str, weight:int): self.adjacency_map[label1].append(Vertex(label2, weight)) self.adjacency_map[label2].append(Vertex(label1, weight)) def prims(self, label:str): res:str = '' v:Vertex = self.vertices[label] v.weight = 0 pq:PriorityQueue = PriorityQueue() for k in self.vertices: vertex = self.vertices[k] pq.insert(vertex) while not pq.is_empty(): v = pq.delete_min() print('Min is ', v.label) if self.prev[v.label] is not None: res += f'{self.prev[v.label]} -> {v.label}, ' for neighbour in self.adjacency_map[v.label]: vertex:Vertex = self.vertices[neighbour.label] if neighbour.weight < vertex.weight: vertex.weight = neighbour.weight self.prev[vertex.label] = v.label pq.decrease_key(vertex.index) print(res) ================================================ FILE: graphs/minimum-spanning-tree/prims-algorithm/main.py ================================================ from graph import Graph graph: Graph = Graph() graph.add_vertex("a") graph.add_vertex("b") graph.add_vertex("f") graph.add_vertex("c") graph.add_vertex("d") graph.add_vertex("e") graph.add_edge("a", "b", 4) graph.add_edge("a", "f", 2) graph.add_edge("b", "c", 6) graph.add_edge("f", "b", 3) graph.add_edge("f", "c", 1) graph.add_edge("f", "e", 4) graph.add_edge("c", "d", 3) graph.add_edge("d", "e", 2) graph.prims("a") # a -> f, f -> c, f -> b, c -> d, d -> e, ================================================ FILE: graphs/minimum-spanning-tree/prims-algorithm/priorityqueue.py ================================================ from typing import List from vertex import Vertex class PriorityQueue: def __init__(self) -> None: self.queue: List[Vertex] = [None] self.pointer:int = 0 def is_empty(self) -> bool: return self.pointer == 0 def insert(self, v:Vertex): self.queue.append(v) self.pointer += 1 v.index = self.pointer self.perc_up(self.pointer) def perc_up(self, index:int): while index // 2 > 0: if self.queue[index].weight < self.queue[index // 2].weight: self.queue[index], self.queue[index // 2] = self.queue[index // 2], self.queue[index] self.queue[index].index = index self.queue[index // 2].index = index // 2 index = index // 2 def decrease_key(self, key:int): self.perc_up(key) def get_min(self) -> Vertex: if self.is_empty(): raise Exception('Priority queue is empty') return self.queue[1] def delete_min(self) -> Vertex: if self.is_empty(): raise Exception('Priority queue is empty') v:Vertex = self.queue[1] self.queue[1] = self.queue[self.pointer] self.queue[1].index = 1 self.queue.pop() self.pointer -= 1 self.perc_down(1) return v def perc_down(self, index:int): while index * 2 <= self.pointer: min_index:int = self.find_min_index(index) if self.queue[index].weight > self.queue[min_index].weight: self.queue[index], self.queue[min_index] = self.queue[min_index], self.queue[index] self.queue[min_index].index = min_index self.queue[index].index = index index = min_index def find_min_index(self, index:int) -> int: if index * 2 + 1 > self.pointer: return index * 2 else: if self.queue[index * 2].weight <= self.queue[index * 2 + 1].weight: return index * 2 else: return index * 2 + 1 ================================================ FILE: graphs/minimum-spanning-tree/prims-algorithm/vertex.py ================================================ class Vertex: def __init__(self, label:str=None, weight:int=float('inf'), index:int=None) -> None: self.label:str = label self.weight:int = weight self.index:int = index ================================================ FILE: graphs/topological-sorting/graph.py ================================================ class Graph: def __init__(self): self.vertices: list = [] self.adjacencyList: dict = {} self.colors: dict = {} self.previous: dict = {} self.distance: dict = {} self.time: int = 0 self.entry: dict = {} self.exit: dict = {} self.no_incoming_edges: dict = {} # to avoid looking through whole list self.explored: list = [] def add_vertex(self, label: str): self.vertices.append(label) self.adjacencyList[label]: list = [] self.colors[label] = "white" self.distance[label] = 0 self.previous[label] = None self.no_incoming_edges[label] = label def add_edge(self, label1: str, label2: str): self.adjacencyList[label1].append(label2) if label2 in self.no_incoming_edges: del self.no_incoming_edges[label2] # remove vertex, it has incoming edge def topsort(self): # perform depth first search on vertices with no incoming edges for label in self.no_incoming_edges: self.dfs(label) self.explored.reverse() print(self.explored) def dfs(self, start: str): self.time += 1 self.entry[start] = self.time self.colors[start] = "gray" for neighbour in self.adjacencyList[start]: if self.colors[neighbour] == "white": self.previous[neighbour] = start self.distance[neighbour] = self.distance[neighbour] + 1 self.dfs(neighbour) self.time += 1 self.exit[start] = self.time self.colors[start] = "black" self.explored.append(start) def show_path(self, label: str) -> str: if self.previous[label] is None: return label else: return self.show_path(self.previous[label]) + " -> " + label ================================================ FILE: graphs/topological-sorting/main.py ================================================ from graph import Graph graph = Graph() vertices = ["a", "b", "c", "d", "e", "f"] for vertex in vertices: graph.add_vertex(vertex) graph.add_edge("a", "c") graph.add_edge("c", "d") graph.add_edge("c", "f") graph.add_edge("b", "c") graph.add_edge("b", "e") graph.topsort() ================================================ FILE: graphs/union-find/number-of-connected-components/graph.py ================================================ from typing import List def find_parent(i: int, components: List[int]) -> int: while i != components[i]: i = components[i] return i class Graph: def number_of_connected_components(self, edges_matrix: List[List[int]]) -> int: n: int = len(edges_matrix) number_of_components: int = n components: List[int] = [None] * n for i in range(n): components[i] = i for i in range(n): for j in range(n): if edges_matrix[i][j] == 1: parent_one: int = find_parent(i, components) parent_two: int = find_parent(j, components) if parent_one != parent_two: components[parent_one] = parent_two number_of_components -= 1 return number_of_components ================================================ FILE: graphs/union-find/union-find-path-compression/graph.py ================================================ from vertex import Vertex class Graph: def __init__(self): self.vertices: dict = {} self.edges: list = [] def add_vertex(self, label: str = None): self.vertices[label] = Vertex(label) def add_edge(self, label1: str, label2: str): self.edges.append([label1, label2]) def union_find(self): for edge in self.edges: label_one: str = edge[0] label_two: str = edge[1] vertex_one = self.vertices[label_one] vertex_two = self.vertices[label_two] root_one: Vertex = self.find_root(vertex_one) root_two: Vertex = self.find_root(vertex_two) if root_one != root_two: if root_one.rank > root_two.rank: root_two.parent = root_one root_one.rank = root_one.rank + 1 else: root_one.parent = root_two root_two.rank = root_two.rank + 1 def find_root(self, vertex: Vertex): if vertex.parent != vertex: vertex.parent = self.find_root(vertex.parent) return vertex.parent return vertex.parent ================================================ FILE: graphs/union-find/union-find-path-compression/main.py ================================================ from graph import Graph from vertex import Vertex g: Graph = Graph() g.add_vertex('a') g.add_vertex('b') g.add_vertex('c') g.add_vertex('d') g.add_vertex('e') g.add_vertex('f') g.add_edge('a', 'b') g.add_edge('c', 'd') g.add_edge('d', 'e') g.add_edge('c', 'f') g.add_edge('f', 'b') g.union_find() for label in g.vertices: vertex: Vertex = g.vertices[label] print(f'{vertex.label} parent is {vertex.parent.label}, rank is {vertex.parent.rank}') ================================================ FILE: graphs/union-find/union-find-path-compression/vertex.py ================================================ class Vertex: def __init__(self, label: str = None): self.label = label self.parent: 'Vertex' = self self.rank: int = 0 ================================================ FILE: hash-table/chaining.py ================================================ from typing import Any, List class KeyValue: def __init__(self, key: int, value: Any) -> None: self.key: int = key self.value: Any = value class HashTable: def __init__(self, capacity:int = 11) -> None: self.capacity: int = capacity self.length: int = 0 self.table: List[List[KeyValue]] = [None] * self.capacity def put(self, key: int, value: Any) -> int: index: int = self.hash(key) if self.table[index] is None: self.table[index] = [KeyValue(key, value)] self.length += 1 else: found: bool = False i: int = 0 items: List[KeyValue] = self.table[index] while i < len(items) and not found: if items[i].key == key: found = True else: i += 1 if found: items[i].value = value else: items.append(KeyValue(key, value)) self.length += 1 def get(self, key: int) -> Any: index: int = self.hash(key) if self.table[index] is None: return None else: found: bool = False i: int = 0 items: List[KeyValue] = self.table[index] while i < len(items) and not found: if items[i].key == key: found = True else: i += 1 if found: return items[i].value else: return None def contains(self, key: int) -> bool: index: int = self.hash(key) if self.table[index] is None: return False else: found: bool = False i: int = 0 items: List[KeyValue] = self.table[index] while i < len(items) and not found: if items[i].key == key: found = True else: i += 1 if found: return True else: return False def delete(self, key: int) -> None: index: int = self.hash(key) if self.table[index] is None: return None else: found: bool = False i: int = 0 items: List[KeyValue] = self.table[index] while i < len(items) and not found: if items[i].key == key: found = True else: i += 1 if not found: return None items.pop(i) if len(items) == 0: self.table[index] = None return None def hash(self, key: int) -> int: return key % self.capacity def size(self) -> int: return self.length ht: HashTable = HashTable() ht.put(11, 'string 11') ht.put(22, 'string 22') ht.put(33, 'string 33') ht.put(44, 'string 44') ht.put(21, 'string 21') ht.put(12, 'string 12') print(ht.size()) print('Get 11', ht.get(11)) print('Get 33', ht.get(33)) print('Get 147', ht.get(147)) print('----------------------------------------') print('Contains 22', ht.contains(22)) ht.delete(22) print(ht.size()) print('Contains 22', ht.contains(22)) print('----------------------------------------') print('Contains 77', ht.contains(77)) ht.put(44, 'string 144') ht.put(77, 'string 77') print(ht.size()) print('Contains 77', ht.contains(77)) ================================================ FILE: hash-table/linear-probing.py ================================================ from typing import Any, List, Tuple class HashTable: def __init__(self, capacity:int = 11) -> None: self.capacity:int = capacity self.keys: List[int] = [None] * self.capacity self.values: List[int] = [None] * self.capacity self.length: int = 0 def put(self, key:int, value:Any): index, contains = self.find(key) if contains: self.values[index] = value return hash:int = self.hash(key) if self.keys[hash] == float('inf') or self.keys[hash] is None: self.keys[hash] = key self.values[hash] = value self.length += 1 else: new_hash:int = self.rehash(hash) % self.capacity while self.keys[new_hash] is not None and self.keys[new_hash] != float('inf') and new_hash != hash: new_hash = self.rehash(new_hash) if self.keys[new_hash] == float('inf') or self.keys[new_hash] is None: self.keys[new_hash] = key self.values[new_hash] = value self.length += 1 def contains(self, key:int) -> bool: _, contains = self.find(key) return contains def get(self, key:int) -> Any: index, contains = self.find(key) if contains: return self.values[index] return None def delete(self, key:int): index, contains = self.find(key) if not contains: return self.keys[index] = float('inf') self.values[index] = None self.length -= 1 def find(self, key:int) -> Tuple[int, bool]: hash:int = self.hash(key) if self.keys[hash] == key: return (hash, True) elif self.keys[hash] is None: return (None, False) else: new_hash:int = self.rehash(hash) % self.capacity while self.keys[new_hash] != key and self.keys[new_hash] is not None and new_hash != hash: new_hash = self.rehash(new_hash) if self.keys[new_hash] == key: return (new_hash, True) return (None, False) def size(self) -> int: return self.length def hash(self, key:int) -> int: return key % self.capacity def rehash(self, old_hash:int) -> int: return (old_hash + 1) % self.capacity ht: HashTable = HashTable() ht.put(11, 'string 11') ht.put(22, 'string 22') ht.put(33, 'string 33') ht.put(44, 'string 44') ht.put(21, 'string 21') ht.put(12, 'string 12') print(ht.keys) print(ht.values) print(ht.size()) print('Get 11', ht.get(11)) print('Get 22', ht.get(22)) print('Get 147', ht.get(147)) print('----------------------------------------') print('Contains 22', ht.contains(22)) ht.delete(22) print(ht.size()) print(ht.keys) print(ht.values) print('Contains 22', ht.contains(22)) print('----------------------------------------') print('Contains 44', ht.contains(44)) print(ht.keys) print(ht.values) print('Contains 77', ht.contains(77)) ht.put(44, 'string 144') ht.put(77, 'string 77') print(ht.size()) print(ht.keys) print(ht.values) print('Contains 77', ht.contains(77)) print('Contains 44', ht.contains(44)) ================================================ FILE: linked-lists/circular-doubly-linked-list/list.py ================================================ from node import Node class List: def __init__(self): self.head: Node = None self.tail: Node = None def is_empty(self) -> bool: return self.head is None def print_all(self): if self.is_empty(): return tmp: Node = self.head stopped: bool = False while not stopped: print(tmp.key, end=", ") tmp = tmp.next if tmp == self.head: stopped = True print("") def number_of_elements(self) -> int: if self.is_empty(): return 0 count: int = 0 tmp: Node = self.head stopped: bool = False while not stopped: count += 1 tmp = tmp.next if tmp == self.head: stopped = True return count def add_to_head(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) self.head.prev = self.tail self.tail.next = self.head else: self.head = Node(key, self.tail, self.head) self.head.next.prev = self.head self.tail.next = self.head def add_to_tail(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) self.head.prev = self.tail self.tail.next = self.head else: self.tail.next = Node(key, self.tail, self.head) self.tail = self.tail.next self.head.prev = self.tail def delete_from_head(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.head if self.head == self.tail: self.head = self.tail = None else: self.head = self.head.next self.head.prev = self.tail self.tail.next = self.head return ret_node.key def delete_from_tail(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.tail if self.head == self.tail: self.head = self.tail = None else: self.tail = self.tail.prev self.tail.next = self.head self.head.prev = self.tail return ret_node.key def delete_nodes_with_value(self, key: int): if self.is_empty(): return None ret_value: int = None tmp: Node = self.head while tmp.next != self.head: if tmp.next.key == key: ret_value = tmp.next.key tmp.next = tmp.next.next tmp.next.prev = tmp else: tmp = tmp.next self.tail = tmp if self.head.key == key: ret_value = self.delete_from_head() return ret_value def delete_on_index(self, index: int): if self.is_empty(): return end_index: int = self.number_of_elements() - 1 if index < 0 or index > end_index: return if index == 0: self.delete_from_head() elif index == end_index: self.delete_from_tail() else: tmp: Node = self.head count: int = 0 while count < index: tmp = tmp.next count += 1 tmp.prev.next = tmp.next tmp.next.prev = tmp.prev def insert_after(self, list_element: int, new_element: int): if self.is_empty(): return tmp: Node = self.head stopped: bool = False while not stopped: if tmp.key == list_element: if tmp == self.tail: self.add_to_tail(new_element) else: new_node = Node(new_element, tmp, tmp.next) tmp.next = new_node new_node.next.prev = new_node tmp = tmp.next tmp = tmp.next if tmp == self.head: stopped = True def insert_before(self, list_element: int, new_element: int): if self.is_empty(): return tmp: Node = self.head stopped: bool = False while not stopped: if tmp.key == list_element: if tmp == self.head: self.add_to_head(new_element) else: new_node = Node(new_element, tmp.prev, tmp) new_node.prev.next = new_node tmp.prev = new_node tmp = tmp.next if tmp == self.head: stopped = True def sort(self): swapped: bool = True outer: Node = self.head inner: Node = self.tail while outer != self.tail: inner = self.tail while inner != outer: if inner.key < inner.prev.key: k = inner.key inner.key = inner.prev.key inner.prev.key = k inner = inner.prev outer = outer.next ================================================ FILE: linked-lists/circular-doubly-linked-list/node.py ================================================ class Node: def __init__(self, key: int=None, prev=None, next=None): self.key = key self.prev = prev self.next = next ================================================ FILE: linked-lists/circular-singly-linked-list/list.py ================================================ from node import Node class List: def __init__(self): self.head: Node = None self.tail: Node = None def is_empty(self) -> bool: return self.head is None def print_all(self): if self.is_empty(): return tmp: Node = self.head stopped: bool = False while not stopped: print(tmp.key, end=", ") tmp = tmp.next if tmp == self.head: stopped = True print("") def number_of_elements(self) -> int: if self.is_empty(): return 0 count: int = 0 tmp: Node = self.head stopped: bool = False while not stopped: count += 1 tmp = tmp.next if tmp == self.head: stopped = True return count def add_to_head(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) self.tail.next = self.head else: self.head = Node(key, self.head) self.tail.next = self.head def add_to_tail(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) self.tail.next = self.head else: self.tail.next = Node(key, self.head) self.tail = self.tail.next def delete_from_head(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.head if self.head == self.tail: self.head = self.tail = None else: self.head = self.head.next self.tail.next = self.head return ret_node.key def delete_from_tail(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.tail if self.head == self.tail: self.head = self.tail = None else: tmp: Node = self.head while tmp.next != self.tail: tmp = tmp.next self.tail = tmp tmp.next = self.head return ret_node.key def delete_nodes_with_value(self, key: int): if self.is_empty(): return None ret_value: int = None tmp: Node = self.head while tmp.next != self.head: if tmp.next.key == key: ret_value = tmp.next.key tmp.next = tmp.next.next else: tmp = tmp.next self.tail = tmp if self.head.key == key: ret_value = self.delete_from_head() return ret_value def delete_on_index(self, index: int): if self.is_empty(): return end_index: int = self.number_of_elements() - 1 if index < 0 or index > end_index: return if index == 0: self.delete_from_head() elif index == end_index: self.delete_from_tail() else: prev: Node = None tmp: Node = self.head count: int = 0 while count < index: prev = tmp tmp = tmp.next count += 1 prev.next = tmp.next def insert_after(self, list_element: int, new_element: int): if self.is_empty(): return tmp: Node = self.head stopped: bool = False while not stopped: if tmp.key == list_element: if tmp == self.tail: self.add_to_tail(new_element) else: tmp.next = Node(new_element, tmp.next) tmp = tmp.next tmp = tmp.next if tmp == self.head: stopped = True def insert_before(self, list_element: int, new_element: int): if self.is_empty(): return prev: Node = None tmp: Node = self.head stopped: bool = False while not stopped: if tmp.key == list_element: if tmp == self.head: self.add_to_head(new_element) else: prev.next = Node(new_element, tmp) prev = tmp tmp = tmp.next if tmp == self.head: stopped = True def sort(self): swapped: bool = True while swapped: swapped = False tmp: Node = self.head while tmp != self.tail: if tmp.key > tmp.next.key: k = tmp.key tmp.key = tmp.next.key tmp.next.key = k swapped = True tmp = tmp.next ================================================ FILE: linked-lists/circular-singly-linked-list/node.py ================================================ class Node: def __init__(self, key: int=None, next=None): self.key = key self.next = next ================================================ FILE: linked-lists/doubly-linked-list/list.py ================================================ from node import Node class List: def __init__(self): self.head: Node = None self.tail: Node = None def is_empty(self) -> bool: return self.head is None def print_all(self): tmp: Node = self.head while tmp is not None: print(tmp.key, end=", ") tmp = tmp.next print("") def number_of_elements(self) -> int: count: int = 0 tmp: Node = self.head while tmp is not None: count += 1 tmp = tmp.next return count def add_to_head(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) else: self.head = Node(key, None, self.head) self.head.next.prev = self.head def add_to_tail(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) else: self.tail.next = Node(key, self.tail) self.tail = self.tail.next def delete_from_head(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.head if self.head == self.tail: self.head = self.tail = None else: self.head = self.head.next self.head.prev = None return ret_node.key def delete_from_tail(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.tail if self.head == self.tail: self.head = self.tail = None else: self.tail = self.tail.prev; self.tail.next = None return ret_node.key def delete_nodes_with_value(self, key: int): if self.is_empty(): return None ret_value: int = None tmp: Node = self.head while tmp.next is not None: if tmp.next.key == key: ret_value = tmp.next.key tmp.next = tmp.next.next if tmp.next is not None: tmp.next.prev = tmp else: tmp = tmp.next self.tail = tmp if self.head.key == key: ret_value = self.delete_from_head() return ret_value def delete_on_index(self, index: int): if self.is_empty(): return end_index: int = self.number_of_elements() - 1 if index < 0 or index > end_index: return if index == 0: self.delete_from_head() elif index == end_index: self.delete_from_tail() else: tmp: Node = self.head count: int = 0 while count < index: tmp = tmp.next count += 1 tmp.prev.next = tmp.next tmp.next.prev = tmp.prev def insert_after(self, list_element: int, new_element: int): tmp: Node = self.head while tmp is not None: if tmp.key == list_element: if tmp == self.tail: self.add_to_tail(new_element) else: new_node: Node = Node(new_element, tmp, tmp.next) tmp.next = new_node new_node.next.prev = new_node tmp = tmp.next tmp = tmp.next def insert_before(self, list_element: int, new_element: int): tmp: Node = self.head while tmp is not None: if tmp.key == list_element: if tmp == self.head: self.add_to_head(new_element) else: new_node: Node = Node(new_element, tmp.prev, tmp) new_node.prev.next = new_node tmp.prev = new_node tmp = tmp.next def sort(self): swapped: bool = True outer: Node = self.head inner: Node = self.tail while outer != self.tail: inner = self.tail while inner != outer: if inner.key < inner.prev.key: k = inner.key inner.key = inner.prev.key inner.prev.key = k inner = inner.prev outer = outer.next ================================================ FILE: linked-lists/doubly-linked-list/node.py ================================================ class Node: def __init__(self, key: int=None, prev=None, next=None): self.key = key self.prev = prev self.next = next ================================================ FILE: linked-lists/singly-linked-list/list.py ================================================ from node import Node class List: def __init__(self): self.head: Node = None self.tail: Node = None def is_empty(self) -> bool: return self.head is None def print_all(self): tmp: Node = self.head while tmp is not None: print(tmp.key, end=", ") tmp = tmp.next print("") def number_of_elements(self) -> int: count: int = 0 tmp: Node = self.head while tmp is not None: count += 1 tmp = tmp.next return count def add_to_head(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) else: self.head = Node(key, self.head) def add_to_tail(self, key: int): if self.is_empty(): self.head = self.tail = Node(key) else: self.tail.next = Node(key) self.tail = self.tail.next def delete_from_head(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.head if self.head == self.tail: self.head = self.tail = None else: self.head = self.head.next return ret_node.key def delete_from_tail(self) -> int: if self.is_empty(): return None else: ret_node: Node = self.tail if self.head == self.tail: self.head = self.tail = None else: tmp: Node = self.head while tmp.next != self.tail: tmp = tmp.next tmp.next = None self.tail = tmp return ret_node.key def delete_nodes_with_value(self, key: int): if self.is_empty(): return None ret_value: int = None tmp: Node = self.head while tmp.next is not None: if tmp.next.key == key: ret_value = tmp.next.key tmp.next = tmp.next.next else: tmp = tmp.next self.tail = tmp if self.head.key == key: ret_value = self.delete_from_head() return ret_value def delete_on_index(self, index: int): if self.is_empty(): return end_index: int = self.number_of_elements() - 1 if index < 0 or index > end_index: return if index == 0: self.delete_from_head() elif index == end_index: self.delete_from_tail() else: prev: Node = None tmp: Node = self.head count: int = 0 while count < index: prev = tmp tmp = tmp.next count += 1 prev.next = tmp.next def insert_after(self, list_element: int, new_element: int): tmp: Node = self.head while tmp is not None: if tmp.key == list_element: if tmp == self.tail: self.add_to_tail(new_element) else: tmp.next = Node(new_element, tmp.next) tmp = tmp.next tmp = tmp.next def insert_before(self, list_element: int, new_element: int): prev: Node = None tmp: Node = self.head while tmp is not None: if tmp.key == list_element: if tmp == self.head: self.add_to_head(new_element) else: prev.next = Node(new_element, tmp) prev = tmp tmp = tmp.next def sort(self): swapped: bool = True while swapped: swapped = False tmp: Node = self.head while tmp != self.tail: if tmp.key > tmp.next.key: k = tmp.key tmp.key = tmp.next.key tmp.next.key = k swapped = True tmp = tmp.next ================================================ FILE: linked-lists/singly-linked-list/node.py ================================================ class Node: def __init__(self, key: int=None, next=None): self.key = key self.next = next ================================================ FILE: queue/circular-queue-fixed-size-array-impl.py ================================================ class Queue: def __init__(self, length: int = 10): self._length: int = length self._queue: list = [None] * self._length self._front = self._rear = -1 self._count: int = 0 def enqueue(self, item): if self.is_full(): raise Exception("Queue is full") self._rear = (self._rear + 1) % self._length if self._front == -1: self._front += 1 self._count += 1 self._queue[self._rear] = item def dequeue(self): if self.is_empty(): raise Exception("Queue is empty") ret_value = self._queue[self._front] if self._front == self._rear: self._front = self._rear = -1 else: self._front = (self._front + 1) % self._length self._count -= 1 return ret_value def peek(self): if self.is_empty(): raise Exception("Queue is empty") return self._queue[self._front] def size(self) -> int: return self._count def is_empty(self) -> bool: return self._front == -1 def is_full(self) -> bool: return (self._rear + 1) % self._length == self._front def hot_potato(namelist, number): q = Queue(30) for name in namelist: q.enqueue(name) while q.size() > 1: for i in range(number): q.enqueue(q.dequeue()) q.dequeue() return q.dequeue() print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7)) ================================================ FILE: queue/queue-array-impl.py ================================================ class Queue: def __init__(self): self._queue = [] def enqueue(self, item): self._queue.insert(0, item) def dequeue(self): if self.is_empty(): raise Exception('Queue is empty') return self._queue.pop() def peek(self): if self.is_empty(): raise Exception('Queue is empty') return self._queue[len(self._queue) - 1] def size(self) -> int: return len(self._queue) def is_empty(self) -> bool: return len(self._queue) == 0 def hot_potato(namelist, number): q = Queue() for name in namelist: q.enqueue(name) while q.size() > 1: for i in range(number): q.enqueue(q.dequeue()) q.dequeue() return q.dequeue() print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7)) ================================================ FILE: queue/queue-fixed-size-array-impl.py ================================================ class Queue: def __init__(self, length: int = 10): self._length: int = length self._queue: list = [None] * self._length self._front = self._rear = -1 self._count: int = 0 def enqueue(self, item): if self.is_full(): raise Exception("Queue is full") self._rear += 1 if self._front == -1: self._front += 1 self._count += 1 self._queue[self._rear] = item def dequeue(self): if self.is_empty(): raise Exception("Queue is empty") ret_value = self._queue[self._front] if self._front == self._rear: self._front = self._rear = -1 else: self._front += 1 self._count -= 1 return ret_value def peek(self): if self.is_empty(): raise Exception("Queue is empty") return self._queue[self._front] def size(self) -> int: return self._count def is_empty(self) -> bool: return self._front == -1 def is_full(self) -> bool: return self._rear == self._length - 1 def hot_potato(namelist, number): q = Queue(300) for name in namelist: q.enqueue(name) while q.size() > 1: for i in range(number): q.enqueue(q.dequeue()) q.dequeue() return q.dequeue() print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7)) ================================================ FILE: queue/queue-linked-list-impl.py ================================================ class Node: def __init__(self, key, next=None): self.key = key self.next = next class Queue: def __init__(self): self._head = self._tail = None self._count: int = 0 def enqueue(self, item): if self.is_empty(): self._head = self._tail = Node(item) else: self._tail.next = Node(item) self._tail = self._tail.next self._count += 1 def dequeue(self): if self.is_empty(): raise Exception("Queue is empty") ret_value: Node = self._head if self._head == self._tail: self._head = self._tail = None else: self._head = self._head.next self._count -= 1 return ret_value.key def peek(self): if self.is_empty(): raise Exception("Queue is empty") return self._head.key def size(self) -> int: return self._count def is_empty(self) -> bool: return self._head is None def hot_potato(namelist, number): q: Queue = Queue() for person in namelist: q.enqueue(person) while q.size() > 1: for i in range(number): q.enqueue(q.dequeue()) q.dequeue() return q.dequeue() print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7)) ================================================ FILE: queue/queue-two-stacks-impl.py ================================================ class Stack: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(self.items) - 1] def size(self): return len(self.items) class Queue: def __init__(self): self.input_stack: Stack = Stack() self.output_stack: Stack = Stack() def is_empty(self) -> bool: return self.input_stack.is_empty() def size(self) -> int: return self.input_stack.size() def enqueue(self, item): self.input_stack.push(item) def dequeue(self): if self.is_empty(): raise Exception("Queue is empty") while not self.input_stack.is_empty(): self.output_stack.push(self.input_stack.pop()) ret_value = self.output_stack.pop() while not self.output_stack.is_empty(): self.input_stack.push(self.output_stack.pop()) return ret_value def peek(self): if self.is_empty(): raise Exception("Queue is empty") while not self.input_stack.is_empty(): self.output_stack.push(self.input_stack.pop()) ret_value = self.input_stack.pop() self.output_stack.push(ret_value) while not self.output_stack.is_empty(): self.input_stack.push(self.output_stack.pop()) return ret_value def hot_potato(people: list, num: int) -> str: q: Queue = Queue() for person in people: q.enqueue(person) while q.size() > 1: for i in range(num): q.enqueue(q.dequeue()) q.dequeue() return q.dequeue() print(hot_potato(["Bill", "David", "Susan", "Jane", "Kent", "Brad"], 7)) ================================================ FILE: recursion/convert-number-iterative.py ================================================ from stack import Stack def converter(num: int, base: int) -> str: digits = "0123456789ABCDEF" stack: Stack = Stack() while num > 0: stack.push(digits[num % base]) num = num // base result: str = "" while not stack.is_empty(): result += stack.pop() return result ''' digits = "0123456789ABCDEF" def converter(num: int, base: int) -> str: r:str = '' while num > 0: r = digits[num % base] + r num = num // base return r ''' print(converter(1453, 16)) # 5AD ================================================ FILE: recursion/convert-number.py ================================================ def converter(num: int, base: int) -> str: digits = "0123456789ABCDEF" if num < base: return digits[num] else: return converter(num // base, base) + digits[num % base] print(converter(1453, 16)) # 5AD ================================================ FILE: recursion/factorial.py ================================================ def factorial_rec(n: int) -> int: if n <= 1: return 1 else: return n * factorial_rec(n - 1) def factorial_it(n: int) -> int: if n <= 1: return 1 result: int = 1 while n > 1: result *= n n -= 1 return result ================================================ FILE: recursion/fibonacci-iterative.py ================================================ def fibonacci(n: int) -> int: if n <= 1: return 0 elif n == 2: return 1 prev: int = 0 curr: int = 1 for _ in range(n - 2): prev, curr = curr, prev + curr return curr for i in range(6): print(fibonacci(i)) ================================================ FILE: recursion/fibonacci-memoization.py ================================================ nums: dict = {} def fibonacci(n: int) -> int: if n <= 1: return 0 elif n == 2: return 1 else: if n in nums: return nums[n] nums[n] = fibonacci(n - 1) + fibonacci(n - 2) return nums[n] for i in range(6): print(fibonacci(i)) ================================================ FILE: recursion/fibonacci-recursive-worst-solution.py ================================================ def fibonacci(n: int) -> int: if n <= 1: return 0 elif n == 2: return 1 else: return fibonacci(n - 1) + fibonacci(n - 2) for i in range(6): print(fibonacci(i)) ================================================ FILE: recursion/fibonacci-recursive.py ================================================ def fibonacci(n: int) -> int: def fibonacci_helper(num: int, prev: int = 0, curr: int = 1) -> int: if num <= 1: return prev elif num == 2: return curr else: return fibonacci_helper(num - 1, curr, prev + curr) return fibonacci_helper(n) for i in range(6): print(fibonacci(i)) ================================================ FILE: recursion/fibonacci-sum-iterative.py ================================================ def fibonaci(n: int): if n == 1: return 0 if n == 2: return 1 prev: int = 0 curr: int = 1 acc: int = 1 for _ in range(n - 2): prev,curr = curr, prev + curr acc += curr return acc for i in range(6): print(f'Fibonacci {i} is : {fibonacci(i)}') ================================================ FILE: recursion/fibonacci-sum-recursive.py ================================================ def fibonacci(n: int) -> int: def fibonacci_helper(num: int, acc: int = 0, prev: int = 0, curr: int = 1): if num <= 1: return prev elif num == 2: return acc + curr else: return fibonacci_helper(num - 1, acc + curr, curr, prev + curr) return fibonacci_helper(n) for i in range(6): print(fibonacci(i)) ================================================ FILE: recursion/maze.py ================================================ from typing import List, Any class Stack: def __init__(self) -> None: self.stack:List[Any] = list() def is_empty(self) -> bool: return len(self.stack) == 0 def size(self) -> int: return len(self.stack) def push(self, item:Any) -> None: self.stack.append(item) def peek(self) -> Any: if self.is_empty(): raise Exception('Stack is empty') return self.stack[len(self.stack) - 1] def pop(self) -> Any: if self.is_empty(): raise Exception('Stack is empty') return self.stack.pop() size: int = 5 matrix = [[0 for x in range(size)] for y in range(size)] ''' 0 - not visited 1 - visited 2 - obstacle ''' # place maze obstacles matrix[0][2] = 2 matrix[0][3] = 2 matrix[1][0] = 2 matrix[2][2] = 2 matrix[2][3] = 2 matrix[2][4] = 2 matrix[3][1] = 2 matrix[4][3] = 2 # starting position matrix[0][0] = 1 stack = Stack() stack.push([0, 0]) def path_finder(matrix:List[List[int]], stack:Stack) -> None: _move(matrix, stack) def _move(matrix:List[List[int]], stack:Stack) -> None: if stack.is_empty(): print('Path not found') else: x, y = stack.peek() if x == len(matrix) - 1 and y == len(matrix[x]) - 1: print('Path found') elif y < len(matrix[x]) - 1 and matrix[x][y + 1] == 0: _add_coordinates_to_stack(matrix, stack, x, y + 1) _move(matrix, stack) elif y > 0 and matrix[x][y - 1] == 0: _add_coordinates_to_stack(matrix, stack, x, y - 1) _move(matrix, stack) elif x > 0 and matrix[x - 1][y] == 0: _add_coordinates_to_stack(matrix, stack, x - 1, y) _move(matrix, stack) elif x < len(matrix) - 1 and matrix[x + 1][y] == 0: _add_coordinates_to_stack(matrix, stack, x + 1, y) _move(matrix, stack) else: stack.pop() _move(matrix, stack) def _add_coordinates_to_stack(matrix:List[List[int]], stack:Stack, x:int, y:int) -> None: matrix[x][y] = 1 stack.push([x, y]) if __name__ == "__main__": path_finder(matrix, stack) ================================================ FILE: recursion/palindrome.py ================================================ def palindrome_checker(string: str) -> bool: def palindrome_helper(s: str, start: int, end: int) -> bool: if start >= end: return True else: if s[start] == s[end]: return palindrome_helper(s, start + 1, end - 1) else: return False return palindrome_helper(string, 0, len(string) - 1) str1: str = "kayak" str2: str = "aibohphobia" print(palindrome_checker(str1)) print(palindrome_checker(str2)) def palindrome_checker_iterative(string:str) -> bool: is_palindrome: bool = True start = 0 end = len(string) - 1 while start < end and is_palindrome: if string[start] != string[end]: is_palindrome = False start += 1 end -= 1 return is_palindrome print(palindrome_checker_iterative(str1)) print(palindrome_checker_iterative(str2)) def palindrome_checker_slicing(string: str) -> bool: if len(string) <= 1: return True else: if string[0] == string[-1]: return palindrome_checker_slicing(string[1: -1]) else: return False print(palindrome_checker_slicing(str1)) print(palindrome_checker_slicing(str2)) ================================================ FILE: recursion/reverse-linked-list-iterative-stack.py ================================================ from stack import Stack class ListNode: def __init__(self, payload = None, next: 'ListNode' = None) -> None: self.payload = payload self.next: 'ListNode' = next def reverse_list(head: ListNode) -> ListNode: if head is None or head.next is None: return head stack: Stack = Stack() while head is not None: stack.push(head) head = head.next dummy: ListNode = ListNode() dummy_tmp = dummy while not stack.is_empty(): dummy_tmp.next = stack.pop() dummy_tmp = dummy_tmp.next dummy_tmp.next = None return dummy.next ================================================ FILE: recursion/reverse-linked-list-iterative.py ================================================ class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def reverse_list(head: ListNode) -> ListNode: if head is None or head.next is None: return head stopped: bool = False prev = head.next head.next = None while not stopped: tmp: ListNode = prev.next prev.next = head head = prev if tmp is not None: prev = tmp else: stopped = True return prev ================================================ FILE: recursion/reverse-linked-list.py ================================================ class ListNode: def __init__(self, val: int = None, next = None): self.val = val self.next = next def reverse_linked_list(node: ListNode) -> ListNode: if node is None: return None elif node.next is None: return node else: next_node: ListNode = node.next node.next = None rest: ListNode = reverse_linked_list(next_node) next_node.next = node return rest ================================================ FILE: recursion/reverse-list.py ================================================ nums: list = [1, 2, 3, 4, 5] def reverse_rec(elements: list): def reverse_list_helper(values: list, start: int, end: int): if start < end: values[start], values[end] = values[end], values[start] reverse_list_helper(values, start + 1, end - 1) reverse_list_helper(elements, 0, len(elements) - 1) print(nums) reverse_rec(nums) print(nums) def reverse_iterative(elements: list): start: int = 0 end: int = len(elements) - 1 while start < end: elements[start], elements[end] = elements[end], elements[start] start += 1 end -= 1 reverse_iterative(nums) print(nums) ================================================ FILE: recursion/reverse-string.py ================================================ string: str = "This string will be reversed" def reverse_string(string: str) -> str: def helper(s: str, end: int) -> str: if end < 0: return "" else: return s[end] + helper(s, end - 1) return helper(string, len(string) - 1) print(reverse_string(string)) def reverse_string_ex_one(string: str) -> str: if len(string) == 0: return "" else: return reverse_string_ex_one(string[1:]) + string[0] print(reverse_string_ex_one(string)) def reverse_string_ex_two(string: str) -> str: if len(string) == 0: return "" else: return string[len(string) - 1] + reverse_string_ex_two(string[0: len(string) - 1]) print(reverse_string_ex_two(string)) ================================================ FILE: recursion/stack.py ================================================ class Stack: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(self.items) - 1] def size(self): return len(self.items) ================================================ FILE: recursion/sum-numbers-binary-recursion.py ================================================ numbers: list = [1, 2, 3, 4, 5] # O(n) time def sum_numbers(nums: list) -> int: def helper(elements: list, start: int, end: int) -> int: if start > end: return 0 else: index: int = start + (end - start) // 2 return helper(elements, start, index - 1) + elements[index] + helper(elements, index + 1, end) return helper(nums, 0, len(nums) - 1) print(sum_numbers(numbers)) ================================================ FILE: recursion/sum-numbers-pointer.py ================================================ numbers: list = [1, 2, 3, 4, 5] # O(n) time def sum_numbers(nums: list) -> int: def helper(elements: list, end: int) -> int: if end < 0: return 0 else: return elements[end] + helper(elements, end - 1) return helper(nums, len(nums) - 1) print(sum_numbers(numbers)) ================================================ FILE: recursion/sum-numbers-slicing.py ================================================ numbers: list = [1, 2, 3, 4, 5] # slicing is O(k), better is solution with pointer def sum_numbers(nums: list) -> int: if len(nums) == 0: return 0 else: return nums[0] + sum_numbers(nums[1:]) print(sum_numbers(numbers)) ================================================ FILE: recursion/towers-of-hanoi.py ================================================ def print_move(start: str, end: str): print("Moving from", start, "to", end) def towers(number: int, start: str, spare: str, end: str): if number == 1: print_move(start, end) else: towers(number - 1, start, end, spare) towers(1, start, spare, end) towers(number - 1, spare, start, end) towers(3, "start", "spare", "end") ================================================ FILE: searching/binary-search-iterative.py ================================================ def binary_search(nums: list, target: int) -> bool: start: int = 0 end: int = len(nums) - 1 found: bool = False while start <= end and not found: midpoint: int = start + (end - start) // 2 if nums[midpoint] == target: found = True else: if nums[midpoint] > target: end = midpoint - 1 else: start = midpoint + 1 return found testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42] print(binary_search(testlist, 3)) print(binary_search(testlist, 13)) ================================================ FILE: searching/binary-search-recursive-pointers.py ================================================ def binary_search(nums: list, target: int) -> bool: def binary_search_helper(numbers: list, element, start: int, end: int) -> bool: if start > end: return False else: midpoint: int = start + (end - start) // 2 if nums[midpoint] == element: return True else: if nums[midpoint] > element: return binary_search_helper(numbers, element, start, midpoint - 1) else: return binary_search_helper(numbers, element, midpoint + 1, end) return binary_search_helper(nums, target, 0, len(nums) - 1) testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42] print(binary_search(testlist, 3)) print(binary_search(testlist, 13)) ================================================ FILE: searching/binary-search-recursive.py ================================================ # slicing a list is O(k) # better is recursive solution with pointers def binary_search(nums: list, target: int) -> bool: if len(nums) == 0: return False else: midpoint: int = len(nums) // 2 if nums[midpoint] == target: return True else: if nums[midpoint] > target: return binary_search(nums[0: midpoint], target) else: return binary_search(nums[midpoint + 1:], target) testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42] print(binary_search(testlist, 3)) print(binary_search(testlist, 13)) ================================================ FILE: searching/sequential-search-ordered-list.py ================================================ def sequential_search(nums: list, target: int) -> bool: found: bool = False stopped: bool = False i: int = 0 while i < len(nums) and not found and not stopped: if nums[i] == target: found = True else: if nums[i] > target: stopped = True else: i += 1 return found testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42] print(sequential_search(testlist, 3)) print(sequential_search(testlist, 13)) ================================================ FILE: searching/sequential-search-unordered-list.py ================================================ def sequential_search(nums: list, target: int) -> bool: i: int = 0 found: bool = False while i < len(nums) and not found: if nums[i] == target: found = True else: i += 1 return found testlist = [1, 2, 32, 8, 17, 19, 42, 13, 0] print(sequential_search(testlist, 3)) print(sequential_search(testlist, 13)) ================================================ FILE: sorting/bubble-sort.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def bubble_sort(nums: list): for i in range(len(nums)): for j in range(len(nums) - 1, i, -1): if nums[j - 1] > nums[j]: nums[j - 1], nums[j] = nums[j], nums[j - 1] print(array) bubble_sort(array) print(array) ================================================ FILE: sorting/insertion-sort.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def insertion_sort(nums: list): for i in range(1, len(nums), +1): curr: int = nums[i] pos: int = i while pos > 0 and nums[pos - 1] > curr: nums[pos] = nums[pos - 1] pos -= 1 nums[pos] = curr print(array) insertion_sort(array) print(array) ================================================ FILE: sorting/merge-sort.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def merge_sort(nums: list): if len(nums) < 2: return midpoint: int = len(nums) // 2 left: list = nums[0:midpoint] right: list = nums[midpoint:] merge_sort(left) merge_sort(right) i = j = k = 0 while i < len(left) and j < len(right): if left[i] < right[j]: nums[k] = left[i] i += 1 else: nums[k] = right[j] j += 1 k += 1 while i < len(left): nums[k] = left[i] i += 1 k += 1 while j < len(right): nums[k] = right[j] j += 1 k += 1 print(array) merge_sort(array) print(array) ================================================ FILE: sorting/quicksort-return-new-array.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def quick_sort(nums: list) -> list: if len(nums) < 2: return nums pivot_index: int = len(nums) // 2 pivot_value: int = nums[pivot_index] left: list = [] right: list = [] for i in range(len(nums)): if i != pivot_index: if nums[i] < pivot_value: left.append(nums[i]) else: right.append(nums[i]) return quick_sort(left) + [pivot_value] + quick_sort(right) print(array) array_sorted: list = quick_sort(array) print(array_sorted) ================================================ FILE: sorting/quicksort.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def quick_sort(nums: list, i: int, j: int): if i < j: left: int = i right: int = j pointer: int = left pivot_index: int = left + (right - left) // 2 pivot_value: int = nums[pivot_index] while pointer <= right: if nums[pointer] < pivot_value: nums[left], nums[pointer] = nums[pointer], nums[left] left += 1 pointer += 1 elif nums[pointer] > pivot_value: nums[pointer], nums[right] = nums[right], nums[pointer] right -= 1 else: pointer += 1 quick_sort(nums, i, left) if pointer > left: quick_sort(nums, pointer, j) else: quick_sort(nums, pointer + 1, j) print(array) quick_sort(array, 0, len(array) - 1) print(array) ================================================ FILE: sorting/selection-sort.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def selection_sort(nums: list): for i in range(0, len(nums), +1): min_index: int = i for j in range(i + 1, len(nums) - 1, +1): if nums[j] < nums[min_index]: min_index = j if min_index != i: nums[min_index], nums[i] = nums[i], nums[min_index] print(array) selection_sort(array) print(array) ================================================ FILE: sorting/short-bubble.py ================================================ array = [54, 26, 93, 17, 77, 31, 44, 55, 20] def short_bubble(nums: list): swapped: bool = True dec: int = 1 while swapped: swapped = False for i in range(len(nums) - dec): if nums[i] > nums[i + 1]: nums[i + 1], nums[i] = nums[i], nums[i + 1] swapped = True dec += 1 print(array) short_bubble(array) print(array) ================================================ FILE: stack/examples/balanced-brackets.py ================================================ from stack import Stack def balanced_brackets(string: str) -> bool: stack: Stack = Stack() for character in string: if character in "([{": stack.push(character) if character in ")]}": if stack.is_empty(): return False if "([{".index(stack.peek()) == ")]}".index(character): stack.pop() else: return False return stack.is_empty() print(balanced_brackets('((()))')) # True print(balanced_brackets('(()')) # False print(balanced_brackets(']()')) # False print(balanced_brackets('(])')) # False ================================================ FILE: stack/examples/number_converter.py ================================================ from stack import Stack def base_converter(num, base) -> str: digits = "0123456789ABCDEF" stack: Stack = Stack() while num > 0: stack.push(digits[num % base]) num = num // base result: str = "" while not stack.is_empty(): result += stack.pop() return result print(base_converter(25, 8)) # 31 print(base_converter(256, 16)) # 100 print(base_converter(26, 26)) # 10 ================================================ FILE: stack/examples/stack.py ================================================ class Stack: def __init__(self): self._stack: list = [] def is_empty(self) -> bool: return len(self._stack) == 0 def push(self, item): self._stack.append(item) def pop(self): if self.is_empty(): raise Exception("Stack is empty") return self._stack.pop() def peek(self): if self.is_empty(): raise Exception("Stack is empty") # return self._stack[-1] # python way return self._stack[len(self._stack) - 1] def size(self) -> int: return len(self._stack) ================================================ FILE: stack/stack-array-impl-less-efficient.py ================================================ # this solution is less efficient because # pop() from end of array is faster than pop(some_other_index) # append(item) is faster than insert(some_other_index, item) class Stack: def __init__(self): self._stack: list = [] def is_empty(self) -> bool: return len(self._stack) == 0 def push(self, item): self._stack.insert(0, item) def pop(self): if self.is_empty(): raise Exception("Stack is empty") return self._stack.pop(0) def peek(self): if self.is_empty(): raise Exception("Stack is empty") return self._stack[0] def size(self) -> int: return len(self._stack) def reverse_string(s: str) -> str: stack: Stack = Stack() for character in s: stack.push(character) result: str = "" while not stack.is_empty(): result += stack.pop() return result string: str = "This string will be reversed ..." print(reverse_string(string)) # ... desrever eb lliw gnirts sihT ================================================ FILE: stack/stack-array-impl.py ================================================ class Stack: def __init__(self): self._stack: list = [] def is_empty(self) -> bool: return len(self._stack) == 0 def push(self, item): self._stack.append(item) def pop(self): if self.is_empty(): raise Exception("Stack is empty") return self._stack.pop() def peek(self): if self.is_empty(): raise Exception("Stack is empty") # return self._stack[-1] # python way return self._stack[len(self._stack) - 1] def size(self) -> int: return len(self._stack) def reverse_string(s: str) -> str: stack: Stack = Stack() for character in s: stack.push(character) result: str = "" while not stack.is_empty(): result += stack.pop() return result string: str = "This string will be reversed ..." print(reverse_string(string)) # ... desrever eb lliw gnirts sihT ================================================ FILE: stack/stack-fixed-size-array-impl.py ================================================ from typing import Any, List class Stack: def __init__(self, capacity: int = 1) -> None: self.capacity: int = capacity self.stack: List[Any] = [None] * self.capacity self.pointer: int = -1 def size(self) -> int: return self.pointer + 1 def is_empty(self) -> bool: return self.pointer == -1 def is_full(self) -> bool: return self.pointer == self.capacity - 1 def push(self, item:Any): if self.is_full(): raise Exception('Stack is full') self.pointer += 1 self.stack[self.pointer] = item def pop(self) -> Any: if self.is_empty(): raise Exception('Stack is empty') item = self.stack[self.pointer] self.pointer -= 1 return item def peek(self) -> Any: if self.is_empty(): raise Exception('Stack is empty') return self.stack[self.pointer] def reverse_string(s: str) -> str: stack: Stack = Stack(len(s)) for character in s: stack.push(character) result: str = "" while not stack.is_empty(): result += stack.pop() return result string: str = "This string will be reversed ..." print(reverse_string(string)) # ... desrever eb lliw gnirts sihT ================================================ FILE: stack/stack-linked-list-impl.py ================================================ class Node: def __init__(self, key=None, next=None): self.key = key self.next = next class Stack: def __init__(self): self._head: Node = None self._count: int = 0 def is_empty(self) -> bool: return self._head is None def push(self, item): self._head = Node(item, self._head) self._count += 1 def pop(self): if self.is_empty(): raise Exception("Stack is empty") ret_value = self._head.key self._head = self._head.next self._count -= 1 return ret_value def peek(self): if self.is_empty(): raise Exception("Stack is empty") return self._head.key def size(self) -> int: return len(self._count) def reverse_string(s: str) -> str: stack: Stack = Stack() for character in s: stack.push(character) result: str = "" while not stack.is_empty(): result += stack.pop() return result string: str = "This string will be reversed ..." print(reverse_string(string)) # ... desrever eb lliw gnirts sihT ================================================ FILE: stack/stack_two_queues.py ================================================ class Queue: def __init__(self): self._queue = [] def enqueue(self, item): self._queue.insert(0, item) def dequeue(self): if self.is_empty(): raise Exception("Queue is empty") return self._queue.pop() def size(self) -> int: return len(self._queue) def is_empty(self) -> bool: return len(self._queue) == 0 def peek(self) -> int: if self.is_empty(): raise Exception("Queue is empty") return self._queue[len(self._queue) - 1] class MyStack: def __init__(self): self.input = Queue() self.output = Queue() def empty(self) -> bool: return self.input.is_empty() def push(self, item): self.input.enqueue(item) def pop(self): if self.empty(): raise Exception("Stack is empty") while self.input.size() > 1 : self.output.enqueue(self.input.dequeue()) val = self.input.dequeue() while not self.output.is_empty(): self.input.enqueue(self.output.dequeue()) return val def top(self): if self.empty(): raise Exception("Stack is empty") while self.input.size() > 1 : self.output.enqueue(self.input.dequeue()) val = self.input.peek() self.output.enqueue(self.input.dequeue()) while not self.output.is_empty(): self.input.enqueue(self.output.dequeue()) return val def size(self) -> int: return self.input.size() ================================================ FILE: stack_two_queues.py ================================================ from typing import Any, List class Queue: def __init__(self) -> None: self.queue:List[Any] = [] def size(self) -> int: return len(self.queue) def is_empty(self) -> bool: return len(self.queue) == 0 def enqueue(self, item:Any) -> None: self.queue.insert(0, item) def dequeue(self) -> Any: if self.is_empty(): raise Exception('Queue is empty') return self.queue.pop() class Stack: def __init__(self): self.input: Queue = Queue() self.output:Queue = Queue() def push(self, x: Any) -> None: self.input.enqueue(x) def pop(self) -> Any: if self.input.is_empty(): raise Exception('Queue is empty') while self.input.size() > 1: self.output.enqueue(self.input.dequeue()) item:Any = self.input.dequeue() while not self.output.is_empty(): self.input.enqueue(self.output.dequeue()) return item def peek(self) -> Any: if self.input.is_empty(): raise Exception('Queue is empty') while self.input.size() > 1: self.output.enqueue(self.input.dequeue()) item:Any = self.input.dequeue() self.output.enqueue(item) while not self.output.is_empty(): self.input.enqueue(self.output.dequeue()) return item def is_empty(self) -> bool: return self.input.is_empty() ================================================ FILE: substring-search/brute_force.py ================================================ # O(m * n) # m - length of the text # n - length of pattern def search(text: str, pattern: str) -> int: t:int = 0 last_index: int = len(text) - len(pattern) + 1 while t <= len(text) - len(pattern): p:int = 0 while p < len(pattern): if text[t + p] != pattern[p]: break else: p += 1 if p == len(pattern): return t t += 1 return -1 ================================================ FILE: trees/avl-tree.py ================================================ class TreeNode: def __init__(self, key=None, value=None, parent=None, left=None, right=None, left_subtree_height: int = 0, right_subtree_height: int = 0, balance_factor: int = 0): self.key = key self.value = value self.parent = parent self.left = left self.right = right self.left_subtree_height: int = left_subtree_height self.right_subtree_height: int = right_subtree_height self.balance_factor : int = balance_factor def has_left_child(self) -> bool: return self.left is not None def has_right_child(self) -> bool: return self.right is not None def has_both_children(self) -> bool: return self.has_left_child() and self.has_right_child() def is_leaf(self) -> bool: return not self.has_left_child() and not self.has_right_child() def is_root(self) -> bool: return self.parent is None def has_parent(self) -> bool: return self.parent is not None def is_left_child(self) -> bool: return self.parent.left == self def is_right_child(self) -> bool: return self.parent.right == self def find_min(self): if self is None: return None if self.has_left_child(): return self.left.find_min() else: return self def find_max(self): if self is None: return None node = self while node.right is not None: node = node.right return node class AVLTree: def __init__(self): self.root: TreeNode = None self.elements: int = 0 def size(self) -> int: return self.elements def is_empty(self) -> bool: return self.root is None def put(self, key, value): if self.is_empty(): self.root = TreeNode(key, value) self.elements += 1 else: self._put(self.root, key, value) def _put(self, root: TreeNode, key, value): if root.key == key: root.value = value elif key < root.key: if root.has_left_child(): self._put(root.left, key, value) else: root.left = TreeNode(key, value, root) self.elements += 1 self._update_balance_factor(root) else: if root.has_right_child(): self._put(root.right, key, value) else: root.right = TreeNode(key, value, root) self.elements += 1 self._update_balance_factor(root) def get(self, key) -> TreeNode: if self.is_empty(): return None else: return self._get(self.root, key) def _get(self, root: TreeNode, key) -> TreeNode: if root.key == key: return root elif key < root.key: if root.has_left_child(): return self._get(root.left, key) else: return None else: if root.has_right_child(): return self._get(root.right, key) else: return None def contains(self, key) -> bool: if self.is_empty(): return None found: bool = False node: TreeNode = self.root while node is not None and not found: if node.key == key: found = True elif key < node.key: node = node.left else: node = node.right return found def delete(self, key): node_to_delete: TreeNode = self.get(key) if node_to_delete is None: return if node_to_delete.is_root(): if node_to_delete.is_leaf(): self.root = None self.elements -= 1 elif node_to_delete.has_both_children(): max_node: TreeNode = node_to_delete.left.find_max() tmp_key = max_node.key tmp_value = max_node.value self.delete(tmp_key) # keep pointer to that node, not root, root might change node_to_delete.key = tmp_key node_to_delete.value = tmp_value else: if node_to_delete.has_left_child(): self.root = node_to_delete.left else: self.root = node_to_delete.right self.root.parent = None self.elements -= 1 else: parent: TreeNode = None if node_to_delete.is_leaf(): parent = node_to_delete.parent if node_to_delete.is_left_child(): node_to_delete.parent.left = None else: node_to_delete.parent.right = None self.elements -= 1 elif node_to_delete.has_both_children(): max_node: TreeNode = node_to_delete.left.find_max() tmp_key = max_node.key tmp_value = max_node.value self.delete(tmp_key) node_to_delete.key = tmp_key node_to_delete.value = tmp_value elif node_to_delete.has_left_child(): parent = node_to_delete.parent self.elements -= 1 if node_to_delete.is_left_child(): node_to_delete.parent.left = node_to_delete.left else: node_to_delete.parent.right = node_to_delete.left node_to_delete.left.parent = node_to_delete.parent else: parent = node_to_delete.parent self.elements -= 1 if node_to_delete.is_left_child(): node_to_delete.parent.left = node_to_delete.right else: node_to_delete.parent.right = node_to_delete.right node_to_delete.right.parent = node_to_delete.parent if parent is not None: self._update_balance_factor(parent) def find_min(self) -> TreeNode: if self.is_empty(): return None node: TreeNode = self.root while node.left is not None: node = node.left return node def find_max(self) -> TreeNode: if self.is_empty(): return None node: TreeNode = self.root while node.right is not None: node = node.right return node def _update_balance_factor(self, root: TreeNode): old_balance_factor: int = root.balance_factor if root.has_left_child(): root.left_subtree_height = max(root.left.left_subtree_height, root.left.right_subtree_height) + 1 else: root.left_subtree_height = 0 if root.has_right_child(): root.right_subtree_height = max(root.right.left_subtree_height, root.right.right_subtree_height) + 1 else: root.right_subtree_height = 0 root.balance_factor = root.left_subtree_height - root.right_subtree_height if root.balance_factor < -1 or root.balance_factor > 1: self._rebalance(root) return if root.balance_factor != old_balance_factor and root.has_parent(): self._update(root.parent) def _rebalance(self, root: TreeNode): if root.balance_factor < 0: if root.right.balance_factor > 0: self._rotate_right(root.right) else: self._rotate_left(root) else: if root.left.balance_factor < 0: self._rotate_left(root.left) else: self._rotate_right(root) def _rotate_left(self, root: TreeNode): old_root: TreeNode = root new_root: TreeNode = old_root.right old_root.right = new_root.left if new_root.has_left_child(): new_root.left.parent = old_root new_root.parent = old_root.parent if old_root.has_parent(): if old_root.is_left_child(): old_root.parent.left = new_root else: old_root.parent.right = new_root else: self.root = new_root old_root.parent = new_root new_root.left = old_root self._update_balance_factor(old_root) def _rotate_right(self, root: TreeNode): old_root: TreeNode = root new_root: TreeNode = old_root.left old_root.left = new_root.right if new_root.has_right_child(): new_root.right.parent = old_root new_root.parent = old_root.parent if old_root.has_parent(): if old_root.is_left_child(): old_root.parent.left = new_root else: old_root.parent.right = new_root else: self.root = new_root old_root.parent = new_root new_root.right = old_root self._update_balance_factor(old_root) ================================================ FILE: trees/binary-heap.py ================================================ # min heap class BinaryHeap: def __init__(self): self.pointer: int = 0 self.heap: list = [None] def is_empty(self) -> bool: return self.pointer == 0 def insert(self, item): self.heap.append(item) self.pointer += 1 self.perc_up(self.pointer) def perc_up(self, index: int): while index // 2 > 0: if self.heap[index] < self.heap[index // 2]: self.heap[index], self.heap[index // 2] = self.heap[index // 2], self.heap[index] index = index // 2 def get_min(self): if self.is_empty(): raise Exception("Heap is empty") return self.heap[1] def delete_min(self): if self.is_empty(): raise Exception("Heap is empty") ret_value: int = self.heap[1] self.heap[1] = self.heap[self.pointer] self.heap.pop() self.pointer -= 1 self.perc_down(1) return ret_value def perc_down(self, index: int): while index * 2 <= self.pointer: swap_index: int = self.find_swap_index(index) if self.heap[swap_index] < self.heap[index]: self.heap[swap_index], self.heap[index] = self.heap[index], self.heap[swap_index] index = swap_index def find_swap_index(self, index: int) -> int: if index * 2 + 1 > self.pointer: return index * 2 else: if self.heap[index * 2] <= self.heap[index * 2 + 1]: return index * 2 else: return index * 2 + 1 def build_heap(self, nums: list): for n in nums: self.insert(n) h: BinaryHeap = BinaryHeap() h.insert(10) h.insert(1) h.insert(3) print(h.heap) # [None, 1, 10, 3] print(h.get_min()) # 1 while not h.is_empty(): print(h.delete_min()) ================================================ FILE: trees/binary-search-tree.py ================================================ class TreeNode: def __init__(self, key=None, value=None, parent=None, left=None, right=None): self.key = key self.value = value self.parent = parent self.left = left self.right = right def has_left_child(self) -> bool: return self.left is not None def has_right_child(self) -> bool: return self.right is not None def has_both_children(self) -> bool: return self.has_left_child() and self.has_right_child() def is_leaf(self) -> bool: return not self.has_left_child() and not self.has_right_child() def is_root(self) -> bool: return self.parent is None def has_parent(self) -> bool: return self.parent is not None def is_left_child(self) -> bool: if self.parent is None: return False return self.parent.left == self def is_right_child(self) -> bool: if self.parent is None: return False return self.parent.right == self def find_min(self): if self is None: return None if self.has_left_child(): return self.left.find_min() else: return self def find_max(self): if self is None: return None node = self while node.right is not None: node = node.right return node class BinarySearchTree: def __init__(self): self.root: TreeNode = None self.elements: int = 0 def size(self) -> int: return self.elements def is_empty(self) -> bool: return self.root is None def put(self, key, value): if self.is_empty(): self.root = TreeNode(key, value) self.elements += 1 else: self._put(self.root, key, value) def _put(self, root: TreeNode, key, value): if root.key == key: root.value = value elif key < root.key: if root.has_left_child(): self._put(root.left, key, value) else: root.left = TreeNode(key, value, root) self.elements += 1 else: if root.has_right_child(): self._put(root.right, key, value) else: root.right = TreeNode(key, value, root) self.elements += 1 def get(self, key) -> TreeNode: if self.is_empty(): return None else: return self._get(self.root, key) def _get(self, root: TreeNode, key) -> TreeNode: if root.key == key: return root elif key < root.key: if root.has_left_child(): return self._get(root.left, key) else: return None else: if root.has_right_child(): return self._get(root.right, key) else: return None def contains(self, key) -> bool: if self.is_empty(): return False found: bool = False node: TreeNode = self.root while node is not None and not found: if node.key == key: found = True elif key < node.key: node = node.left else: node = node.right return found def delete(self, key): node_to_delete: TreeNode = self.get(key) if node_to_delete is None: return if node_to_delete.is_root(): if node_to_delete.is_leaf(): self.root = None self.elements -= 1 elif node_to_delete.has_both_children(): max_node: TreeNode = node_to_delete.left.find_max() tmp_key = max_node.key tmp_value = max_node.value self.delete(tmp_key) node_to_delete.key = tmp_key node_to_delete.value = tmp_value else: if node_to_delete.has_left_child(): self.root = node_to_delete.left else: self.root = node_to_delete.right self.root.parent = None self.elements -= 1 else: if node_to_delete.is_leaf(): if node_to_delete.is_left_child(): node_to_delete.parent.left = None else: node_to_delete.parent.right = None self.elements -= 1 elif node_to_delete.has_both_children(): max_node: TreeNode = node_to_delete.left.find_max() tmp_key = max_node.key tmp_value = max_node.value self.delete(tmp_key) node_to_delete.key = tmp_key node_to_delete.value = tmp_value elif node_to_delete.has_left_child(): self.elements -= 1 if node_to_delete.is_left_child(): node_to_delete.parent.left = node_to_delete.left else: node_to_delete.parent.right = node_to_delete.left node_to_delete.left.parent = node_to_delete.parent else: self.elements -= 1 if node_to_delete.is_left_child(): node_to_delete.parent.left = node_to_delete.right else: node_to_delete.parent.right = node_to_delete.right node_to_delete.right.parent = node_to_delete.parent def find_min(self) -> TreeNode: if self.is_empty(): return None node: TreeNode = self.root while node.left is not None: node = node.left return node def find_max(self) -> TreeNode: if self.is_empty(): return None node: TreeNode = self.root while node.right is not None: node = node.right return node ================================================ FILE: trees/class-representation.py ================================================ class TreeNode: def __init__(self, key=None, left=None, right=None): self.key = key self.left = left self.right = right def insert_root_value(self, key=None): self.key = key def insert_left(self, key=None): self.left = TreeNode(key, self.left) def insert_right(self, key=None): self.right = TreeNode(key, None, self.right) def get_root_value(self): return self.key def get_left_child(self): return self.left def get_right_child(self): return self.right # Write a function build_tree that returns a tree # using the list of lists functions that look like this : # a # / \ # b c # \ / \ # d e f def build_tree() -> TreeNode: tree: TreeNode = TreeNode('a') tree.insert_right('f') tree.insert_right('c') tree.get_right_child().insert_left('e') tree.insert_left('b') tree.get_left_child().insert_right('d') return tree binary_tree: TreeNode = build_tree() def print_tree(tree: TreeNode): if tree is not None: print_tree(tree.get_left_child()) print(tree.key, end=", ") print_tree(tree.get_right_child()) print_tree(binary_tree) # ['a', # ['b', # [], # ['d', [], []]], # ['c', # ['e', [], []], # ['f', [], []] # ] # ] ================================================ FILE: trees/list-representation.py ================================================ def create_tree(root=None) -> list: return [None, [], []] def insert_root(tree: list, root=None): tree[0] = root def insert_left(tree: list, root=None): left_child: list = tree.pop(1) tree.insert(1, [root, left_child, []]) def insert_right(tree: list, root=None): right_child: list = tree.pop(2) tree.insert(2, [root, [], right_child]) def get_root(tree: list): return tree[0] def get_left_child(tree: list) -> list: return tree[1] def get_right_child(tree: list) -> list: return tree[2] # Write a function build_tree that returns a tree # using the list of lists functions that look like this : # a # / \ # b c # \ / \ # d e f def build_tree() -> list: tree: list = create_tree() insert_root(tree, 'a') insert_right(tree, 'f') insert_right(tree, 'c') insert_left(get_right_child(tree), 'e') insert_left(tree, 'b') insert_right(get_left_child(tree), 'd') return tree binary_tree: list = build_tree() print(binary_tree) # ['a', # ['b', # [], # ['d', [], []]], # ['c', # ['e', [], []], # ['f', [], []] # ] # ] ================================================ FILE: trees/parse-tree.py ================================================ from stack import Stack import operator import re class TreeNode: def __init__(self, key=None, left=None, right=None): self.key = key self.left = left self.right = right def insert_root_value(self, key=None): self.key = key def insert_left(self, key=None): self.left = TreeNode(key, self.left) def insert_right(self, key=None): self.right = TreeNode(key, None, self.right) def get_root_value(self): return self.key def get_left_child(self): return self.left def get_right_child(self): return self.right opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} pattern = re.compile("[0-9]") string = "( ( 10 + 5 ) * 3 )" def tree_parser(s: str) -> TreeNode: arr: list = s.split() stack: Stack = Stack() node: TreeNode = TreeNode() current_node: TreeNode = node stack.push(node) for e in arr: if e == "(": current_node.insert_left() stack.push(current_node) current_node = current_node.get_left_child() elif e in "+-*/": current_node.insert_root_value(e) current_node.insert_right() stack.push(current_node) current_node = current_node.get_right_child() elif pattern.match(e): current_node.insert_root_value(int(e)) current_node = stack.pop() elif e == ")": current_node = stack.pop() else: raise Exception() return node tree_node: TreeNode = tree_parser(string) def evaluate(node: TreeNode) -> int: if node.get_left_child() is not None and node.get_right_child() is not None: f = opers[node.get_root_value()] return f(evaluate(node.get_left_child()), evaluate(node.get_right_child())) else: return node.get_root_value() print(evaluate(tree_node)) # 45 ================================================ FILE: trees/stack.py ================================================ class Stack: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(self.items) - 1] def size(self): return len(self.items) ================================================ FILE: trees/tree-traversal/functions.py ================================================ from treenode import TreeNode node: TreeNode = TreeNode('a') node.insert_left('b') node.insert_left('c') node.get_left_child().insert_left('f') node.insert_right('k') node.insert_right('j') print("\nInorder") node.inorder() print("\nPreorder") node.preorder() print("\nPostorder") node.postorder() # functions for tree traversal def inorder(tree: TreeNode): if tree is not None: inorder(tree.left) print(tree.key, end=", ") inorder(tree.right) def preorder(tree: TreeNode): if tree is not None: print(tree.key, end=", ") preorder(tree.left) preorder(tree.right) def postorder(tree: TreeNode): if tree is not None: postorder(tree.left) postorder(tree.right) print(tree.key, end=", ") print("\nInorder") inorder(node) print("\nPreorder") preorder(node) print("\nPostorder") postorder(node) ================================================ FILE: trees/tree-traversal/inorder-traversal-example.py ================================================ from stack import Stack import operator import re from treenode import TreeNode opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} pattern = re.compile("[0-9]") string = "( ( 10 + 5 ) * 3 )" def tree_parser(s: str) -> TreeNode: arr: list = s.split() stack: Stack = Stack() node: TreeNode = TreeNode() current_node: TreeNode = node stack.push(node) for e in arr: if e == "(": current_node.insert_left() stack.push(current_node) current_node = current_node.get_left_child() elif e in "+-*/": current_node.insert_root_value(e) current_node.insert_right() stack.push(current_node) current_node = current_node.get_right_child() elif pattern.match(e): current_node.insert_root_value(int(e)) current_node = stack.pop() elif e == ")": current_node = stack.pop() else: raise Exception() return node tree_node: TreeNode = tree_parser(string) def inorder(node: TreeNode) -> str: ret_value: str = "" if node.get_left_child() is not None: ret_value += "(" + inorder(node.get_left_child()) ret_value += str(node.get_root_value()) if node.get_right_child() is not None: ret_value += inorder(node.get_right_child()) + ")" return ret_value print(inorder(tree_node)) # ((10+5)*3) ================================================ FILE: trees/tree-traversal/postorder-traversal-example.py ================================================ from stack import Stack import operator import re from treenode import TreeNode opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} pattern = re.compile("[0-9]") string = "( ( 10 + 5 ) * 3 )" def tree_parser(s: str) -> TreeNode: arr: list = s.split() stack: Stack = Stack() node: TreeNode = TreeNode() current_node: TreeNode = node stack.push(node) for e in arr: if e == "(": current_node.insert_left() stack.push(current_node) current_node = current_node.get_left_child() elif e in "+-*/": current_node.insert_root_value(e) current_node.insert_right() stack.push(current_node) current_node = current_node.get_right_child() elif pattern.match(e): current_node.insert_root_value(int(e)) current_node = stack.pop() elif e == ")": current_node = stack.pop() else: raise Exception() return node tree_node: TreeNode = tree_parser(string) def postorder(node: TreeNode) -> str: if node is None: return None left = postorder(node.get_left_child()) right = postorder(node.get_right_child()) if left is not None and right is not None: f = opers[node.get_root_value()] return f(left, right) else: return node.get_root_value() print(postorder(tree_node)) # 45 ================================================ FILE: trees/tree-traversal/preorder-traversal-example.py ================================================ from stack import Stack import operator import re from treenode import TreeNode opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv} pattern = re.compile("[0-9]") string = "( ( 10 + 5 ) * 3 )" def tree_parser(s: str) -> TreeNode: arr: list = s.split() stack: Stack = Stack() node: TreeNode = TreeNode() current_node: TreeNode = node stack.push(node) for e in arr: if e == "(": current_node.insert_left() stack.push(current_node) current_node = current_node.get_left_child() elif e in "+-*/": current_node.insert_root_value(e) current_node.insert_right() stack.push(current_node) current_node = current_node.get_right_child() elif pattern.match(e): current_node.insert_root_value(int(e)) current_node = stack.pop() elif e == ")": current_node = stack.pop() else: raise Exception() return node tree_node: TreeNode = tree_parser(string) def preorder(node: TreeNode, space: int = 0): if node is not None: print(" " * space, node.key) preorder(node.get_left_child(), space + 2) preorder(node.get_right_child(), space + 2) preorder(tree_node) ================================================ FILE: trees/tree-traversal/stack.py ================================================ class Stack: def __init__(self): self.items = [] def is_empty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(self.items) - 1] def size(self): return len(self.items) ================================================ FILE: trees/tree-traversal/treenode.py ================================================ class TreeNode: def __init__(self, key=None, left=None, right=None): self.key = key self.left = left self.right = right def insert_root_value(self, key=None): self.key = key def insert_left(self, key=None): self.left = TreeNode(key, self.left) def insert_right(self, key=None): self.right = TreeNode(key, None, self.right) def get_root_value(self): return self.key def get_left_child(self): return self.left def get_right_child(self): return self.right def inorder(self): if self.get_left_child() is not None: self.get_left_child().inorder() print(self.key, end=", ") if self.get_right_child() is not None: self.get_right_child().inorder() def preorder(self): print(self.key, end=", ") if self.get_left_child() is not None: self.get_left_child().preorder() if self.get_right_child() is not None: self.get_right_child().preorder() def postorder(self): if self.get_left_child() is not None: self.get_left_child().postorder() if self.get_right_child() is not None: self.get_right_child().postorder() print(self.key, end=", ") ================================================ FILE: trie/trie.py ================================================ class TrieNode: def __init__(self, key, parent = None, children: dict = {}): self.key = key self.parent = parent self.children:dict = {} self.endchar: bool = False class Trie: def __init__(self): self.root: TrieNode = TrieNode(None) def insert(self, string: str): current: TrieNode = self.root for character in string: if character not in current.children: current.children[character] = TrieNode(character, current) current = current.children[character] current.endchar = True def contains(self, string: str)->bool: current: TrieNode = self.root for character in string: if character not in current.children: current = None break current = current.children[character] if current is None: return False return current.endchar def delete(self, string: str): current: TrieNode = self.root for character in string: if character not in current.children: current = None break current = current.children[character] if current is None: return current.endchar = False parent: TrieNode = current.parent while parent is not None and not current.endchar and len(current.children) == 0: del(parent.children[current.key]) current = parent parent = current.parent def prefix(self, prefix: str)->list: current: TrieNode = self.root for character in prefix: if character not in current.children: current = None break current = current.children[character] if current is None: return words: list = [] self.helper(current, words, prefix) return words def helper(self, node: TrieNode, words: list, currentWord: str): if node is None: return if node.endchar: words.append(currentWord) for key in node.children: self.helper(node.children[key], words, currentWord + key) def allWords(self)->list: words: list = [] self.helper(self.root, words, "") return words def count(self)->int: return self.countHelper(self.root) def countHelper(self, node: TrieNode)->int: if node is None: return 0 sum: int = 0 if node.endchar: sum += 1 for character in node.children: sum += self.countHelper(node.children[character]) return sum trie = Trie() trie.insert("javascript") trie.insert("java") trie.insert("scala") trie.insert("scale") trie.insert("scalable") trie.insert("perl") print("Contains 'javascript' : ", trie.contains("javascript")) print("Contains 'java' : ", trie.contains("java")) print("Contains 'ruby' : ", trie.contains("ruby")) #trie.delete("java") trie.delete("javascript") print("Contains 'javascript' : ", trie.contains("javascript")) print("Contains 'java' : ", trie.contains("java")) print("Contains 'ruby' : ", trie.contains("ruby")) print(trie.prefix("scal")) # ['scala', 'scalable', 'scale'] print(trie.prefix("java")) # ['java'] print("All words", trie.allWords()) # All words ['java', 'scala', 'scalable', 'scale', 'perl'] print("Count : ", trie.count())