+8

6 Algorithms Every Developer Should Know

As a developer, you're probably familiar with the concept of algorithms. But if data structures and algorithms aren't your thing, don't worry. In this article, we'll go over the six key algorithms that every developer should know. These six algorithms can almost always solve any problem you'll encounter in your development process.

1. Sorting Algorithm

Sorting is the process of arranging items in a list in a specific order. The most common types of sorting algorithms include:

Bubble Sort

Bubble sort is the most basic sorting algorithm. It works by repeatedly swapping adjacent elements if they are out of order.

function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}
console.log(bubbleSort([5, 3, 8, 2, 1, 4])); // [1, 2, 3, 4, 5, 8]

Merge Sort

Merge sort uses the "divide and conquer" strategy. It divides an array into two halves, sorts each half, and then merges the sorted halves back together.

function mergeSort(arr) {
  if (arr.length < 2) return arr;
  let middle = Math.floor(arr.length / 2);
  let left = arr.slice(0, middle);
  let right = arr.slice(middle);
  return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
  let result = [];
  while (left.length && right.length) {
    if (left[0] <= right[0]) {
      result.push(left.shift());
    } else {
      result.push(right.shift());
    }
  }
  while (left.length) result.push(left.shift());
  while (right.length) result.push(right.shift());
  return result;
}
console.log(mergeSort([5, 3, 8, 2, 1, 4])); // [1, 2, 3, 4, 5, 8]

Quick Sort

Quicksort is a popular sorting algorithm that performs n log n comparisons on average when sorting an array of n elements. It is a more efficient and faster sorting algorithm.

function quickSort(arr) {
  if (arr.length < 2) return arr;
  let pivot = arr[arr.length - 1];
  let left = [];
  let right = [];
  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat(pivot, quickSort(right));
}
console.log(quickSort([5, 3, 8,2, 1, 4])); // [1, 2, 3, 4, 5, 8]

Heap Sort

Heap sort works by visualizing the array elements as a special type of complete binary tree known as a heap. The process of heapifying an array is repeated until the array is sorted.

function heapSort(arr) {
  let n = arr.length;
  for (let i = n / 2 - 1; i >= 0; i--) {
    heapify(arr, n, i);
  }
  for (let i = n - 1; i >= 0; i--) {
    [arr[0], arr[i]] = [arr[i], arr[0]];
    heapify(arr, i, 0);
  }
  return arr;
}
function heapify(arr, n, i) {
  let largest = i;
  let l = 2 * i + 1;
  let r = 2 * i + 2;
  if (l < n && arr[l] > arr[largest]) {
    largest = l;
  }
  if (r < n && arr[r] > arr[largest]) {
    largest = r;
  }
  if (largest !== i) {
    [arr[i], arr[largest]] = [arr[largest], arr[i]];
    heapify(arr, n, largest);
  }
}
console.log(heapSort([5, 3, 8, 2, 1, 4])); // [1, 2, 3, 4, 5, 8]

2. Searching Algorithm

Searching is the process of finding an element in a data set. Common types of searching algorithms include:

Binary Search

Binary search uses the "divide and conquer" strategy. It divides a sorted list into two halves and compares the target value to the middle element. If a match is found, the middle element's location is returned.

function binarySearch(arr, x) {
  let left = 0;
  let right = arr.length - 1;
  while (left <= right) {
    let middle = Math.floor((left + right) / 2);
    if (arr[middle] === x) {
      return middle;
    } else if (arr[middle] < x) {
      left = middle + 1;
    } else {
      right = middle - 1;
    }
  }
  return -1;
}
console.log(binarySearch([1, 2, 3, 4, 5, 8], 4)); // 3

Breadth-First Search (BFS)

Breadth-first search is a graph traversal algorithm that begins at the root node and explores all neighboring nodes.

function breadthFirstSearch(root, target) {
  let queue = [root];
  while (queue.length) {
    let node = queue.shift();
    if (node.value === target) return node;
    queue.push(...node.children);
  }
  return null;
}

Depth-First Search (DFS)

Depth-first search is a graph traversal algorithm that begins at the first node of the graph and goes deeper and deeper until the goal node or node with no children is found.

function depthFirstSearch(node, target) {
  if (node.value === target) return node;
  for (let child of node.children) {
    let found = depthFirstSearch(child, target);
    if (found) return found;
  }
  return null;
}

3. Dynamic Programming

Dynamic programming (DP) is an algorithmic technique that solves an optimization problem by breaking it down into simpler sub-problems and taking advantage of the fact that the optimal solution to the overall problem is dependent on the optimal solution to its sub-problems. A common example is the problem of computing the nth Fibonacci number.

function fibonacci(n) {
  let dp = [];
  dp[0] = 0;
  dp[1] = 1;
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}
console.log(fibonacci(5)); // 5

In this example, we use an array dp to store the intermediate results of each sub-problem (the Fibonacci numbers up to the nth). This allows us to avoid redundant computation, greatly reducing the time complexity of the algorithm.

4. Recursion Algorithm

Recursion is a problem-solving technique in which the solution is dependent on solutions to smaller instances of the same problem. A classic example of recursion is computing factorials.

function factorial(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
}
console.log(factorial(5)); // 120

5. Divide and Conquer

Divide and conquer is a general algorithmic strategy that involves breaking a problem down into smaller sub-problems and solving each sub-problem independently. An example of divide-and-conquer algorithm is the problem of finding the closest pair of points in a set of points in a 2-dimensional space.

function closestPair(points) {
  let minDistance = Number.MAX_SAFE_INTEGER;
  let closestPair = [];
  points.sort((a, b) => a.x - b.x);
  for (let i = 0; i < points.length - 1; i++) {
    for (let j = i + 1; j < points.length; j++) {
      let distance = Math.sqrt(
        (points[i].x - points[j].x) ** 2 + (points[i].y - points[j].y) ** 2
      );
      if (distance < minDistance) {
        minDistance = distance;
        closestPair = [points[i], points[j]];
      }
    }
  }
  return closestPair;
}
console.log(
  closestPair([
    { x: 0, y: 0 },
    { x: 3, y: 4 },
    { x: 1, y: 1 },
    { x: 2, y: 2 },
    { x: 5, y: 6 },
  ])
);

In this example, the algorithm first sorts the points by their x-coordinates. Then, for each point, it compares its distance to all the points that come after it in the sorted array, updating the minimum distance and closest pair if a closer pair is found.

Hashmap

A Hashmap is a data structure that stores key-value pairs and allows for constant-time O(1) insertion, deletion, and lookup. It works by taking the key and applying a hash function to it, which returns an index in an array, the element of that index is called a bucket. The value is then stored in that bucket.

class HashMap {
  constructor() {
    this.buckets = Array(10)
      .fill(null)
      .map(() => []);
    this.keys = this.keys = {};
  }

  hash(key) {
    let hash = 0;
    for (let i = 0; i < key.length; i++) {
      hash += key.charCodeAt(i);
    }
    return hash % this.buckets.length;
  }

  set(key, value) {
    let index = this.hash(key);
    for (let bucket of this.buckets[index]) {
      if (bucket[0] === key) {
        bucket[1] = value;
        return;
      }
    }
    this.buckets[index].push([key, value]);
    this.keys[key] = value;
  }

  get(key) {
    let index = this.hash(key);
    for (let bucket of this.buckets[index]) {
      if (bucket[0] === key) {
        return bucket[1];
      }
    }
    return null;
  }

  delete(key) {
    let index = this.hash(key);
    let i = 0;
    for (let bucket of this.buckets[index]) {
      if (bucket[0] === key) {
        this.buckets[index].splice(i, 1);
        delete this.keys[key];
        return;
      }
      i++;
    }
  }
}

let map = new HashMap();
map.set("name", "John Smith");
map.set("age", 30);
console.log(map.get("name")); // "John Smith"
console.log(map.get("age")); // 30
map.delete("name");
console.log(map.get("name")); // null

In this example, we created a HashMap class which has properties buckets, keys and methods hash, set, get, delete. The hash method takes a key as an input, converts it into a numerical value, and returns the index of the corresponding bucket in the array. The set, get, and delete methods use the hash method to find the appropriate bucket, then add, retrieve, or remove the key-value pair as appropriate.

Hashmap is a powerful data structure that is used in a wide range of scenarios, such as caching, implementing dictionaries, or storing large amounts of data. Knowing how to use Hashmap will be helpful in a wide range of problems you will encounter as a developer.

Conclusion

Understanding and mastering six key algorithms is crucial for any developer.

The six algorithms are:

  • Sorting algorithms: bubble sort, merge sort, quick sort, heap sort
  • Searching algorithms: binary search, breadth-first search, depth-first search
  • Dynamic programming
  • Recursion
  • Divide and conquer

These algorithms are fundamental building blocks of computer science and are used in a wide range of problems, from sorting and searching data, to optimizing complex problems. Understanding and practicing these algorithms will improve your development skills and make you a more efficient and effective programmer. Additionally, learning these algorithms will give you a solid foundation of computer science knowledge that will be useful regardless of the programming language you are working with.

Understanding and using Hashmap is an important skill for a developer as well, it's a data structure that can help you to efficiently store, retrieve, and modify large amounts of data.

Each of these algorithms comes with its own use cases, strengths and weaknesses. Mastering all of them will help you choose the right algorithm to solve any specific problem you may encounter in your development career.

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí