mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Add graph.
This commit is contained in:
parent
840635e613
commit
67cdad8030
@ -8,6 +8,7 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"no-bitwise": "off",
|
"no-bitwise": "off",
|
||||||
"no-lonely-if": "off",
|
"no-lonely-if": "off",
|
||||||
"class-methods-use-this": "off"
|
"class-methods-use-this": "off",
|
||||||
|
"arrow-body-style": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
8. [Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree)
|
8. [Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree)
|
||||||
* [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree)
|
* [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree)
|
||||||
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
|
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
|
||||||
|
9. [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph)
|
||||||
|
|
||||||
## [Algorithms](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms)
|
## [Algorithms](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms)
|
||||||
|
|
||||||
|
88
src/data-structures/graph/Graph.js
Normal file
88
src/data-structures/graph/Graph.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
export default class Graph {
|
||||||
|
/**
|
||||||
|
* @param isDirected {boolean}
|
||||||
|
*/
|
||||||
|
constructor(isDirected = false) {
|
||||||
|
this.vertices = {};
|
||||||
|
this.isDirected = isDirected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param newVertex {GraphVertex}
|
||||||
|
* @returns {Graph}
|
||||||
|
*/
|
||||||
|
addVertex(newVertex) {
|
||||||
|
this.vertices[newVertex.getKey()] = newVertex;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param vertexKey {string}
|
||||||
|
* @returns GraphVertex
|
||||||
|
*/
|
||||||
|
getVertexByKey(vertexKey) {
|
||||||
|
return this.vertices[vertexKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param edge {GraphEdge}
|
||||||
|
* @returns {Graph}
|
||||||
|
*/
|
||||||
|
addEdge(edge) {
|
||||||
|
// Try to find and end start vertices.
|
||||||
|
let startVertex = this.getVertexByKey(edge.startVertex.getKey());
|
||||||
|
let endVertex = this.getVertexByKey(edge.endVertex.getKey());
|
||||||
|
|
||||||
|
// Insert start vertex if it wasn't inserted.
|
||||||
|
if (!startVertex) {
|
||||||
|
this.addVertex(edge.startVertex);
|
||||||
|
startVertex = this.getVertexByKey(edge.startVertex.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert end vertex if it wasn't inserted.
|
||||||
|
if (!endVertex) {
|
||||||
|
this.addVertex(edge.endVertex);
|
||||||
|
endVertex = this.getVertexByKey(edge.endVertex.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO: Check if edge has been already added.
|
||||||
|
|
||||||
|
// Add edge to the vertices.
|
||||||
|
if (this.isDirected) {
|
||||||
|
// If graph IS directed then add the edge only to start vertex.
|
||||||
|
startVertex.addEdge(edge);
|
||||||
|
} else {
|
||||||
|
// If graph ISN'T directed then add the edge to both vertices.
|
||||||
|
startVertex.addEdge(edge);
|
||||||
|
endVertex.addEdge(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param startVertex {GraphVertex}
|
||||||
|
* @param endVertex {GraphVertex}
|
||||||
|
*/
|
||||||
|
findEdge(startVertex, endVertex) {
|
||||||
|
const vertex = this.getVertexByKey(startVertex.getKey());
|
||||||
|
return vertex.findEdge(endVertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param vertexKey {string}
|
||||||
|
* @returns {GraphVertex}
|
||||||
|
*/
|
||||||
|
findVertexByKey(vertexKey) {
|
||||||
|
if (this.vertices[vertexKey]) {
|
||||||
|
return this.vertices[vertexKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return Object.keys(this.vertices).toString();
|
||||||
|
}
|
||||||
|
}
|
12
src/data-structures/graph/GraphEdge.js
Normal file
12
src/data-structures/graph/GraphEdge.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default class GraphEdge {
|
||||||
|
/**
|
||||||
|
* @param startVertex {GraphVertex}
|
||||||
|
* @param endVertex {GraphVertex}
|
||||||
|
* @param weight {number}
|
||||||
|
*/
|
||||||
|
constructor(startVertex, endVertex, weight = 1) {
|
||||||
|
this.startVertex = startVertex;
|
||||||
|
this.endVertex = endVertex;
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
}
|
85
src/data-structures/graph/GraphVertex.js
Normal file
85
src/data-structures/graph/GraphVertex.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import LinkedList from '../linked-list/LinkedList';
|
||||||
|
|
||||||
|
export default class GraphVertex {
|
||||||
|
constructor(value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error('Graph vertex must have a value');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normally you would store string value like vertex name.
|
||||||
|
// But generally it may be any object as well
|
||||||
|
this.value = value;
|
||||||
|
this.edges = new LinkedList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param edge {GraphEdge}
|
||||||
|
* @returns {GraphVertex}
|
||||||
|
*/
|
||||||
|
addEdge(edge) {
|
||||||
|
this.edges.append(edge);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNeighbors() {
|
||||||
|
const edges = this.edges.toArray();
|
||||||
|
|
||||||
|
const neighborsConverter = ({ value }) => {
|
||||||
|
return value.startVertex === this ? value.endVertex : value.startVertex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return either start or end vertex.
|
||||||
|
// For undirected graphs it is possible that current vertex will be the end one.
|
||||||
|
return edges.map(neighborsConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param requiredEdge {GraphEdge}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasEdge(requiredEdge) {
|
||||||
|
const edgeNode = this.edges.find({
|
||||||
|
callback: edge => edge === requiredEdge,
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!edgeNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param vertex {GraphVertex}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasNeighbor(vertex) {
|
||||||
|
const vertexNode = this.edges.find({
|
||||||
|
callback: edge => edge.startVertex === vertex || edge.endVertex === vertex,
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!vertexNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
findEdge(vertex) {
|
||||||
|
const edgeFinder = (edge) => {
|
||||||
|
return edge.startVertex === vertex || edge.endVertex === vertex;
|
||||||
|
};
|
||||||
|
|
||||||
|
const edge = this.edges.find({ callback: edgeFinder });
|
||||||
|
|
||||||
|
return edge ? edge.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getKey() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callback {function}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toString(callback) {
|
||||||
|
return callback ? callback(this.value) : `${this.value}`;
|
||||||
|
}
|
||||||
|
}
|
114
src/data-structures/graph/__test__/Graph.test.js
Normal file
114
src/data-structures/graph/__test__/Graph.test.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import Graph from '../Graph';
|
||||||
|
import GraphVertex from '../GraphVertex';
|
||||||
|
import GraphEdge from '../GraphEdge';
|
||||||
|
|
||||||
|
describe('Graph', () => {
|
||||||
|
it('should add vertices to graph', () => {
|
||||||
|
const graph = new Graph();
|
||||||
|
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
|
||||||
|
graph
|
||||||
|
.addVertex(vertexA)
|
||||||
|
.addVertex(vertexB);
|
||||||
|
|
||||||
|
expect(graph.toString()).toBe('A,B');
|
||||||
|
expect(graph.getVertexByKey(vertexA.getKey())).toEqual(vertexA);
|
||||||
|
expect(graph.getVertexByKey(vertexB.getKey())).toEqual(vertexB);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add edges to undirected graph', () => {
|
||||||
|
const graph = new Graph();
|
||||||
|
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
|
||||||
|
graph.addEdge(edgeAB);
|
||||||
|
|
||||||
|
const graphVertexA = graph.findVertexByKey(vertexA.getKey());
|
||||||
|
const graphVertexB = graph.findVertexByKey(vertexB.getKey());
|
||||||
|
|
||||||
|
expect(graph.toString()).toBe('A,B');
|
||||||
|
expect(graphVertexA).toBeDefined();
|
||||||
|
expect(graphVertexB).toBeDefined();
|
||||||
|
|
||||||
|
expect(graph.findVertexByKey('not existing')).toBeNull();
|
||||||
|
|
||||||
|
expect(graphVertexA.getNeighbors().length).toBe(1);
|
||||||
|
expect(graphVertexA.getNeighbors()[0]).toEqual(vertexB);
|
||||||
|
expect(graphVertexA.getNeighbors()[0]).toEqual(graphVertexB);
|
||||||
|
|
||||||
|
expect(graphVertexB.getNeighbors().length).toBe(1);
|
||||||
|
expect(graphVertexB.getNeighbors()[0]).toEqual(vertexA);
|
||||||
|
expect(graphVertexB.getNeighbors()[0]).toEqual(graphVertexA);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add edges to directed graph', () => {
|
||||||
|
const graph = new Graph(true);
|
||||||
|
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
|
||||||
|
graph.addEdge(edgeAB);
|
||||||
|
|
||||||
|
const graphVertexA = graph.findVertexByKey(vertexA.getKey());
|
||||||
|
const graphVertexB = graph.findVertexByKey(vertexB.getKey());
|
||||||
|
|
||||||
|
expect(graph.toString()).toBe('A,B');
|
||||||
|
expect(graphVertexA).toBeDefined();
|
||||||
|
expect(graphVertexB).toBeDefined();
|
||||||
|
|
||||||
|
expect(graphVertexA.getNeighbors().length).toBe(1);
|
||||||
|
expect(graphVertexA.getNeighbors()[0]).toEqual(vertexB);
|
||||||
|
expect(graphVertexA.getNeighbors()[0]).toEqual(graphVertexB);
|
||||||
|
|
||||||
|
expect(graphVertexB.getNeighbors().length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find edge by vertices in undirected graph', () => {
|
||||||
|
const graph = new Graph();
|
||||||
|
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB, 10);
|
||||||
|
|
||||||
|
graph.addEdge(edgeAB);
|
||||||
|
|
||||||
|
const graphEdgeAB = graph.findEdge(vertexA, vertexB);
|
||||||
|
const graphEdgeBA = graph.findEdge(vertexB, vertexA);
|
||||||
|
const graphEdgeAC = graph.findEdge(vertexB, vertexC);
|
||||||
|
|
||||||
|
expect(graphEdgeAC).toBeNull();
|
||||||
|
expect(graphEdgeAB).toEqual(edgeAB);
|
||||||
|
expect(graphEdgeBA).toEqual(edgeAB);
|
||||||
|
expect(graphEdgeAB.weight).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find edge by vertices in directed graph', () => {
|
||||||
|
const graph = new Graph(true);
|
||||||
|
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB, 10);
|
||||||
|
|
||||||
|
graph.addEdge(edgeAB);
|
||||||
|
|
||||||
|
const graphEdgeAB = graph.findEdge(vertexA, vertexB);
|
||||||
|
const graphEdgeBA = graph.findEdge(vertexB, vertexA);
|
||||||
|
const graphEdgeAC = graph.findEdge(vertexB, vertexC);
|
||||||
|
|
||||||
|
expect(graphEdgeAC).toBeNull();
|
||||||
|
expect(graphEdgeBA).toBeNull();
|
||||||
|
expect(graphEdgeAB).toEqual(edgeAB);
|
||||||
|
expect(graphEdgeAB.weight).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
24
src/data-structures/graph/__test__/GraphEdge.test.js
Normal file
24
src/data-structures/graph/__test__/GraphEdge.test.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import GraphEdge from '../GraphEdge';
|
||||||
|
import GraphVertex from '../GraphVertex';
|
||||||
|
|
||||||
|
describe('GraphEdge', () => {
|
||||||
|
it('should create graph edge with default weight', () => {
|
||||||
|
const startVertex = new GraphVertex('A');
|
||||||
|
const endVertex = new GraphVertex('B');
|
||||||
|
const edge = new GraphEdge(startVertex, endVertex);
|
||||||
|
|
||||||
|
expect(edge.startVertex).toEqual(startVertex);
|
||||||
|
expect(edge.endVertex).toEqual(endVertex);
|
||||||
|
expect(edge.weight).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create graph edge with predefined weight', () => {
|
||||||
|
const startVertex = new GraphVertex('A');
|
||||||
|
const endVertex = new GraphVertex('B');
|
||||||
|
const edge = new GraphEdge(startVertex, endVertex, 10);
|
||||||
|
|
||||||
|
expect(edge.startVertex).toEqual(startVertex);
|
||||||
|
expect(edge.endVertex).toEqual(endVertex);
|
||||||
|
expect(edge.weight).toEqual(10);
|
||||||
|
});
|
||||||
|
});
|
100
src/data-structures/graph/__test__/GraphVertex.test.js
Normal file
100
src/data-structures/graph/__test__/GraphVertex.test.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import GraphVertex from '../GraphVertex';
|
||||||
|
import GraphEdge from '../GraphEdge';
|
||||||
|
|
||||||
|
describe('GraphVertex', () => {
|
||||||
|
it('should throw an error when trying to create vertex without value', () => {
|
||||||
|
let vertex = null;
|
||||||
|
|
||||||
|
function createEmptyVertex() {
|
||||||
|
vertex = new GraphVertex();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(vertex).toBeNull();
|
||||||
|
expect(createEmptyVertex).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create graph vertex', () => {
|
||||||
|
const vertex = new GraphVertex('A');
|
||||||
|
|
||||||
|
expect(vertex).toBeDefined();
|
||||||
|
expect(vertex.value).toBe('A');
|
||||||
|
expect(vertex.toString()).toBe('A');
|
||||||
|
expect(vertex.getKey()).toBe('A');
|
||||||
|
expect(vertex.edges.toString()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add edges to vertex and check if it exists', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('A');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
vertexA.addEdge(edgeAB);
|
||||||
|
|
||||||
|
expect(vertexA.hasEdge(edgeAB)).toBeTruthy();
|
||||||
|
expect(vertexB.hasEdge(edgeAB)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return vertex neighbors in case if current node is start one', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
const edgeAC = new GraphEdge(vertexA, vertexC);
|
||||||
|
vertexA
|
||||||
|
.addEdge(edgeAB)
|
||||||
|
.addEdge(edgeAC);
|
||||||
|
|
||||||
|
expect(vertexB.getNeighbors()).toEqual([]);
|
||||||
|
|
||||||
|
const neighbors = vertexA.getNeighbors();
|
||||||
|
|
||||||
|
expect(neighbors.length).toBe(2);
|
||||||
|
expect(neighbors[0]).toEqual(vertexB);
|
||||||
|
expect(neighbors[1]).toEqual(vertexC);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return vertex neighbors in case if current node is end one', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
|
||||||
|
const edgeBA = new GraphEdge(vertexB, vertexA);
|
||||||
|
const edgeCA = new GraphEdge(vertexC, vertexA);
|
||||||
|
vertexA
|
||||||
|
.addEdge(edgeBA)
|
||||||
|
.addEdge(edgeCA);
|
||||||
|
|
||||||
|
expect(vertexB.getNeighbors()).toEqual([]);
|
||||||
|
|
||||||
|
const neighbors = vertexA.getNeighbors();
|
||||||
|
|
||||||
|
expect(neighbors.length).toBe(2);
|
||||||
|
expect(neighbors[0]).toEqual(vertexB);
|
||||||
|
expect(neighbors[1]).toEqual(vertexC);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check if vertex has specific neighbor', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
vertexA.addEdge(edgeAB);
|
||||||
|
|
||||||
|
expect(vertexA.hasNeighbor(vertexB)).toBeTruthy();
|
||||||
|
expect(vertexA.hasNeighbor(vertexC)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edge by vertex', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
vertexA.addEdge(edgeAB);
|
||||||
|
|
||||||
|
expect(vertexA.findEdge(vertexB)).toEqual(edgeAB);
|
||||||
|
expect(vertexA.findEdge(vertexC)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user