Week 7 Learning Objectives

Big-O

  1. Order the common complexity classes according to their growth rate
  1. Identify the complexity classes of common sort methods
Sort Name Time Complexity Space Complexity
bubble O(n^2) O(1)
selection O(n^2) O(1)
insertion O(n^2) O(1)
merge O(n log n) *O(n) OR O(n log n)
quick **O(n log n) OR O(n^2) **O(n log n) OR O(log n)
  1. Identify complexity classes of code

Memoization and Tabulation

  1. Apply memoization to recursive problems to make them less than polynomial time.
// Standard Implementation
// Time Complexity: O(2^n)
//   - For each call to fib, we have to make two branches, with n levels to this tree
function fib(n) {
  if (n === 1 || n === 2) return 1;
  return fib(n - 1) + fib(n - 2); // fib(3) + fib(2)
}
//fib(2) + fib(1)

// Using memoization
// Time Complexity: O(n)
//   - We only ever have to calculate fib(x) one time, every other time that we use it in a larger problem, the result is immediately accessible in our memo
//   - The memo removes the repeated trees of calculations from our original code
function fib(n, memo = {}) { // n = 4, memo = { 3: 2, 4: 3 }
  if (n in memo) return memo[n]; // If we already calculated this value, return it
  if (n === 1 || n === 2) return 1;

  // Store the result in the memo first before returning
  // Make sure to pass the memo in to your calls to fib!
  memo[n] = fib(n - 1, memo) + fib(n - 2, memo); 
  return memo[n];
}

// memo[5] = fib(4) + fib(3) = 3 + memo[3] = 5
// memo[4] = fib(3) + fib(2) = 2 + 1 = 3
// memo[3] = fib(2) + fib(1) = 1 + 1 = 2
  1. Apply tabulation to iterative problems to make them less than polynomial time.
// Using tabulation
// Time Complexity: O(n)
//   - We are iterating through an array directly related to the size of the input and performing a constant number of calculations at each step (adding our two previous values together and storing the result in the array).
function fib(n) {
  // We create a table to track our values as we build them up
  // We're making it n+1 here so that table[n] lines up with the nth fib number
  // This is because arrays are zero-indexed.
  // We could have used an array of length n, but we would have to remember that 
  // the nth fib number would then be stored at table[n-1]. Completely doable,
  // but I think making them line up is more intuitive.
  let table = new Array(n + 1); 
  // Seed our table with our starting values.
  // Again, if we had a table of length n, we could have seeded table[0] = 1
  // and table[1] = 1 and had the same final result with our indices shifted.
  table[0] = 0;
  table[1] = 1;

  // Iterate through our input and fill out our table as we go.
  for (let i = 2; i < table.length; i++) {
    table[i] = table[i - 1] + table[i - 2];
  }

  // At the end, the final entry in our table is the result we are looking for.
  // The table holds all of the sub-answers that we used to get to this result.
  return table[n];
}

Sorting Algorithms

Code and Gifs for Sorting Algorithms

  1. Explain the complexity of and write a function that performs bubble sort on an array of numbers.
function bubbleSort(array) {
  let swapped = true;

  while(swapped) {
    swapped = false;

    for (let i = 0; i < array.length - 1; i++) {
      if (array[i] > array[i+1]) {
        let temp = array[i];
        array[i] = array[i + 1];
        array[i + 1] = temp;
        // The above three lines could also be in a helper swap function
        // swap(array, i, i+1);
        swapped = true;
      }
    }
  }

  return array;
}
  1. Explain the complexity of and write a function that performs selection sort on an array of numbers.
function selectionSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    let minIndex = i;

    for (let j = i + 1; j < arr.length; j++) {
      if (arr[minIndex] > arr[j]) {
        minIndex = j;
      }
    }

    let temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
    // The above three lines could also be in a helper swap function
    // swap(arr, i, minIndex);
  }
  return arr;
}
  1. Explain the complexity of and write a function that performs insertion sort on an array of numbers.
function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    let currElement = arr[i];
    for (var j = i - 1; j >= 0 && currElement < arr[j]; j--) {
      arr[j + 1] = arr[j];
    }
    arr[j + 1] = currElement;
  }
  return arr;
}
  1. Explain the complexity of and write a function that performs merge sort on an array of numbers.
// The merge function is what is combining our sorted sub-arrays
function merge(array1, array2) {
  let merged = [];

  // keep running while either array still contains elements
  while (array1.length || array2.length) {
    // if array1 is nonempty, take its the first element as ele1
    // otherwise array1 is empty, so take Infinity as ele1
    let ele1 = array1.length ? array1[0] : Infinity;

    // do the same for array2, ele2
    let ele2 = array2.length ? array2[0] : Infinity;

    let next;
    // remove the smaller of the eles from it's array
    if (ele1 < ele2) {
      next = array1.shift();
    } else {
      next = array2.shift();
    }

    // and add that ele to the new array
    merged.push(next);
  }

  return merged;
}

// The mergeSort function breaks apart our input into smaller sub-arrays until we have an input of length <= 1, which is inherently sorted.
// Once we have a left and right subarray that's sorted, we can merge them together to get our sorted result of this sub-problem, passing the sorted version back up the call stack.
function mergeSort(array) {
  if (array.length <= 1) {
    return array;
  }

  let midIdx = Math.floor(array.length / 2);
  let leftHalf = array.slice(0, midIdx);
  let rightHalf = array.slice(midIdx);

  let sortedLeft = mergeSort(leftHalf);
  let sortedRight = mergeSort(rightHalf);

  return merge(sortedLeft, sortedRight);
}
  1. Explain the complexity of and write a function that performs quick sort on an array of numbers.
function quickSort(array) {
  if (array.length <= 1) {
      return array;
  }

  let pivot = array.shift();
  // This implementation uses filter, which returns a new array with any element that passes the criteria (ie the callback returns true).
  // We also could have iterated over the array (array.forEach(el => ...)) and pushed each value into the appropriate left/right subarray as we encountered it.
  let left = array.filter(el => el < pivot);
  let right = array.filter(el => el >= pivot);

  let leftSorted = quickSort(left);
  let rightSorted = quickSort(right);

  return [ ...leftSorted, pivot, ...rightSorted ];
  // We also could have concatenated the arrays instead of spreading their contents
  // return leftSorted.concat([pivot]).concat(rightSorted);
}
  1. Explain the complexity of and write a function that performs a binary search on a sorted array of numbers.
// Returns simply true/false for presence
function binarySearch(array, target) {
  if (array.length === 0) {
      return false;
  }

  let midIdx = Math.floor(array.length / 2);
  let leftHalf = array.slice(0, midIdx);
  let rightHalf = array.slice(midIdx + 1);

  if (target < array[midIdx]) {
      return binarySearch(leftHalf, target);
  } else if (target > array[midIdx]) {
      return binarySearch(rightHalf, target);
  } else {
      return true;
  }
}

// Returns the index or -1 if not found
function binarySearchIndex(array, target) {
  if (!array.length) return -1;

  const midIdx = Math.floor(array.length / 2);
  const midEl = array[midIdx];

  if (target < midEl) {
    return binarySearchIndex(array.slice(0, midIdx), target);
  } else if (target > midEl) {
    // Since our recursive call will have new indices for the subarray, we have to adjust the return value to align it with the indices of our original array.
    // If the recursive call returns -1, it was not found and we can immediately return -1
    // If it was found in the subarray, we have to add on the number of elements that were removed from the beginning of our larger original array.
    // For example, if we try to find 15 in an array of [5, 10, 15]:
      // - Our first call to binarySearchIndex will check our middle element of 10
      // - Since our target is greater, we will recursively call our search on elements to the right, being the subarray [15]
      // - On our recursive call we found our target! It's index in this call is 0.
      // - When we return 0 to where binarySearchIndex was called, we need to adjust it to line up with this larger array (the 0th element of this larger array is 5, but our target was at the 0th index of the subarray)
      // - Since we sliced off 2 elements from the beginning before making our recursive call, we add 2 to the return value to adjust it back to line up with our original array.
    const idxShift = binarySearchIndex(array.slice(midIdx + 1), target);
    return idxShift === -1 ? -1 : idxShift + midIdx + 1;
  } else {
    return midIdx;
  }
}

Lists, Stacks, and Queues

  1. Explain and implement a Linked List.
  1. Explain and implement a Stack.
  1. Explain and implement a Queue.

GitHub (not going to be on the assessment)

  1. You will be able to participate in the social aspects of GitHub by starring repositories, following other developers, and reviewing your followers
  1. You will be able to use Markdown to write code snippets in your README files
  1. You will craft your GitHub profile and contribute throughout the course by keeping your “gardens green”
  1. You will be able to identify the basics of a good Wiki entries for proposals and minimum viable products
  1. You will be able to identify the basics of a good project README that includes technologies at the top, images, descriptions and code snippets

Format of Assessment