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": {
|
||||
"no-bitwise": "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)
|
||||
* [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)
|
||||
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)
|
||||
|
||||
|
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