GitHub, Big-O, and Intro to Data Structures and Algorithms (Week 7) - Learning Objectives

Assessment Structure

Binary Search (W7D1) - Learning Objectives

  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;
  }
}

Sorting Algorithms (W7D2) - Learning Objectives

  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);
}

Big-O, Memoization, and Tabulation (W7D3) - 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)
quick *O(n log n)/O(n^2) *O(n)/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);
}

// 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 = {}) {
  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];
}
  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];
}

Lists, Stacks, and Queues (W7D4) - Learning Objectives

Lists, Stacks, and Queues

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

Heaps (W7D5)

Not on the assessment don’t worry