├── .babelrc
├── .eslintrc
├── .gitignore
├── README.md
├── assets
├── andres-iniesta.jpg
├── brand-noise.png
├── courtois.jpg
├── cover-noise.png
├── daniel-alves.jpg
├── danielle-derossi.jpg
├── darker-noise.png
├── david-alaba.jpg
├── david-silva.jpg
├── default-bg.png
├── diego-costa.jpg
├── eden-hazard.jpg
├── favicon.ico
├── fonts
│ ├── Icons.eot
│ ├── Icons.svg
│ ├── Icons.ttf
│ └── Icons.woff
├── gareth-bale.jpg
├── gary-cahill.jpg
├── gerard-pique.jpg
├── harry-kane.jpg
├── hexa-pattern.png
├── illustration-top-white.svg
├── illustration-top.svg
├── james-rodriguez.jpg
├── leo-messi.jpg
├── logotype-black.svg
├── logotype-color.svg
├── logotype-white.svg
├── manuel-neuer.jpg
├── marcelo.jpg
├── mario-gotze.jpg
├── muller.jpg
├── neymar.jpg
├── obi-mikel.jpg
├── pattern-hexas.svg
├── petr-cech.jpg
├── phillip-lahm.jpg
├── ronaldo.jpg
├── sergio-aguero.jpg
├── share-image.png
├── share-image.psd
├── thiago-silva.jpg
├── vincent-kompany.jpg
├── ws-logo-white.svg
├── ws-logo.svg
├── yaya-toure.jpg
└── zlatan-ibrahimovic.jpg
├── client
├── algorithms
│ ├── ArticulationPointSearch.js
│ ├── BipartitenessAlgorithm.js
│ ├── BreadthFirstSearch.js
│ ├── DepthFirstSearch.js
│ └── TarjanStrongConnection.js
├── blog
│ ├── nodes.md
│ ├── references.js
│ ├── section1.js
│ ├── section2.js
│ ├── section3.js
│ ├── section4.js
│ ├── section5.js
│ └── section6.js
├── components
│ ├── BlogHeading.js
│ ├── BlogHeading.styl
│ ├── ContentSection.js
│ ├── ContentSection.styl
│ ├── PlotContainer.js
│ ├── PlotContainer.styl
│ ├── PlotExplanation.js
│ ├── PlotExplanation.styl
│ ├── PlotHeader.js
│ ├── PlotHeader.styl
│ ├── ShareButtons.js
│ └── ShareButtons.styl
├── constants
│ └── ItemTypes.js
├── containers
│ ├── AlgoVizContainer.js
│ ├── AlgoVizContainer.styl
│ ├── AppContainer.js
│ ├── AppContainer.styl
│ ├── BlogContainer.js
│ ├── BlogContainer.styl
│ ├── ContentsContainer.js
│ └── ContentsContainer.styl
├── dataStructures
│ ├── Queue.js
│ └── Stack.js
├── main.js
├── models
│ └── NodeModel.js
└── utils.js
├── fonts
├── icomoon.eot
├── icomoon.svg
├── icomoon.ttf
└── icomoon.woff
├── index.html
├── package.json
├── style
├── icons-selection.json
├── icons.css
├── main.styl
├── pure.css
└── variables.styl
├── webpack.config.js
└── webpack.dev.server.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015" , "stage-0"],
3 | "env": {
4 | "start": {
5 | "presets": ["react-hmre"]
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | // I want to use babel-eslint for parsing!
3 | "parser": "babel-eslint",
4 | "ecmaFeatures": {
5 | "jsx": true,
6 | "classes": true,
7 | "modules": true,
8 | },
9 | "env": {
10 | // I write for browser
11 | "browser": true,
12 | // in CommonJS
13 | "node": true
14 | },
15 | "globals": {
16 | "process": false,
17 | "require": false,
18 | "define": false,
19 | "console": false,
20 | },
21 | // To give you an idea how to override rule options:
22 | "rules": {
23 | "quotes": [2, "single"],
24 | "semi": [2, "always"],
25 | "strict": [2, "never"],
26 | "eol-last": [0],
27 | "no-mixed-requires": [0],
28 | "no-underscore-dangle": [0],
29 | "no-bitwise": 2,
30 | "camelcase": 2,
31 | "eqeqeq": 2,
32 | "wrap-iife": [2, "inside"],
33 | "no-use-before-define": [2, "nofunc"],
34 | "no-caller": 2,
35 | "no-undef": 2,
36 | "new-cap": 2,
37 | "react/jsx-uses-react": 2,
38 | "react/jsx-uses-vars": 2,
39 | "react/react-in-jsx-scope": 2
40 | },
41 | "plugins": [
42 | "react"
43 | ]
44 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **.DS_Store
2 | node_modules
3 | build
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Graph Algorithms Visualised
2 |
3 | Source code for our blog post A visual guide to [Graph Traversal Algorithms](https://workshape.github.io/visual-graph-algorithms/).
4 |
--------------------------------------------------------------------------------
/assets/andres-iniesta.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/andres-iniesta.jpg
--------------------------------------------------------------------------------
/assets/brand-noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/brand-noise.png
--------------------------------------------------------------------------------
/assets/courtois.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/courtois.jpg
--------------------------------------------------------------------------------
/assets/cover-noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/cover-noise.png
--------------------------------------------------------------------------------
/assets/daniel-alves.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/daniel-alves.jpg
--------------------------------------------------------------------------------
/assets/danielle-derossi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/danielle-derossi.jpg
--------------------------------------------------------------------------------
/assets/darker-noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/darker-noise.png
--------------------------------------------------------------------------------
/assets/david-alaba.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/david-alaba.jpg
--------------------------------------------------------------------------------
/assets/david-silva.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/david-silva.jpg
--------------------------------------------------------------------------------
/assets/default-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/default-bg.png
--------------------------------------------------------------------------------
/assets/diego-costa.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/diego-costa.jpg
--------------------------------------------------------------------------------
/assets/eden-hazard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/eden-hazard.jpg
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/favicon.ico
--------------------------------------------------------------------------------
/assets/fonts/Icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/fonts/Icons.eot
--------------------------------------------------------------------------------
/assets/fonts/Icons.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/fonts/Icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/fonts/Icons.ttf
--------------------------------------------------------------------------------
/assets/fonts/Icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/fonts/Icons.woff
--------------------------------------------------------------------------------
/assets/gareth-bale.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/gareth-bale.jpg
--------------------------------------------------------------------------------
/assets/gary-cahill.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/gary-cahill.jpg
--------------------------------------------------------------------------------
/assets/gerard-pique.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/gerard-pique.jpg
--------------------------------------------------------------------------------
/assets/harry-kane.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/harry-kane.jpg
--------------------------------------------------------------------------------
/assets/hexa-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/hexa-pattern.png
--------------------------------------------------------------------------------
/assets/illustration-top-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
137 |
--------------------------------------------------------------------------------
/assets/illustration-top.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
97 |
--------------------------------------------------------------------------------
/assets/james-rodriguez.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/james-rodriguez.jpg
--------------------------------------------------------------------------------
/assets/leo-messi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/leo-messi.jpg
--------------------------------------------------------------------------------
/assets/logotype-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
--------------------------------------------------------------------------------
/assets/logotype-color.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
--------------------------------------------------------------------------------
/assets/logotype-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
--------------------------------------------------------------------------------
/assets/manuel-neuer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/manuel-neuer.jpg
--------------------------------------------------------------------------------
/assets/marcelo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/marcelo.jpg
--------------------------------------------------------------------------------
/assets/mario-gotze.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/mario-gotze.jpg
--------------------------------------------------------------------------------
/assets/muller.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/muller.jpg
--------------------------------------------------------------------------------
/assets/neymar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/neymar.jpg
--------------------------------------------------------------------------------
/assets/obi-mikel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/obi-mikel.jpg
--------------------------------------------------------------------------------
/assets/petr-cech.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/petr-cech.jpg
--------------------------------------------------------------------------------
/assets/phillip-lahm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/phillip-lahm.jpg
--------------------------------------------------------------------------------
/assets/ronaldo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/ronaldo.jpg
--------------------------------------------------------------------------------
/assets/sergio-aguero.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/sergio-aguero.jpg
--------------------------------------------------------------------------------
/assets/share-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/share-image.png
--------------------------------------------------------------------------------
/assets/share-image.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/share-image.psd
--------------------------------------------------------------------------------
/assets/thiago-silva.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/thiago-silva.jpg
--------------------------------------------------------------------------------
/assets/vincent-kompany.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/vincent-kompany.jpg
--------------------------------------------------------------------------------
/assets/ws-logo-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
110 |
--------------------------------------------------------------------------------
/assets/ws-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
110 |
--------------------------------------------------------------------------------
/assets/yaya-toure.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/yaya-toure.jpg
--------------------------------------------------------------------------------
/assets/zlatan-ibrahimovic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshape/graph-algorithms-visualised/aa3cae9b65ce34fe590895ee7ad7490887f965af/assets/zlatan-ibrahimovic.jpg
--------------------------------------------------------------------------------
/client/algorithms/ArticulationPointSearch.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Promise from 'bluebird';
3 |
4 | import { promiseSequence } from '../utils';
5 |
6 | /**
7 | * Depth First Search Algorithm Class
8 | */
9 | export default class ArticulationPointSearch {
10 | /**
11 | * @constructor
12 | *
13 | * @param {Array} nodes An array of NodeModel objects
14 | * @param {React/Component} component A React Component
15 | * @param {Number} pause
16 | */
17 | constructor(nodes, component, pause = 500) {
18 | this.nodes = nodes;
19 | this.component = component;
20 | this.stack = [];
21 | this.visitOrder = [];
22 | this.explanation = [];
23 | this.currentVisitIndex = 0;
24 | this.pause = pause;
25 | }
26 |
27 | /**
28 | * Log a message (used for incremental updates about algorithms progress)
29 | *
30 | * @param {String} text
31 | */
32 | log(text) {
33 | console.log(text);
34 | this.explanation.push(text);
35 | this.component.setState({
36 | explanation: _.assign([],this.explanation)
37 | });
38 | }
39 |
40 | /**
41 | * Start the search process
42 | *
43 | * @param {NodeModel} startNode
44 | *
45 | * @return {Promise}
46 | */
47 | start(startNode) {
48 | if (!this.nodes || this.nodes.length === 0) {
49 | this.log(' | No nodes to search!');
50 | return Promise.reject(new Error('No Nodes to search!'));
51 | }
52 | startNode = startNode || this.nodes[0];
53 |
54 | this.log(` | Start at ${startNode.id}`);
55 | return this.search(startNode)
56 | .then(() => {
57 | this.log(' | Search Complete!');
58 | let unvisitedPoints = this.nodes.filter((n) => !n.visited);
59 | let articulationPoints = this.nodes.filter((n) => n.articulationPoint);
60 | if (unvisitedPoints.length === 0 && articulationPoints.length === 0) {
61 | this.log(' | This graph is Biconnected!');
62 | } else {
63 | this.log(' | This graph is not Biconnected');
64 | if (articulationPoints.length > 0) {
65 | this.log(` | Articulation Points: ${articulationPoints.map((n) => n.id).join(', ')}`);
66 | }
67 | if (unvisitedPoints.length > 0) {
68 | this.log(` | Unvisited Points: ${unvisitedPoints.map((n) => n.id).join(', ')}`);
69 | }
70 | }
71 | });
72 | }
73 |
74 | /**
75 | * Update the component's state with the nodes array that
76 | * reflects current status of search
77 | *
78 | * @param {NodeModel} currentNode
79 | *
80 | * @return {Promise}
81 | */
82 | updateComponent(currentNode, parentNode) {
83 | return Promise.resolve()
84 | .then(() => {
85 | let updateState = this.nodes.map((node) => {
86 | if (node.id === currentNode.id) {
87 | node = currentNode;
88 | node.current = true;
89 | } else {
90 | node.current = false;
91 | }
92 | return node;
93 | });
94 |
95 | this.component.setState({
96 | nodes: updateState,
97 | visitOrder: _.assign([], this.visitOrder)
98 | });
99 |
100 | return Promise.delay(this.pause);
101 | });
102 | }
103 |
104 | /**
105 | * Search a node
106 | *
107 | * - This function triggers an update to the UI
108 | * - If the node has not been visited it marks it as visited
109 | * - If the node has no children or all children have been visited it will move back to parent
110 | * - If the node has children that are still be visited it will move to next available child
111 | *
112 | * @param {NodeModel} node
113 | *
114 | * @return {Promise}
115 | */
116 | search(node, parent) {
117 | this.log(` | Visit ${node.id}`);
118 | node.childVisitCount = 0;
119 | node.visited = true;
120 |
121 | node.visitIndex = this.currentVisitIndex++;
122 | node.lowLink = node.visitIndex;
123 |
124 | return this.updateComponent(node, parent)
125 | .then(() => {
126 | return promiseSequence(node.children.map((c) => () => {
127 | var child = this.nodes.find((n) => n.id === c);
128 | if (!child.visited) {
129 | node.childVisitCount++;
130 | child.visitedFrom = node.id;
131 |
132 | return this.search(child, node)
133 | .then(() => {
134 | node.lowLink = Math.min(node.lowLink, child.lowLink);
135 |
136 | if (node.visitedFrom === null && node.childVisitCount > 1) {
137 | this.log(' | Node is root and has more than one child - it\'s an articulation point!');
138 | node.articulationPoint = true;
139 | } else if (node.visitedFrom !== null && child.lowLink >= node.visitIndex) {
140 | this.log(` | Node ${node.id} - parent ${node.visitedFrom}`);
141 | this.log(' | Node is not root but one of it\'s children has a back link to an ancestor - it\'s an articulation point!');
142 | node.articulationPoint = true;
143 | }
144 | });
145 | } else {
146 | return Promise.resolve()
147 | .then(() => {
148 | if (child.id !== node.visitedFrom) {
149 | node.lowLink = Math.min(node.lowLink, child.visitIndex);
150 | }
151 | });
152 | }
153 | }));
154 | });
155 | }
156 | }
--------------------------------------------------------------------------------
/client/algorithms/BipartitenessAlgorithm.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Promise from 'bluebird';
3 |
4 | import Queue from '../dataStructures/Queue';
5 | import { promiseSequence, promiseWhile } from '../utils';
6 |
7 | /**
8 | * Depth First Search Algorithm Class
9 | */
10 | export default class BipartitenessAlgorithm {
11 | /**
12 | * @constructor
13 | *
14 | * @param {Array} nodes An array of NodeModel objects
15 | * @param {React/Component} component A React Component
16 | * @param {Number} pause
17 | */
18 | constructor(nodes, component, pause = 500) {
19 | this.nodes = nodes;
20 | this.component = component;
21 | this.queue = new Queue();
22 | this.visitOrder = [];
23 | this.explanation = [];
24 | this.pause = pause;
25 | }
26 |
27 | /**
28 | * Log a message (used for incremental updates about algorithms progress)
29 | *
30 | * @param {String} text
31 | */
32 | log(text) {
33 | console.log(text);
34 | this.explanation.push(text);
35 | this.component.setState({
36 | explanation: _.assign([],this.explanation)
37 | });
38 | }
39 |
40 | /**
41 | * Start the search process
42 | *
43 | * @param {NodeModel} startNode
44 | *
45 | * @return {Promise}
46 | */
47 | start(startNode) {
48 | if (!this.nodes || this.nodes.length === 0) {
49 | this.log(' | No nodes to search!');
50 | return Promise.reject(new Error('No Nodes to search!'));
51 | }
52 |
53 | startNode = startNode || this.nodes[0];
54 |
55 | let startNodes = [ startNode ].concat(this.nodes.filter((n) => n.parents.length === 0 && n.id !== startNode.id));
56 |
57 | let searches = startNodes.map((n) => () => {
58 | this.log(` | Start at ${n.id}`);
59 | return this.search(n)
60 | });
61 |
62 | return promiseSequence(searches)
63 | .then(() => {
64 | this.log(' | Queue empty, BFS complete!');
65 | let groupings = _.uniq(this.nodes.map((n) => n.group));
66 | if (groupings.length === 2) {
67 | this.log(' | This graph is a bipartite graph!');
68 | this.log(' | Disjoint Sets')
69 | this.log(` | Set 1: ${this.nodes.filter((n) => n.group === 1).map((n) => n.id).join(', ')}`)
70 | this.log(` | Set 2: ${this.nodes.filter((n) => n.group === 2).map((n) => n.id).join(', ')}`)
71 | } else {
72 | this.log(' | This graph is not a bipartite graph!');
73 | this.log(` | ${groupings.length} groups had to be used`);
74 | }
75 | })
76 | .catch((e) => {
77 | this.log(e);
78 | });
79 | }
80 |
81 | /**
82 | * Update the component's state with the nodes array that
83 | * reflects current status of search
84 | *
85 | * @param {NodeModel} currentNode
86 | *
87 | * @return {Promise}
88 | */
89 | updateComponent(currentNode, parentId) {
90 | return Promise.resolve()
91 | .then(() => {
92 | let updateState = this.nodes.map((node) => {
93 | node.current = (node.id === currentNode.id);
94 | if (parentId && node.id === currentNode.id) {
95 | node.visitedFrom = parentId;
96 | }
97 | return node;
98 | });
99 |
100 | this.component.setState({
101 | nodes: updateState,
102 | visitOrder: _.assign([], this.visitOrder)
103 | });
104 |
105 | return Promise.delay(this.pause);
106 | });
107 | }
108 |
109 | /**
110 | * Search a node
111 | *
112 | * - This function triggers an update to the UI
113 | * - If the node has not been visited it marks it as visited
114 | * - If the node has no children or all children have been visited it will move back to parent
115 | * - If the node has children that are still be visited it will move to next available child
116 | *
117 | * @param {NodeModel} node
118 | *
119 | * @return {Promise}
120 | */
121 | search(node) {
122 | if (!node) {
123 | return;
124 | }
125 |
126 | return this.updateComponent(node)
127 | .then(() => {
128 | if (!node.visited) {
129 | this.log(` | Enqueued ${node.id}`);
130 | this.queue.enqueue([node.id, null]);
131 | }
132 |
133 | return promiseWhile(this.queue.isNotEmpty.bind(this.queue), () => {
134 | let edge = this.queue.dequeue();
135 | let currentNode = this.nodes.find((n) => n.id === edge[0]);
136 | let parentId = null;
137 | if (!currentNode.visited) {
138 | parentId = edge[1];
139 | this.markAsVisited(currentNode);
140 | this.enqueueUnvisitedChildren(currentNode);
141 | }
142 | this.groupNode(currentNode);
143 | return this.updateComponent(currentNode, parentId);
144 | });
145 | });
146 | }
147 |
148 | enqueueUnvisitedChildren(parent) {
149 | let sortedChildren = this.sortChildren(parent);
150 | sortedChildren.forEach((childId) => {
151 | let node = this.nodes.find((n) => n.id === childId);
152 | if (!node.visited) {
153 | this.log(` | Enqueued ${node.id}`);
154 | this.queue.enqueue([node.id, parent.id]);
155 | }
156 | });
157 | }
158 |
159 | groupNode(node) {
160 | if (node.parents.length === 0) {
161 | this.log(` | - set node ${node.id} to group 1`);
162 | node.group = 1;
163 | } else {
164 | let parentGroups = _.sortedUniq(
165 | node.parents
166 | .map((p) => {
167 | let parent = this.nodes.find((n) => n.id === p);
168 | return parent.group;
169 | })
170 | .filter((n) => n !== null)
171 | .sort()
172 | );
173 |
174 | if (parentGroups.length === 0) {
175 | this.log(` | - set node ${node.id} to group 1`);
176 | node.group = 1;
177 | } else if (parentGroups.length === 1) {
178 | let group = (parentGroups[0] === 1) ? 2 : 1;
179 | this.log(` | - set node ${node.id} to group ${group}`);
180 | node.group = group;
181 | } else {
182 | console.log('parentGroups not 1 or 0', parentGroups);
183 | let group = parentGroups[parentGroups.length-1] + 1;
184 | this.log(` | - set node ${node.id} to group ${group}`);
185 | node.group = group;
186 | }
187 | }
188 | }
189 |
190 | /**
191 | * Marks a Node as visited, updates the stack and output order
192 | *
193 | * @param {NodeModel} node
194 | * @param {NodeModel} parent
195 | */
196 | markAsVisited(node, parent) {
197 | this.log(` | Visit ${node.id}`);
198 | node.visited = true;
199 | this.visitOrder.push(node);
200 | }
201 |
202 | /**
203 | * Sorts the children of a NodeModel by natural ordering
204 | *
205 | * @param {NodeModel} node
206 | *
207 | * @return {Array}
208 | */
209 | sortChildren(node) {
210 | return node.children.sort((a,b) => {
211 | if (a < b) return -1;
212 | if (a > b) return 1;
213 | return 0;
214 | });
215 | }
216 | }
--------------------------------------------------------------------------------
/client/algorithms/BreadthFirstSearch.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Promise from 'bluebird';
3 |
4 | import Queue from '../dataStructures/Queue';
5 | import { promiseWhile } from '../utils';
6 |
7 | /**
8 | * Depth First Search Algorithm Class
9 | */
10 | export default class BreadthFirstSearch {
11 | /**
12 | * @constructor
13 | *
14 | * @param {Array} nodes An array of NodeModel objects
15 | * @param {React/Component} component A React Component
16 | * @param {Number} pause
17 | */
18 | constructor(nodes, component, pause = 500) {
19 | this.nodes = nodes;
20 | this.component = component;
21 | this.queue = new Queue();
22 | this.visitOrder = [];
23 | this.explanation = [];
24 | this.pause = pause;
25 | }
26 |
27 | /**
28 | * Log a message (used for incremental updates about algorithms progress)
29 | *
30 | * @param {String} text
31 | */
32 | log(text) {
33 | console.log(text);
34 | this.explanation.push(text);
35 | this.component.setState({
36 | explanation: _.assign([],this.explanation)
37 | });
38 | }
39 |
40 | /**
41 | * Start the search process
42 | *
43 | * @param {NodeModel} startNode
44 | *
45 | * @return {Promise}
46 | */
47 | start(startNode) {
48 | if (!this.nodes || this.nodes.length === 0) {
49 | this.log(' | No nodes to search!');
50 | return Promise.reject(new Error('No Nodes to search!'));
51 | }
52 | startNode = startNode || this.nodes[0];
53 |
54 | this.log(` | Start at ${startNode.id}`);
55 | return this.search(startNode)
56 | .then(() => {
57 | this.log(' | Queue empty, BFS complete!');
58 | this.log(' | Search Steps');
59 | this.log(' | ------------');
60 | this.visitOrder.forEach((node, id) => this.log(` | Step ${id+1} -> Node ${node.id}`) );
61 | })
62 | .catch((e) => {
63 | this.log(e);
64 | });
65 | }
66 |
67 | /**
68 | * Update the component's state with the nodes array that
69 | * reflects current status of search
70 | *
71 | * @param {NodeModel} currentNode
72 | *
73 | * @return {Promise}
74 | */
75 | updateComponent(currentNode, parentId) {
76 | return Promise.resolve()
77 | .then(() => {
78 | let updateState = this.nodes.map((node) => {
79 | node.current = (node.id === currentNode.id);
80 | if (parentId && node.id === currentNode.id) {
81 | node.visitedFrom = parentId;
82 | }
83 | return node;
84 | });
85 |
86 | this.component.setState({
87 | nodes: updateState,
88 | visitOrder: _.assign([], this.visitOrder)
89 | });
90 |
91 | return Promise.delay(this.pause);
92 | });
93 | }
94 |
95 | /**
96 | * Search a node
97 | *
98 | * - This function triggers an update to the UI
99 | * - If the node has not been visited it marks it as visited
100 | * - If the node has no children or all children have been visited it will move back to parent
101 | * - If the node has children that are still be visited it will move to next available child
102 | *
103 | * @param {NodeModel} node
104 | *
105 | * @return {Promise}
106 | */
107 | search(node) {
108 | if (!node) {
109 | return;
110 | }
111 |
112 | return this.updateComponent(node)
113 | .then(() => {
114 | if (!node.visited) {
115 | this.log(` | Enqueued ${node.id}`);
116 | this.queue.enqueue([node.id, null]);
117 | }
118 |
119 | return promiseWhile(this.queue.isNotEmpty.bind(this.queue), () => {
120 | let edge = this.queue.dequeue();
121 | let currentNode = this.nodes.find((n) => n.id === edge[0]);
122 | let parentId = null;
123 | if (!currentNode.visited) {
124 | parentId = edge[1];
125 | this.markAsVisited(currentNode);
126 | this.enqueueUnvisitedChildren(currentNode);
127 | }
128 |
129 | return this.updateComponent(currentNode, parentId);
130 | });
131 | });
132 | }
133 |
134 | enqueueUnvisitedChildren(parent) {
135 | let sortedChildren = this.sortChildren(parent);
136 | sortedChildren.forEach((childId) => {
137 | let node = this.nodes.find((n) => n.id === childId);
138 | if (!node.visited) {
139 | this.log(` | Enqueued ${node.id}`);
140 | this.queue.enqueue([node.id, parent.id]);
141 | }
142 | });
143 | }
144 |
145 | /**
146 | * Marks a Node as visited, updates the stack and output order
147 | *
148 | * @param {NodeModel} node
149 | */
150 | markAsVisited(node) {
151 | this.log(` | Visit ${node.id}`);
152 | node.visited = true;
153 | this.visitOrder.push(node);
154 | }
155 |
156 | /**
157 | * Sorts the children of a NodeModel by natural ordering
158 | *
159 | * @param {NodeModel} node
160 | *
161 | * @return {Array}
162 | */
163 | sortChildren(node) {
164 | return node.children.sort((a,b) => {
165 | if (a < b) return -1;
166 | if (a > b) return 1;
167 | return 0;
168 | });
169 | }
170 | }
--------------------------------------------------------------------------------
/client/algorithms/DepthFirstSearch.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Promise from 'bluebird';
3 |
4 | /**
5 | * Depth First Search Algorithm Class
6 | */
7 | export default class DepthFirstSearch {
8 | /**
9 | * @constructor
10 | *
11 | * @param {Array} nodes An array of NodeModel objects
12 | * @param {React/Component} component A React Component
13 | * @param {Number} pause
14 | */
15 | constructor(nodes, component, pause = 500) {
16 | this.nodes = nodes;
17 | this.component = component;
18 | this.stack = [];
19 | this.visitOrder = [];
20 | this.explanation = [];
21 | this.pause = pause;
22 | }
23 |
24 | /**
25 | * Log a message (used for incremental updates about algorithms progress)
26 | *
27 | * @param {String} text
28 | */
29 | log(text) {
30 | console.log(text);
31 | this.explanation.push(text);
32 | this.component.setState({
33 | explanation: _.assign([],this.explanation)
34 | });
35 | }
36 |
37 | /**
38 | * Start the search process
39 | *
40 | * @param {NodeModel} startNode
41 | *
42 | * @return {Promise}
43 | */
44 | start(startNode) {
45 | if (!this.nodes || this.nodes.length === 0) {
46 | this.log(' | No nodes to search!');
47 | return Promise.reject(new Error('No Nodes to search!'));
48 | }
49 | startNode = startNode || this.nodes[0];
50 |
51 | this.log(` | Start at ${startNode.id}`);
52 | return this.search(startNode)
53 | .catch((e) => {
54 | this.log(e.message);
55 | this.log(' | Search Steps');
56 | this.log(' | ------------');
57 | this.visitOrder.forEach((node, id) => this.log(` | Step ${id+1} -> Node ${node.id}`) );
58 | });
59 | }
60 |
61 | /**
62 | * Update the component's state with the nodes array that
63 | * reflects current status of search
64 | *
65 | * @param {NodeModel} currentNode
66 | *
67 | * @return {Promise}
68 | */
69 | updateComponent(currentNode, parentNode) {
70 | return Promise.resolve()
71 | .then(() => {
72 | let updateState = this.nodes.map((node) => {
73 | if (node.id === currentNode.id) {
74 | node.current = true;
75 | if (parentNode) {
76 | node.visitedFrom = parentNode.id;
77 | }
78 | } else {
79 | node.current = false;
80 | }
81 |
82 | return node;
83 | });
84 | this.component.setState({
85 | nodes: updateState,
86 | visitOrder: _.assign([], this.visitOrder)
87 | });
88 |
89 | return Promise.delay(this.pause);
90 | });
91 | }
92 |
93 | /**
94 | * Search a node
95 | *
96 | * - This function triggers an update to the UI
97 | * - If the node has not been visited it marks it as visited
98 | * - If the node has no children or all children have been visited it will move back to parent
99 | * - If the node has children that are still be visited it will move to next available child
100 | *
101 | * @param {NodeModel} node
102 | *
103 | * @return {Promise}
104 | */
105 | search(node, parent) {
106 | if (parent) {
107 | console.log('Parent set', 'parent is', parent.id, 'child is', node.id);
108 | }
109 | if (!node) {
110 | return;
111 | }
112 |
113 | return this.updateComponent(node, parent)
114 | .then(() => {
115 | if (!node.visited) {
116 | this.markAsVisited(node);
117 | }
118 |
119 | if (!node.hasChildren() || this.allChildrenVisited(node)) {
120 | return this.moveToParent();
121 | } else {
122 | return this.moveToNextChild();
123 | }
124 | });
125 | }
126 | /**
127 | * Returns a node that sits at the top of the stack
128 | *
129 | * @return {NodeModel} [description]
130 | *
131 | * @throws {Error} If the stack is empty
132 | */
133 | peek() {
134 | if (this.stack.length === 0) {
135 | throw new Error(' | Stack empty, DFS complete!');
136 | }
137 |
138 | return this.stack[this.stack.length - 1];
139 | }
140 |
141 | /**
142 | * Moves back to the parent node and triggers a UI update
143 | *
144 | * @return {Promise}
145 | */
146 | moveToParent() {
147 | this.log(' | Move Up!');
148 | this.stack.pop();
149 | let currentNode = this.peek();
150 | this.updateComponent(currentNode);
151 | return this.search(currentNode);
152 | }
153 |
154 | /**
155 | * Moves to the next available child
156 | *
157 | * @return {Promise}
158 | */
159 | moveToNextChild() {
160 | this.log(' | Move Down!');
161 | let parent = this.peek();
162 | let sortedChildren = this.sortChildren(parent);
163 |
164 | let unvisitedChildren = sortedChildren.filter((x) => {
165 | let node = this.nodes.find((n) => n.id === x);
166 | return !node.visited;
167 | });
168 |
169 | if (unvisitedChildren.length === 0) {
170 | return Promise.resolve();
171 | } else {
172 | let nextId = unvisitedChildren.pop();
173 | let next = this.nodes.find((x) => x.id === nextId);
174 | return this.search(next, parent);
175 | }
176 | }
177 |
178 | /**
179 | * Marks a Node as visited, updates the stack and output order
180 | *
181 | * @param {NodeModel} node
182 | */
183 | markAsVisited(node) {
184 | this.log(` | Visit ${node.id}`);
185 | node.visited = true;
186 | this.stack.push(node);
187 | this.visitOrder.push(node);
188 | }
189 |
190 | /**
191 | * Sorts the children of a NodeModel by natural ordering
192 | *
193 | * @param {NodeModel} node
194 | *
195 | * @return {Array}
196 | */
197 | sortChildren(node) {
198 | return node.children.sort((a,b) => {
199 | if (a > b) return -1;
200 | if (a < b) return 1;
201 | return 0;
202 | });
203 | }
204 |
205 | /**
206 | * Checks to see if all children of a NodeModel has been visited or not
207 | *
208 | * @param {NodeModel} node
209 | *
210 | * @return {Boolean}
211 | */
212 | allChildrenVisited(node) {
213 | let output = node.children.reduce((prev, current) => {
214 | let currentNode = this.nodes.find((x) => x.id === current);
215 | return prev && currentNode.visited;
216 | }, true);
217 | return output;
218 | }
219 | }
--------------------------------------------------------------------------------
/client/algorithms/TarjanStrongConnection.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Promise from 'bluebird';
3 |
4 | import { promiseSequence } from '../utils';
5 | import Stack from '../dataStructures/Stack';
6 |
7 | /**
8 | * Tarjan Strongly Connected Components Graph Algorithm Class
9 | */
10 | export default class TarjanStrongConnection {
11 | /**
12 | * @constructor
13 | *
14 | * @param {Array} nodes An array of NodeModel objects
15 | * @param {React/Component} component A React Component
16 | * @param {Number} pause
17 | */
18 | constructor(nodes, component, pause = 500) {
19 | this.nodes = nodes;
20 | this.component = component;
21 | this.visitIndex = 0;
22 | this.stack = new Stack();
23 | this.visitOrder = [];
24 | this.explanation = [];
25 | this.components = [];
26 | this.pause = pause;
27 | }
28 |
29 | /**
30 | * Log a message (used for incremental updates about algorithms progress)
31 | *
32 | * @param {String} text
33 | */
34 | log(text) {
35 | console.log(text);
36 | this.explanation.push(text);
37 | this.component.setState({
38 | explanation: _.assign([],this.explanation)
39 | });
40 | }
41 |
42 | /**
43 | * Start the search process
44 | *
45 | * @param {NodeModel} startNode
46 | *
47 | * @return {Promise}
48 | */
49 | start(startNode) {
50 | if (!this.nodes || this.nodes.length === 0) {
51 | this.log(' | No nodes to search!');
52 | return Promise.reject(new Error('No Nodes to search!'));
53 | }
54 |
55 | let searchNodes = this.nodes.map((node) => () => this.search(node));
56 |
57 | return promiseSequence(searchNodes)
58 | .then(() => {
59 | this.log(' | Strongly Connected Components');
60 | this.log(' | ------------');
61 | this.components.forEach((component, id) => this.log(` | Component ${id} -> ${component.join(', ')}`) );
62 | this.component.setState({
63 | components: _.assign([], this.components)
64 | });
65 | });
66 | }
67 |
68 | /**
69 | * Update the component's state with the nodes array that
70 | * reflects current status of search
71 | *
72 | * @param {NodeModel} currentNode
73 | *
74 | * @return {Promise}
75 | */
76 | updateComponent(currentNode, parentNode) {
77 | return Promise.resolve()
78 | .then(() => {
79 | let updateState = this.nodes.map((node) => {
80 | if (node.id === currentNode.id) {
81 | node.current = true;
82 | if (parentNode) {
83 | node.visitedFrom = parentNode.id;
84 | }
85 | } else {
86 | node.current = false;
87 | }
88 |
89 | return node;
90 | });
91 | this.component.setState({
92 | nodes: updateState,
93 | components: _.assign([], this.components),
94 | visitOrder: _.assign([], this.visitOrder)
95 | });
96 |
97 | return Promise.delay(this.pause);
98 | });
99 | }
100 |
101 | /**
102 | * Search a node
103 | *
104 | * - This function triggers an update to the UI
105 | * - If the node has not been visited it marks it as visited
106 | * - If the node has no children or all children have been visited it will move back to parent
107 | * - If the node has children that are still be visited it will move to next available child
108 | *
109 | * @param {NodeModel} node
110 | *
111 | * @return {Promise}
112 | */
113 | search(node, parent) {
114 | if (!node) {
115 | return Promise.resolve();
116 | }
117 |
118 | if (node.visitIndex !== null) {
119 | return Promise.resolve();
120 | }
121 |
122 | return this.updateComponent(node, parent)
123 | .then(() => {
124 | this.markAsVisited(node);
125 |
126 | let childTasks = node.children.map((childId) => {
127 | let child = this.nodes.find((x) => x.id === childId);
128 | if (child.visitIndex === null) {
129 | return () => {
130 | return this.search(child, node).then(() => {
131 | let updatedChild = this.nodes.find((x) => x.id === childId);
132 | node.lowLink = Math.min(node.lowLink, updatedChild.lowLink);
133 | return Promise.resolve();
134 | });
135 | };
136 | } else if (child.onStack) {
137 | return () => {
138 | node.lowLink = Math.min(node.lowLink, child.visitIndex);
139 | return Promise.resolve();
140 | };
141 | } else {
142 | return () => {
143 | return Promise.resolve();
144 | };
145 | }
146 | });
147 |
148 | return promiseSequence(childTasks)
149 | .then(() => {
150 | if (node.lowLink === node.visitIndex) {
151 | let component = [ node.id ];
152 | let otherNodeId = this.stack.pop();
153 | let otherNode = this.nodes.find((x) => x.id === otherNodeId);
154 | otherNode.onStack = false;
155 | while (otherNodeId !== node.id) {
156 | component.push(otherNode.id);
157 | otherNodeId = this.stack.pop();
158 | otherNode = this.nodes.find((x) => x.id === otherNodeId);
159 | otherNode.onStack = false;
160 | }
161 | this.components.push(component);
162 | return Promise.resolve();
163 | }
164 | });
165 | });
166 | }
167 |
168 | /**
169 | * Marks a Node as visited, updates the stack and output order
170 | *
171 | * @param {NodeModel} node
172 | */
173 | markAsVisited(node) {
174 | this.log(` | Visit ${node.id}`);
175 | node.visited = true;
176 | node.visitIndex = this.visitIndex;
177 | node.lowLink = this.visitIndex;
178 | node.onStack = true;
179 | this.visitIndex++;
180 | this.stack.push(node.id);
181 | this.visitOrder.push(node);
182 | }
183 | }
--------------------------------------------------------------------------------
/client/blog/nodes.md:
--------------------------------------------------------------------------------
1 | Ingredients:
2 | - variety of teaching oneself engineering
3 | - veriety of ways of actually engineering stuff - front end/back-end/hardware
4 |
5 | One of the things I noticed whilst reading these pieces, and their comment streams, was that a
6 | sizeable proportion of developers felt frustration when they discovered in an interview that were to be quizzed
7 | on Computer Science 101.
8 |
9 |
10 | Needs to represent the dynamic nature of the state of Graph Search
--------------------------------------------------------------------------------
/client/blog/references.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ShareButtons from '../components/ShareButtons';
3 |
4 | export default (
5 |
The real world uses of testing whether a graph is bipartite, but it is a good introduction to the topic of
6 | Graph Coloring which is an interesting area of study.
This algorithm, because it is an extension of BFS, uses a queue to maintain which node to visit next. For each
10 | node it assigns a color based upon the colour(s) assigned to it's parents. Once all nodes have been assigned a color
11 | if we end with using 2 colours, then the graph is bipartite and its nodes can be seperated into two disjoint sets.
It's quite safe to say that there is a lot of pent up frustration towards the hiring process in the software
47 | industry today [1
48 | , 2
49 | , 3
50 | , 4
51 | ]. With the plethora of pathways one can take nowadays to become a software developer
52 | combined with how one can choose to apply themselves there is increasing chance for ambiguity when it comes to how
53 | to access the suitability of a candidate for a job.
54 |
55 |
One of the topics that is often raised in these pieces is around some of the foundations of Computer Science -
56 | namely Data Structures and Algorithms. Whilst I am conflicted on their value in technical interviews
57 | I found myself wondering about how we could help people that struggle with these concepts - I'd put myself in this pool.
58 | In my opinion the delivery mechanisms used for educating someone about these topics are outdated and could be improved
59 | (I always used to grow frustrated how lecture slides would often rely upon singular examples to explain algorithms).
60 | Perhaps with a bit more thought to how to improve the learning experience we can help more people understand these basic concepts
61 | that are essentially the building blocks for the subject of Computer Science.
62 |
63 |
So I decided to create something that explains some of these concepts. The target requirements I felt were
64 | important to maximise value and minimise complexity were the following:
65 |
66 |
67 |
Visual - visually show the steps of the algorithm and how it traverses the graph
68 |
Interactive - user can easily, and intuitively draw their own graphs and trees and run an algorithm
69 | against it
70 |
Examples - Access to pre-defined graphs relevant to each algorithm
71 |
Running Commentary - Display the output of the algorithm at it runs
72 |
Minimal - users can just click run to see the algorithms run and their output
73 |
74 |
75 |
This blog marks the beginning. I've decided to focus on the topic of graph search algorithms here. The hope is
76 | that anyone that is struggling with these algorithms and their applications can gain a better understanding
77 | through running the algorithm and its variations with their own graphs and trees.
78 |
79 |
By enabling readers to test out an algorithm with their own graph/tree structures I believe the reader can
80 | learn and grok the concepts more efficiently. In many ways this reflects the intentions
81 | of Workshape, in so far as, we try to limit the dependence of words when
82 | transferring information in order to minimise ambiguity!
83 |
84 |
It must be said that there are already some great resources out there for teaching people about these types of complex subject
85 | matter with visual cues. I have taken inspiration from two in particular:
86 |
87 |
88 |
Visualgo - a very comprehensive resource for the visualisation
89 | of algorithms and data structures. Kudos to the team behind this.
90 |
A Visual Introduction to
91 | Machine Learning - a personal favourite that tells the story of how various statistical techniques
92 | can be used to build models to make predictions to classifcation problems - the visual explanation is
93 | triggered by scrolling!
For those who want to take a look at the source code,
98 | check it out on our Github. It is written using React, D3 and Bluebird (Promise implementation).
If you have any feedback and would like to share it, or if you see any errors please email Gordon at gordon [at] workshape [dot] io. Also, if you would be interested in
111 | contributing in further content like this please do get in touch!
112 |
113 |
114 |
115 | Made with
116 |
117 |
118 | by
119 |
120 |
121 |
122 |
Graph Traversal Algorithms are a key topic of study in Computer Science and lay a foundation for
6 | Artificial Intelligence. The various algorithms in this field are used to perform search upon
7 | graph or tree data structures and we unknowingly rely upon these techniques on a daily basis with
8 | our electronically augmented lives.
9 |
10 |
Whilst an appreciation of how they work may be fresh on the minds of recent Comp Sci graduates it
11 | may be a mystery to others. Considering this subject frequently seems to come up in interview processes
12 | for various software engineers roles we thought it would be a good idea to experiment on building a resource
13 | to help anyone understand how some of these algorithms work.
14 |
15 |
In this blog we look at Depth First Search plus Breadth First Search. This resource is interactive and
16 | readers can use the visualisations to see how the algorithms can be applied to search
17 | graphs and solve certain problems. You can run it against various examples or they can draw your own graphs.
18 |
19 |
20 |
Creating your own graph
21 |
You can draw a new node by clicking anywhere on the visualisation canvas. To add an edge
22 | between nodes you have to drag your mouse from one node to another.
When it comes to algorithms Depth First Search
7 | (DFS) is one of the first things students will be taught at university and it is a gateway for many other important
8 | topics in Computer Science. It is an algorithm for searching or traversing Graph and Tree data structures just like
9 | it's sibling Breadth First Search (BFS).
10 |
11 |
If you run the visualisation below you can see how it works - you can run it on example graphs, or alternatively
12 | draw your own - add nodes by clicking on the canvas below and add edges by dragging from one node to another.
You can see in the visualisation after the algorithm completes the edges that the algorithm traversed in order to first visit a node.
5 | It is important to take note of this when comparing to Breadth First Search. The visit index of each node is labelled in red.
This pseudocode encapsulates the main principle of DFS using a stack and recursive function calls to explore down a pathway to a leaf node before
9 | backtracking, using a stack, and looking for other routes to other unvisited children.
Whilst DFS goes as deep as possible until it reaches a dead end and then back tracks BFS queues up all nodes reachable at a certain depth
34 | and then increases the depth until all nodes are visited. Check out the visualisation below, the same example graph is loaded, pay attention
35 | to the different visit order and edges traversed by each algorithm.
Because the visualisation highlights the edges that are traversed by BFS you can see that it traverses the shortest path to each node - this property of
5 | BFS makes it a good approach for this exact purpose.
This pseudocode encapsulates the main principle of BFS using a queue to store nodes that are ready to be visited
9 | process and recursive function calls visit all reachable nodes.
If you have run both algorithms on the same structure you can make a few observations:
28 |
29 |
The nodes of the graph are visited in a different order (if the depth is greater than 2)
30 |
If there are multiple edges to a child node, the algorithms will traverse different edges
31 |
BFS will always traverse the shortest path (based upon intermediate nodes visited) between two nodes
32 |
Each algorithm will only traverse nodes reachable from the start node, disconnected components are not traversed. You can change the start node to see how this effects what is discovered.
The DFS algorithm by itself does not seem immediately useful, in comparison to BFS which is capable of finding shortest paths in nodes. In order to appreciate
38 | its usefulness you have to look to some of the ways in which it can be extended to be applied to certain problem domains. In this blog I am going to demonstrate
39 | two examples, finding strongly connected components, and identifying whether a graph is biconnected or not.
A Strongly Connected Component (SCC) is a subgraph where all nodes are reachable from every other node in the group. Robert Tarjan, a
44 | Professor of Computer Science from the US, created an algorithm for identifying SCCs in linear time, O(N), that is based upon DFS. If you take a look at the visualisation below you
45 | can see algorithm identify the SCCs in a graph.
This algorithm builds upon DFS using a stack and recursion to explore a pathway until it finds a leaf node or
15 | a node already visisted. Unlike DFS though this algorithm does not take a start point, instead it ensures all
16 | nodes are visited by instigating search on all nodes. At the end of each search cycle if the value of
17 | node.visitIndex is the same as node.lowLink then it creates a new SCC and iteratively pops from the stack and adds
18 | these nodes to the SCC too until it reaches itself again.
A graph is biconnected if any node can be
58 | removed and yet the graph remains connected (all nodes can still be reached from all other nodes). The key to
59 | identifying is a graph is biconnected or not, is by searching for the presence of articulation points, or cut
60 | vertices. These are points, that if removed from a graph results in an increase in the number of connected components
61 | within a graph and basically means that without their presence all remaining nodes would not be reachable from one
62 | another.
63 |
64 |
The most basic way to search for articulation points in a graph is to take a brute force approach and play out
65 | all scenerios. Each node is removed from the graph and we then see if remaining nodes are all connected, if they
66 | are not then the node that was removed is an articulation point. We place the removed node back in and then repeat
67 | the process for all remaining nodes.
68 |
69 |
Whilst this approach will works its time complexity is relatively inefficient at O(N^2). There is an
70 | alternative though that is an extension of DFS that's time complexity is O(N). This algorithm is demonstrated
71 | in the visualisation below.
The major real world use of identifying if a graph is biconnected or not is about identifying whether a graph
5 | has redundancy in place if a node fails. This is especially important in communication network design and travel
6 | networks where it ensures resilience to one node failing.
Similarly to Tarjan's Strongly Connected Components Algorithm we maintain two important values for each node when traversing
10 | the graph, lowLink and visitIndex with the additional property of the number of children visited. There are two
11 | specific situations that indicate whether a node is an articulation point:
12 |
13 |
14 |
The node is a the root of the DFS search tree and two or more of its children are visisted from it.
15 |
The node is not a root but the lowLink value of ones of its children is more than its on visit
16 | index.
17 |
18 |
19 |
We do need to maintain a stack in this algorithm but we do make use of recursive function calls to traverse
20 | down a path of the graph.
As mentioned earlier BFS has usefulness out of the box as it can identify the shortest path between two nodes
55 | in an unweighted graph. You can see this behaviour by running the BFS example. In this blog we'll explore one
56 | other application of BFS, using it to test if a graph is bipartite or not.
A graph is bipartite if its nodes can be put into two disjoint sets such that every node in the first set
60 | connects to one or more in the second. If you check out the visualisation below you can see a variation of BFS
61 | that has been applied to see if a graph can be broken into these two disjoint sets. When a node is visited it is
62 | coloured according the colour assigned to its parents:
63 |
64 |
65 |
it it has no parents, it'll be set to color 1
66 |
if all it's parents have the same color, it will be set to the alternate of that color
67 |
if it's parents have differing colors, it will be set a new color
68 |
69 |
70 |
At the end of the process if only 2 colors are used then the graph is bipartite.