arrow-left

All pages
gitbookPowered by GitBook
1 of 3

Loading...

Loading...

Loading...

Searching & Sorting Computational Complexity (JS)

hashtag
Sorting Algorithms

hashtag
Bubble Sort

Time Complexity: Quadratic O(n^2)

  • The inner for-loop contributes to O(n), however in a worst case scenario the while loop will need to run n times before bringing all n elements to their final resting spot.

Space Complexity: O(1)

  • Bubble Sort will always use the same amount of memory regardless of n.

  • The first major sorting algorithm one learns in introductory programming courses.

  • Gives an intro on how to convert unsorted data into sorted data.

It’s almost never used in production code because:

  • It’s not efficient

  • It’s not commonly used

  • There is stigma attached to it

  • Worst Case & Best Case are always the same because it makes nested loops.

  • Double for loops are polynomial time complexity or more specifically in this case Quadratic (Big O) of: O(n²)

hashtag
Selection Sort

Time Complexity: Quadratic O(n^2)

  • Our outer loop will contribute O(n) while the inner loop will contribute O(n / 2) on average. Because our loops are nested we will get O(n²);

Space Complexity: O(1)

  • Selection Sort will always use the same amount of memory regardless of n.

  • Selection sort organizes the smallest elements to the start of the array.

Summary of how Selection Sort should work:

  1. Set MIN to location 0

  2. Search the minimum element in the list.

  3. Swap with value at location Min

hashtag
Insertion Sort

Time Complexity: Quadratic O(n^2)

  • Our outer loop will contribute O(n) while the inner loop will contribute O(n / 2) on average. Because our loops are nested we will get O(n²);

Space Complexity: O(n)

  • Because we are creating a subArray for each element in the original input, our Space Comlexity becomes linear.

hashtag
Merge Sort

Time Complexity: Log Linear O(nlog(n))

  • Since our array gets split in half every single time we contribute O(log(n)). The while loop contained in our helper merge function contributes O(n) therefore our time complexity is O(nlog(n)); Space Complexity: O(n)

  • We are linear O(n) time because we are creating subArrays.

hashtag

hashtag
Example of Merge Sort

  • Merge sort is O(nlog(n)) time.

  • We need a function for merging and a function for sorting.

Steps:

  1. If there is only one element in the list, it is already sorted; return the array.

  2. Otherwise, divide the list recursively into two halves until it can no longer be divided.

  3. Merge the smallest lists into new list in a sorted order.

hashtag
Quick Sort

Time Complexity: Quadratic O(n^2)

  • Even though the average time complexity O(nLog(n)), the worst case scenario is always quadratic.

Space Complexity: O(n)

  • Our space complexity is linear O(n) because of the partition arrays we create.

  • QS is another Divide and Conquer strategy.

  • Some key ideas to keep in mind:

hashtag
Binary Search

Time Complexity: Log Time O(log(n))

Space Complexity: O(1)

Recursive Solution

Min Max Solution

  • Must be conducted on a sorted array.

  • Binary search is logarithmic time, not exponential b/c n is cut down by two, not growing.

  • Binary Search is part of Divide and Conquer.

hashtag
Insertion Sort

  • Works by building a larger and larger sorted region at the left-most end of the array.

Steps:

  1. If it is the first element, and it is already sorted; return 1.

  2. Pick next element.

  3. Compare with all elements in the sorted sub list

Bubbling Up : Term that infers that an item is in motion, moving in some direction, and has some final resting destination.
  • Bubble sort, sorts an array of integers by bubbling the largest integer to the top.

  • Increment Min to point to next element.
  • Repeat until list is sorted.

  • It is easy to sort elements of an array relative to a particular target value.
  • An array of 0 or 1 elements is already trivially sorted.

  • Shift all the elements in the sorted sub list that is greater than the value to be sorted.
  • Insert the value

  • Repeat until list is sorted.

  • https://gist.github.com/eengineergz/fd4acc0c89033bd219ebf9d3ec40b053arrow-up-right
    https://gist.github.com/eengineergz/80934783c628c70ac2a5a48119a82d54arrow-up-right

    Binary Search

    def binary_search(arr, target):
        left =
    
    function binarySearch(arr, target) {
        let left = 0;
        let right = arr.length - 1;
        while (left <= right) {
            const mid = left + Math.floor((right - left) / 2);
            if (arr[mid] === target) {
                return mid;
            }
            if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }
    
    console.log(binarySearch([1, 2, 3, 10], 1) === 0);
    console.log(binarySearch([1, 2, 3, 10], 2) === 1);
    console.log(binarySearch([1, 2, 3, 10], 3) === 2);
    console.log(binarySearch([1, 2, 3, 10], 10) === 3);
    console.log(binarySearch([1, 2, 3, 10], 9) === -1);
    console.log(binarySearch([1, 2, 3, 10], 4) === -1);
    console.log(binarySearch([1, 2, 3, 10], 0) === -1);
    console.log(binarySearch([1, 2, 3, 10], 11) === -1);
    console.log(binarySearch([5, 7, 8, 10], 3) === -1);
    
    def binary_search(arr, target):
        left = 0;
        right = len(arr) - 1
        while left <= right:
            mid = left + (right - left) // 2;
            if arr[mid] == target:
                return mid
            elif arr[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return -1
    
    def bisect_left(arr, target):
        """Returns the leftmost position that `target` should
        go to such that the sequence remains sorted."""
        left = 0
        right = len(arr)
        while left < right:
            mid = (left + right) // 2
            if arr[mid] < target:
                left = mid + 1
            else:
                right = mid
        return left
    
    def bisect_right(arr, target):
        """Returns the rightmost position that `target` should
        go to such that the sequence remains sorted."""
        left = 0
        right = len(arr)
        while left < right:
            mid = (left + right) // 2
            if arr[mid] > target:
                right = mid
            else:
                left = mid + 1
        return left
    
    print(binary_search([1, 2, 3, 10], 1) == 0)
    print(binary_search([1, 2, 3, 10], 2) == 1)
    print(binary_search([1, 2, 3, 10], 3) == 2)
    print(binary_search([1, 2, 3, 10], 10) == 3)
    print(binary_search([1, 2, 3, 10], 9) == -1)
    print(binary_search([1, 2, 3, 10], 4) == -1)
    print(binary_search([1, 2, 3, 10], 0) == -1)
    print(binary_search([1, 2, 3, 10], 11) == -1)
    print(binary_search([5, 7, 8, 10], 3) == -1)
    
    print(bisect_left([1, 2, 3, 3, 10], 1) == 0)
    print(bisect_left([1, 2, 3, 3, 10], 2) == 1)
    print(bisect_left([1, 2, 3, 3, 10], 3) == 2) # First "3" is at index 2
    print(bisect_left([1, 2, 3, 3, 10], 10) == 4)
    
    # These return a valid index despite target not being in array.
    print(bisect_left([1, 2, 3, 3, 10], 9) == 4)
    print(bisect_left([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
    print(bisect_left([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
    
    print(bisect_right([1, 2, 3, 3, 10], 1) == 1)
    print(bisect_right([1, 2, 3, 3, 10], 2) == 2)
    print(bisect_right([1, 2, 3, 3, 10], 3) == 4) # Last "3" is at index 3, so insert new "3" at index 4
    print(bisect_right([1, 2, 3, 3, 10], 10) == 5)
    
    # These return a valid index despite target not being in array.
    print(bisect_right([1, 2, 3, 3, 10], 9) == 4)
    print(bisect_right([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
    print(bisect_right([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
    
    
    0
    ;
    right = len(arr) - 1
    while left <= right:
    mid = left + (right - left) // 2;
    if arr[mid] == target:
    return mid
    elif arr[mid] < target:
    left = mid + 1
    else:
    right = mid - 1
    return -1
    def bisect_left(arr, target):
    """Returns the leftmost position that `target` should
    go to such that the sequence remains sorted."""
    left = 0
    right = len(arr)
    while left < right:
    mid = (left + right) // 2
    if arr[mid] < target:
    left = mid + 1
    else:
    right = mid
    return left
    def bisect_right(arr, target):
    """Returns the rightmost position that `target` should
    go to such that the sequence remains sorted."""
    left = 0
    right = len(arr)
    while left < right:
    mid = (left + right) // 2
    if arr[mid] > target:
    right = mid
    else:
    left = mid + 1
    return left
    print(binary_search([1, 2, 3, 10], 1) == 0)
    print(binary_search([1, 2, 3, 10], 2) == 1)
    print(binary_search([1, 2, 3, 10], 3) == 2)
    print(binary_search([1, 2, 3, 10], 10) == 3)
    print(binary_search([1, 2, 3, 10], 9) == -1)
    print(binary_search([1, 2, 3, 10], 4) == -1)
    print(binary_search([1, 2, 3, 10], 0) == -1)
    print(binary_search([1, 2, 3, 10], 11) == -1)
    print(binary_search([5, 7, 8, 10], 3) == -1)
    print(bisect_left([1, 2, 3, 3, 10], 1) == 0)
    print(bisect_left([1, 2, 3, 3, 10], 2) == 1)
    print(bisect_left([1, 2, 3, 3, 10], 3) == 2) # First "3" is at index 2
    print(bisect_left([1, 2, 3, 3, 10], 10) == 4)
    # These return a valid index despite target not being in array.
    print(bisect_left([1, 2, 3, 3, 10], 9) == 4)
    print(bisect_left([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
    print(bisect_left([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back
    print(bisect_right([1, 2, 3, 3, 10], 1) == 1)
    print(bisect_right([1, 2, 3, 3, 10], 2) == 2)
    print(bisect_right([1, 2, 3, 3, 10], 3) == 4) # Last "3" is at index 3, so insert new "3" at index 4
    print(bisect_right([1, 2, 3, 3, 10], 10) == 5)
    # These return a valid index despite target not being in array.
    print(bisect_right([1, 2, 3, 3, 10], 9) == 4)
    print(bisect_right([1, 2, 3, 3, 10], 0) == 0) # Insert "0" at front
    print(bisect_right([1, 2, 3, 3, 10], 11) == 5) # Insert "5" at back

    Searching

    hashtag
    Sorting and Searching

    • Sorting search results on a page given a certain set of criteria.

    • Sort a list of numbers in which each number is at a distance K from its actual position.

    • Given an array of integers, sort the array so that all odd indexes are greater than the even indexes.

    • Given users with locations in a list and a logged-in user with locations, find their travel buddies (people who shared more than half of your locations).

    • Search for an element in a sorted and rotated array.

    • Sort a list where each element is no more than k positions away from its sorted position.

    • Search for an item in a sorted, but rotated, array.

    • Merge two sorted lists together.

    • Give 3 distinct algorithms to find the K largest values in a list of N items.

    • Find the minimum element in a sorted rotated array in faster than O(n) time.

    • Write a function that takes a number as input and outputs the biggest number with the same set of digits.

    Sourcearrow-up-right
    Sourcearrow-up-right
    Arraychevron-right
    Binary Search Treechevron-right
    Linked Listchevron-right
    Extra-Arraychevron-right
    Stackchevron-right
    Binary Treechevron-right
    Recursionchevron-right
    Hash Tablechevron-right
    Searchingchevron-right
    Sortingchevron-right
    Queue Sandboxchevron-right
    Hash Tablechevron-right
    Double Linked Listchevron-right
    Graphschevron-right
    Exoticchevron-right
    Heapchevron-right
    # linear search O(n)
    def name_in_phonebook(to_find, phonebook):
        for name in phonebook:
            if name == to_find:
                return True
        return False
    
    # binary search O(log n)
    def name_in_phonebook_2(to_find, name):
        # sentinal , edge case
        if len(to_find) == 0:
            return False
        # set first element to zero
        first = 0
        # set the last items to size - 1
        last = (len(to_find) - 1)
        # set a found flag to false
        found = False
    
        # loop until either found or end of list
        while first <= last and not found:
            # find the middle of the list using interger division //
            middle = (first + last) // 2
    
            # if found update found variable
            if to_find[middle] == name:
                found = True
            # otherwise
            else:
                # if name  is to the left of the data
                if name < to_find[middle]:
                    # search the lower half
                    last = middle - 1
                # otherwise
                else:
                    # search the upper half
                    first = middle + 1 
        # return found
        return found
    
    from collections import deque
    from collections.abc import Sequence
    
    
    def bfs_search_grid(grid: Sequence[Sequence[int]], start: tuple[int, int], goal: tuple[int, int]) -> bool:
        """On a grid of 0s and 1s, find if start is connected to goal via a path of 1s."""
        rows = range(len(grid))
        cols = range(len(grid[0]))
        seen = {start}
        to_visit = deque([start])
        while to_visit:
            r, c = to_visit.popleft()
            if (r, c) == goal:
                return True
            adjacent = {(r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)}
            for next_node in adjacent - seen:
                r1, c1 = next_node
                # Using these range objects is a concise alternative to 0 <= r1 < len(graph) and 0 <= c1 < len(graph[0])
                if r1 in rows and c1 in cols and grid[r1][c1]:
                    seen.add(next_node)
                    to_visit.append(next_node)
        return False
    
    from collections.abc import Callable
    
    
    def bisect_search(predicate: Callable[[int], bool], low: int, high: int) -> int:
        """Find the lowest int between low and high where predicate(int) is True."""
        while low < high:
            mid = low + (high - low) // 2  # Avoids integer overflow compared to mid = (low + high) // 2
            if predicate(mid):
                high = mid
            else:
                low = mid + 1
        return low
    
    # Uses python3
    import random
    
    """You're going to write a binary search function.
    You should use an iterative approach - meaning
    using loops.
    Your function should take two inputs:
    a Python list to search through, and the value
    you're searching for.
    Assume the list only has distinct elements,
    meaning there are no repeated values, and
    elements are in a strictly increasing order.
    Return the index of value, or -1 if the value
    doesn't exist in the list."""
    
    
    def binary_search(input_array, value):
        test_array = input_array
        current_index = len(input_array) // 2
        input_index = current_index
    
        found_value = test_array[current_index]
        while len(test_array) > 1 and found_value != value:
            if found_value < value:
                test_array = test_array[current_index:]
                current_index = len(test_array) // 2
                input_index += current_index
                found_value = input_array[input_index]
            else:
                test_array = test_array[0:current_index]
                current_index = len(test_array) // 2
                # divmod needed to be used instead of round() since the behavior
                # for .5 changed from rounding up to rounding down in Python 3
                q, r = divmod(len(test_array), 2.0)
                input_index = int(input_index - q - r)
                found_value = input_array[input_index]
        else:
            if found_value == value:
                return input_index
    
        return -1
    
    
    def linear_search(a, x):
        for i in range(len(a)):
            if a[i] == x:
                return i
        return -1
    
    
    # compare naive algorithm linear search vs. binary search results
    def stress_test(n, m):
        test_cond = True
        while test_cond:
            a = []
            for i in range(n):
                a.append(random.randint(0, 10 ** 9))
            a.sort()
            for i in range(m):
                b = random.randint(0, n - 1)
                print([linear_search(a, a[b]), binary_search(a, a[b])])
                # stops if the searches do not give identical answers
                if linear_search(a, a[b]) != binary_search(a, a[b]):
                    test_cond = False
                    print("broke here!")
                    break
    
    
    stress_test(100, 100000)
    
    
    # test_list = [1,3,9,11,15,19,29, 35, 36, 37]
    # test_val1 = 25
    # test_val2 = 15
    # print(binary_search(test_list, test_val1))
    # print(binary_search(test_list, test_val2))
    # print(binary_search(test_list, 11))
    
    class BSTNode:
        def __init__(self, value):
            self.value = value
            self.left = None
            self.right = None
    
        def insert(self, value):
            # create a node for the new value
            new_node = BSTNode(value)
            # compare the node value to the self value
            if (
                new_node.value <= self.value
            ):  # less then or equal to will all go to the left if any duplicats too
                # we add to the left
                if self.left is None:
                    self.left = new_node
                else:
                    self.left.insert(value)
            else:
                # we add to the right
                # if space exists
                if self.right is None:
                    self.right = new_node
                else:
                    self.right.insert(value)
    
        def search(self, target):
            if target == self.value:
                return True
            # check if value is less than self.value
    
            if target < self.value:
                # look to the left
                if self.left is None:
                    return False
                return self.left.search(target)
    
            else:
                # look to the right
                if self.right is None:
                    return False
                return self.right.search(target)
    
        def find_minimum_value(self):
            # if self.left is None: #recursive here
            #     return self.value
            # min_value =  self.left.find_minimum_value()
            # return min_value
            curr_node = self
            while curr_node.left is not None:
                curr_node = curr_node.left
            return curr_node.value
    
    
    root = BSTNode(10)
    # root.left = BSTNode(6)
    # root.right = BSTNode(12)
    root.insert(6)
    root.insert(7)
    root.insert(12)
    root.insert(5)
    root.insert(14)
    root.insert(8)
    
    print(f"minimum value in tree is: {root.find_minimum_value()}")
    
    print(f"does 8 exist? {root.search(8)}")
    print(f"does 8 exist? {root.search(7)}")
    print(f"does 8 exist? {root.search(15)}")
    
    # -*- coding: utf-8 -*-
    """Searching.ipynb
    
    Automatically generated by Colaboratory.
    
    Original file is located at
        https://colab.research.google.com/drive/1Od6PSVwt0pqP6Iko09In0reDVftWMK1F
    
    # Searching
    
    - Linear Search
    - Binary Search
    
    ## Linear Search
    
    
    ## Binary Search
    
    
    
    # Recursion
    - iteration
    - recursive function
    - call stack
    """
    
    """
    Searching (Linear)
    """
    # O(n)
    data = [12, 23, 1, 34, 56, 100]
    target = 10
    
    # starting at the beginning of the data
    # take each value and compare that value to a target value
    # if they are equal return the index of the target value or return the target value
    # if we reach the end of the data, without finding the target then we can return -1
    def linear_search(data, target):
        for i in range(len(data)):
            if data[i] == target:
                return (i, data[i])
        return -1
    
    
    print(linear_search(data, target))
    
    """
    Searching (Binary)
    """
    # 0 (log(n))
    #        0   1   2   3   4    5
    data = [12, 23, 45, 67, 99, 200]
    target = 99
    
    # keep track of begin and end
    
    # while the begin and end do not overlap
    # create a guess index in the middle of the view of data
    # check if the data at the guess index is equal to the target
    # return (guess_index, guess)
    # otherwise is the data at the guess index less than the target
    # set the begin to the guess_index + 1
    # otherwise
    # set end to the guess_index - 1
    
    # if we get here we can not find the target
    # return -1
    def binary_search(data, target):
        begin = 0
        end = len(data) - 1
    
        while not end < begin:
            guess_index = (end + begin) // 2
    
            if data[guess_index] == target:
                return (guess_index, target)
            elif data[guess_index] < target:
                begin = guess_index + 1
            else:
                end = guess_index - 1
    
        return -1
    
    
    print(binary_search(data, target))
    
    """# CODE: 9356"""
    
    ob
    
    """# Recursive Functionality
    Think of a loop and how that actually works. Now lets think about a function and see how that really works
    
    Let's compare the two...
    """
    
    """
    Looping
    """
    import time
    
    n = 10
    s = []
    start = time.time()
    while n > 0:  # O(n)
        print(n)
        n -= 1
    end = time.time()
    print(f"loop runtime = {end - start}")
    
    """
    Recursive Function
    """
    import time
    
    n = 10
    
    
    def while_rec(n):  # O(n)
    
        if not n > 0:  # O(1)
            return
        print(n)  # O(1)
    
        while_rec(n - 1)  # O(1)
    
    
    start = time.time()
    while_rec(n)
    end = time.time()
    print(f"func runtime = {end - start}")
    
    # memoization
    # generic memo_func
    
    
    def memo_func(f):
        cache = {}
    
        def memo_helper(n):
            if n not in cache:
                cache[n] = f(n)
            return cache[n]
    
        return memo_helper
    
    
    """
    [ 0, 1, 1, 2, 3, 5, 8]
    fib(n) => fib(n - 1) + fib(n - 2)
    2
    fib(3) => 1  + 1
    fib(2) => 1 + 0
    fib(1) => 1
    fib(0) => 0
    fib(1) => 1
    """
    from functools import lru_cache
    
    # import sys
    # reclim = sys.getrecursionlimit()
    # sys.setrecursionlimit(reclim * 10)
    # reclim = sys.getrecursionlimit()
    print(reclim)
    
    
    @lru_cache(maxsize=10000)
    def fib(n):
        if n <= 1:
            return n
        else:
            return fib(n - 1) + fib(n - 2)
    
    
    @lru_cache(maxsize=1000000)
    def fib2(n):
        if n <= 1:
            return n
        else:
            return fib(n - 1) + fib(n - 2)
    
    
    # fib(46)
    # memfib = memo_func(fib)
    
    # memfib(46)
    fib(460)
    
    from collections import deque
    from collections.abc import Callable, Iterable, Mapping
    
    from src.typehints import Node
    
    
    def bfs_search_dict(graph: Mapping[Node, Iterable[Node]], start: Node, predicate: Callable[[Node], bool]) -> bool:
        """Find the closest node to start that matches the predicate using breadth first search."""
        visited = set()
        to_visit = deque([start])
        while to_visit:
            node = to_visit.popleft()
            if node in visited:
                continue
            visited.add(node)
            if predicate(node):
                return True
            to_visit += graph[node]
        return False