Developing strength in the area of graph is a great outlet to sharpen your programming craft. It also has a ripple effect. You wander with a queue, a stack, a priority queue, and whatnot, while dealing with a graph.

To get a glimpse of it, in today’s issue, I’d like to communicate briefly the idea of a Bipartite Graph and its implementation in a very gentle manner. Let’s go.

The whole idea with graph is that we make connections between things, which form a network along its perimeter. A connection sets a bond between two nodes. And if it is possible for two distinct colors to be exhausted on each edge, then we wind up calling it a Bipartite.

Since the structure of a graph can be as broad as the ocean and simultaneously it can be as high as mountains, we can either traverse broadly by exploiting neighbor nodes before descendants, or can go deep by striking descendants first.

These two are the most common techniques to lean into when information is organized like this. The first one is BFS (Breadth First Search), and the latter one is DFS (Depth First Search).

Let’s lay out what steps the BFS Algorithm takes internally for the Bipartite check:

Here’s the BFS implementation in TypeScript:

enum COLORS = {
  COLORED_RED: 0,
  COLORED_GREEN: 1,
  NOT_COLORED: -1
}

const checkBipartiteBFS = function (
  startingNode: number,
  adj: number[][],
  colors: COLORS[]
): boolean {
  // define a queue and give starting node a color and enqueue it
  const queue: number[] = [];
  colors[startingNode] = 0;
  queue.push(startingNode);

  // run a loop until queue becomes empty
  while (queue.length !== 0) {
    // dequeues a node
    const node = queue.shift() as number;

    // look for its neighbour nodes and do those checking stuffs and all
    for (let j = 0; j < adj[node].length; j++) {
      const neighbourNode = adj[node][j];

      // both the neighbour and dequeued node are having same color, not a bipartite
      if (colors[neighbourNode] === colors[node]) {
        return false;
      }

      // neighbour node is not colored,so color and enqueue it in turn
      if (colors[neighbourNode] === -1) {
        colors[neighbourNode] = (colors[node] + 1) % 2; // 0 becomes 1 and 1 becomes 0
        queue.push(neighbourNode);
      }
    }
  }

  // queue becomes empty here, it is a bipartite graph surely
  return true;
};

A quick Primer on Recursion —

Since DFS is recursive by nature, we don’t push a node into the queue every time we discover an un-colored node. Instead, we invoke the function again with the un-colored node. That’s it. That’s the only major difference to observe.

We are repeating the same thing on each iteration if we look closely at BFS. So why not call the function itself from within itself, but with a different node, otherwise the pitfall of recursion pops up, business as usual (shoutout to Stack Overflow).

With that in mind, let’s look at the steps of the DFS Algorithm for Bipartite:

Here’s the DFS implementation in TypeScript:

enum COLORS = {
  COLORED_RED: 0,
  COLORED_GREEN: 1,
  NOT_COLORED: -1,
}

const checkBipartiteDFS = function (
  node: number,
  color: 0 | 1,
  colors: COLORS[],
  adjList: number[][]
) {
  // give that node a color
  colors[node] = color;

  // look for its neighbours and do the checking and call the function recursively if needed
  for (const neighbourNode of adjList[node]) {
    // both having the same color, return false right away
    if (colors[neighbourNode] === colors[node]) return false;

    // neighbour node is not yet been colored, so call the function again with the opposite color  
    if (colors[neighbourNode] === -1 && 
        !checkBipartiteDFS(neighbourNode, color === 0 ? 1 : 0, colors, adjList)
     ){      
          return false;    
     }  
   }
  
   // the iteration has been done and this node did not break the bipertiteness, so we return true  
   return true;
};

There you have it. I hope it was helpful. Thanks so much for reading.

You can connect with me on LinkedIn.  There, I share programming-related things just like this one as I learn them.