parent
1f7d9440b2
commit
cfe644f4f9
@ -1,979 +0,0 @@
|
||||
class GraphEdge {
|
||||
/**
|
||||
* @param {GraphVertex} startVertex
|
||||
* @param {GraphVertex} endVertex
|
||||
* @param {number} [weight=1]
|
||||
*/
|
||||
constructor(startVertex, endVertex, weight = 0) {
|
||||
this.startVertex = startVertex;
|
||||
this.endVertex = endVertex;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
getKey() {
|
||||
const startVertexKey = this.startVertex.getKey();
|
||||
const endVertexKey = this.endVertex.getKey();
|
||||
|
||||
return `${startVertexKey}_${endVertexKey}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {GraphEdge}
|
||||
*/
|
||||
reverse() {
|
||||
const tmp = this.startVertex;
|
||||
this.startVertex = this.endVertex;
|
||||
this.endVertex = tmp;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
toString() {
|
||||
return this.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
class Comparator {
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {function(a: *, b: *)} [compareFunction] - It may be custom compare function that, let's
|
||||
* say may compare custom objects together.
|
||||
*/
|
||||
constructor(compareFunction) {
|
||||
this.compare = compareFunction || Comparator.defaultCompareFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default comparison function. It just assumes that "a" and "b" are strings or numbers.
|
||||
* @param {(string|number)} a
|
||||
* @param {(string|number)} b
|
||||
* @returns {number}
|
||||
*/
|
||||
static defaultCompareFunction(a, b) {
|
||||
if (a === b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two variables are equal.
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
equal(a, b) {
|
||||
return this.compare(a, b) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if variable "a" is less than "b".
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
lessThan(a, b) {
|
||||
return this.compare(a, b) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if variable "a" is greater than "b".
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
greaterThan(a, b) {
|
||||
return this.compare(a, b) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if variable "a" is less than or equal to "b".
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
lessThanOrEqual(a, b) {
|
||||
return this.lessThan(a, b) || this.equal(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if variable "a" is greater than or equal to "b".
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
greaterThanOrEqual(a, b) {
|
||||
return this.greaterThan(a, b) || this.equal(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the comparison order.
|
||||
*/
|
||||
reverse() {
|
||||
const compareOriginal = this.compare;
|
||||
this.compare = (a, b) => compareOriginal(b, a);
|
||||
}
|
||||
}
|
||||
|
||||
class LinkedListNode {
|
||||
constructor(value, next = null) {
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
toString(callback) {
|
||||
return callback ? callback(this.value) : `${this.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
class LinkedList {
|
||||
/**
|
||||
* @param {Function} [comparatorFunction]
|
||||
*/
|
||||
constructor(comparatorFunction) {
|
||||
/** @var LinkedListNode */
|
||||
this.head = null;
|
||||
|
||||
/** @var LinkedListNode */
|
||||
this.tail = null;
|
||||
|
||||
this.compare = new Comparator(comparatorFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} value
|
||||
* @return {LinkedList}
|
||||
*/
|
||||
prepend(value) {
|
||||
// Make new node to be a head.
|
||||
const newNode = new LinkedListNode(value, this.head);
|
||||
this.head = newNode;
|
||||
|
||||
// If there is no tail yet let's make new node a tail.
|
||||
if (!this.tail) {
|
||||
this.tail = newNode;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} value
|
||||
* @return {LinkedList}
|
||||
*/
|
||||
append(value) {
|
||||
const newNode = new LinkedListNode(value);
|
||||
|
||||
// If there is no head yet let's make new node a head.
|
||||
if (!this.head) {
|
||||
this.head = newNode;
|
||||
this.tail = newNode;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Attach new node to the end of linked list.
|
||||
this.tail.next = newNode;
|
||||
this.tail = newNode;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} value
|
||||
* @param {number} index
|
||||
* @return {LinkedList}
|
||||
*/
|
||||
insert(value, rawIndex) {
|
||||
const index = rawIndex < 0 ? 0 : rawIndex;
|
||||
if (index === 0) {
|
||||
this.prepend(value);
|
||||
} else {
|
||||
let count = 1;
|
||||
let currentNode = this.head;
|
||||
const newNode = new LinkedListNode(value);
|
||||
while (currentNode) {
|
||||
if (count === index) break;
|
||||
currentNode = currentNode.next;
|
||||
count += 1;
|
||||
}
|
||||
if (currentNode) {
|
||||
newNode.next = currentNode.next;
|
||||
currentNode.next = newNode;
|
||||
} else {
|
||||
if (this.tail) {
|
||||
this.tail.next = newNode;
|
||||
this.tail = newNode;
|
||||
} else {
|
||||
this.head = newNode;
|
||||
this.tail = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} value
|
||||
* @return {LinkedListNode}
|
||||
*/
|
||||
delete(value) {
|
||||
if (!this.head) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let deletedNode = null;
|
||||
|
||||
// If the head must be deleted then make next node that is different
|
||||
// from the head to be a new head.
|
||||
while (this.head && this.compare.equal(this.head.value, value)) {
|
||||
deletedNode = this.head;
|
||||
this.head = this.head.next;
|
||||
}
|
||||
|
||||
let currentNode = this.head;
|
||||
|
||||
if (currentNode !== null) {
|
||||
// If next node must be deleted then make next node to be a next next one.
|
||||
while (currentNode.next) {
|
||||
if (this.compare.equal(currentNode.next.value, value)) {
|
||||
deletedNode = currentNode.next;
|
||||
currentNode.next = currentNode.next.next;
|
||||
} else {
|
||||
currentNode = currentNode.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if tail must be deleted.
|
||||
if (this.compare.equal(this.tail.value, value)) {
|
||||
this.tail = currentNode;
|
||||
}
|
||||
|
||||
return deletedNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} findParams
|
||||
* @param {*} findParams.value
|
||||
* @param {function} [findParams.callback]
|
||||
* @return {LinkedListNode}
|
||||
*/
|
||||
find({
|
||||
value = undefined,
|
||||
callback = undefined
|
||||
}) {
|
||||
if (!this.head) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let currentNode = this.head;
|
||||
|
||||
while (currentNode) {
|
||||
// If callback is specified then try to find node by callback.
|
||||
if (callback && callback(currentNode.value)) {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
// If value is specified then try to compare by value..
|
||||
if (value !== undefined && this.compare.equal(currentNode.value, value)) {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
currentNode = currentNode.next;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {LinkedListNode}
|
||||
*/
|
||||
deleteTail() {
|
||||
const deletedTail = this.tail;
|
||||
|
||||
if (this.head === this.tail) {
|
||||
// There is only one node in linked list.
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
|
||||
return deletedTail;
|
||||
}
|
||||
|
||||
// If there are many nodes in linked list...
|
||||
|
||||
// Rewind to the last node and delete "next" link for the node before the last one.
|
||||
let currentNode = this.head;
|
||||
while (currentNode.next) {
|
||||
if (!currentNode.next.next) {
|
||||
currentNode.next = null;
|
||||
} else {
|
||||
currentNode = currentNode.next;
|
||||
}
|
||||
}
|
||||
|
||||
this.tail = currentNode;
|
||||
|
||||
return deletedTail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {LinkedListNode}
|
||||
*/
|
||||
deleteHead() {
|
||||
if (!this.head) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const deletedHead = this.head;
|
||||
|
||||
if (this.head.next) {
|
||||
this.head = this.head.next;
|
||||
} else {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
}
|
||||
|
||||
return deletedHead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*[]} values - Array of values that need to be converted to linked list.
|
||||
* @return {LinkedList}
|
||||
*/
|
||||
fromArray(values) {
|
||||
values.forEach((value) => this.append(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {LinkedListNode[]}
|
||||
*/
|
||||
toArray() {
|
||||
const nodes = [];
|
||||
|
||||
let currentNode = this.head;
|
||||
while (currentNode) {
|
||||
nodes.push(currentNode);
|
||||
currentNode = currentNode.next;
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} [callback]
|
||||
* @return {string}
|
||||
*/
|
||||
toString(callback) {
|
||||
return this.toArray().map((node) => node.toString(callback)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a linked list.
|
||||
* @returns {LinkedList}
|
||||
*/
|
||||
reverse() {
|
||||
let currNode = this.head;
|
||||
let prevNode = null;
|
||||
let nextNode = null;
|
||||
|
||||
while (currNode) {
|
||||
// Store next node.
|
||||
nextNode = currNode.next;
|
||||
|
||||
// Change next node of the current node so it would link to previous node.
|
||||
currNode.next = prevNode;
|
||||
|
||||
// Move prevNode and currNode nodes one step forward.
|
||||
prevNode = currNode;
|
||||
currNode = nextNode;
|
||||
}
|
||||
|
||||
// Reset head and tail.
|
||||
this.tail = this.head;
|
||||
this.head = prevNode;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class GraphVertex {
|
||||
/**
|
||||
* @param {*} value
|
||||
*/
|
||||
constructor(value) {
|
||||
if (value === undefined) {
|
||||
throw new Error('Graph vertex must have a value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphEdge} edgeA
|
||||
* @param {GraphEdge} edgeB
|
||||
*/
|
||||
const edgeComparator = (edgeA, edgeB) => {
|
||||
if (edgeA.getKey() === edgeB.getKey()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return edgeA.getKey() < edgeB.getKey() ? -1 : 1;
|
||||
};
|
||||
|
||||
// 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(edgeComparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphEdge} edge
|
||||
* @returns {GraphVertex}
|
||||
*/
|
||||
addEdge(edge) {
|
||||
this.edges.append(edge);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphEdge} edge
|
||||
*/
|
||||
deleteEdge(edge) {
|
||||
this.edges.delete(edge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {GraphVertex[]}
|
||||
*/
|
||||
getNeighbors() {
|
||||
const edges = this.edges.toArray();
|
||||
|
||||
/** @param {LinkedListNode} node */
|
||||
const neighborsConverter = (node) => {
|
||||
return node.value.startVertex === this ? node.value.endVertex : node.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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {GraphEdge[]}
|
||||
*/
|
||||
getEdges() {
|
||||
return this.edges.toArray().map((linkedListNode) => linkedListNode.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
getDegree() {
|
||||
return this.edges.toArray().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphEdge} requiredEdge
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasEdge(requiredEdge) {
|
||||
const edgeNode = this.edges.find({
|
||||
callback: (edge) => edge === requiredEdge,
|
||||
});
|
||||
|
||||
return !!edgeNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphVertex} vertex
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasNeighbor(vertex) {
|
||||
const vertexNode = this.edges.find({
|
||||
callback: (edge) => edge.startVertex === vertex || edge.endVertex === vertex,
|
||||
});
|
||||
|
||||
return !!vertexNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphVertex} vertex
|
||||
* @returns {(GraphEdge|null)}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {GraphVertex}
|
||||
*/
|
||||
deleteAllEdges() {
|
||||
this.getEdges().forEach((edge) => this.deleteEdge(edge));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} [callback]
|
||||
* @returns {string}
|
||||
*/
|
||||
toString(callback) {
|
||||
return callback ? callback(this.value) : `${this.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
class Graph {
|
||||
/**
|
||||
* @param {boolean} isDirected
|
||||
*/
|
||||
constructor(isDirected = false) {
|
||||
this.vertices = {};
|
||||
this.edges = {};
|
||||
this.isDirected = isDirected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphVertex} newVertex
|
||||
* @returns {Graph}
|
||||
*/
|
||||
addVertex(newVertex) {
|
||||
this.vertices[newVertex.getKey()] = newVertex;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} vertexKey
|
||||
* @returns GraphVertex
|
||||
*/
|
||||
getVertexByKey(vertexKey) {
|
||||
return this.vertices[vertexKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphVertex} vertex
|
||||
* @returns {GraphVertex[]}
|
||||
*/
|
||||
getNeighbors(vertex) {
|
||||
return vertex.getNeighbors();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {GraphVertex[]}
|
||||
*/
|
||||
getAllVertices() {
|
||||
return Object.values(this.vertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {GraphEdge[]}
|
||||
*/
|
||||
getAllEdges() {
|
||||
return Object.values(this.edges);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphEdge} edge
|
||||
* @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());
|
||||
}
|
||||
|
||||
// Check if edge has been already added.
|
||||
if (this.edges[edge.getKey()]) {
|
||||
throw new Error('Edge has already been added before');
|
||||
} else {
|
||||
this.edges[edge.getKey()] = edge;
|
||||
}
|
||||
|
||||
// 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 {GraphEdge} edge
|
||||
*/
|
||||
deleteEdge(edge) {
|
||||
// Delete edge from the list of edges.
|
||||
if (this.edges[edge.getKey()]) {
|
||||
delete this.edges[edge.getKey()];
|
||||
} else {
|
||||
throw new Error('Edge not found in graph');
|
||||
}
|
||||
|
||||
// Try to find and end start vertices and delete edge from them.
|
||||
const startVertex = this.getVertexByKey(edge.startVertex.getKey());
|
||||
const endVertex = this.getVertexByKey(edge.endVertex.getKey());
|
||||
|
||||
startVertex.deleteEdge(edge);
|
||||
endVertex.deleteEdge(edge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GraphVertex} startVertex
|
||||
* @param {GraphVertex} endVertex
|
||||
* @return {(GraphEdge|null)}
|
||||
*/
|
||||
findEdge(startVertex, endVertex) {
|
||||
const vertex = this.getVertexByKey(startVertex.getKey());
|
||||
|
||||
if (!vertex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return vertex.findEdge(endVertex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
getWeight() {
|
||||
return this.getAllEdges().reduce((weight, graphEdge) => {
|
||||
return weight + graphEdge.weight;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse all the edges in directed graph.
|
||||
* @return {Graph}
|
||||
*/
|
||||
reverse() {
|
||||
/** @param {GraphEdge} edge */
|
||||
this.getAllEdges().forEach((edge) => {
|
||||
// Delete straight edge from graph and from vertices.
|
||||
this.deleteEdge(edge);
|
||||
|
||||
// Reverse the edge.
|
||||
edge.reverse();
|
||||
|
||||
// Add reversed edge back to the graph and its vertices.
|
||||
this.addEdge(edge);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {object}
|
||||
*/
|
||||
getVerticesIndices() {
|
||||
const verticesIndices = {};
|
||||
this.getAllVertices().forEach((vertex, index) => {
|
||||
verticesIndices[vertex.getKey()] = index;
|
||||
});
|
||||
|
||||
return verticesIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {*[][]}
|
||||
*/
|
||||
getAdjacencyMatrix() {
|
||||
const vertices = this.getAllVertices();
|
||||
const verticesIndices = this.getVerticesIndices();
|
||||
|
||||
// Init matrix with infinities meaning that there is no ways of
|
||||
// getting from one vertex to another yet.
|
||||
const adjacencyMatrix = Array(vertices.length).fill(null).map(() => {
|
||||
return Array(vertices.length).fill(Infinity);
|
||||
});
|
||||
|
||||
// Fill the columns.
|
||||
vertices.forEach((vertex, vertexIndex) => {
|
||||
vertex.getNeighbors().forEach((neighbor) => {
|
||||
const neighborIndex = verticesIndices[neighbor.getKey()];
|
||||
adjacencyMatrix[vertexIndex][neighborIndex] = this.findEdge(vertex, neighbor).weight;
|
||||
});
|
||||
});
|
||||
|
||||
return adjacencyMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
toString() {
|
||||
return Object.keys(this.vertices).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][]} adjacencyMatrix
|
||||
* @param {object} verticesIndices
|
||||
* @param {GraphVertex[]} cycle
|
||||
* @param {GraphVertex} vertexCandidate
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isSafe(adjacencyMatrix, verticesIndices, cycle, vertexCandidate) {
|
||||
const endVertex = cycle[cycle.length - 1];
|
||||
|
||||
// Get end and candidate vertices indices in adjacency matrix.
|
||||
const candidateVertexAdjacencyIndex = verticesIndices[vertexCandidate.getKey()];
|
||||
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
|
||||
|
||||
// Check if last vertex in the path and candidate vertex are adjacent.
|
||||
if (adjacencyMatrix[endVertexAdjacencyIndex][candidateVertexAdjacencyIndex] === Infinity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if vertexCandidate is being added to the path for the first time.
|
||||
const candidateDuplicate = cycle.find((vertex) => vertex.getKey() === vertexCandidate.getKey());
|
||||
|
||||
return !candidateDuplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][]} adjacencyMatrix
|
||||
* @param {object} verticesIndices
|
||||
* @param {GraphVertex[]} cycle
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isCycle(adjacencyMatrix, verticesIndices, cycle) {
|
||||
// Check if first and last vertices in hamiltonian path are adjacent.
|
||||
|
||||
// Get start and end vertices from the path.
|
||||
const startVertex = cycle[0];
|
||||
const endVertex = cycle[cycle.length - 1];
|
||||
|
||||
// Get start/end vertices indices in adjacency matrix.
|
||||
const startVertexAdjacencyIndex = verticesIndices[startVertex.getKey()];
|
||||
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
|
||||
|
||||
// Check if we can go from end vertex to the start one.
|
||||
return adjacencyMatrix[endVertexAdjacencyIndex][startVertexAdjacencyIndex] !== Infinity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][]} adjacencyMatrix
|
||||
* @param {GraphVertex[]} vertices
|
||||
* @param {object} verticesIndices
|
||||
* @param {GraphVertex[][]} cycles
|
||||
* @param {GraphVertex[]} cycle
|
||||
*/
|
||||
function hamiltonianCycleRecursive({
|
||||
adjacencyMatrix,
|
||||
vertices,
|
||||
verticesIndices,
|
||||
cycles,
|
||||
cycle,
|
||||
}) {
|
||||
// Clone cycle in order to prevent it from modification by other DFS branches.
|
||||
const currentCycle = [...cycle].map((vertex) => new GraphVertex(vertex.value));
|
||||
|
||||
if (vertices.length === currentCycle.length) {
|
||||
// Hamiltonian path is found.
|
||||
// Now we need to check if it is cycle or not.
|
||||
if (isCycle(adjacencyMatrix, verticesIndices, currentCycle)) {
|
||||
// Another solution has been found. Save it.
|
||||
cycles.push(currentCycle);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let vertexIndex = 0; vertexIndex < vertices.length; vertexIndex += 1) {
|
||||
// Get vertex candidate that we will try to put into next path step and see if it fits.
|
||||
const vertexCandidate = vertices[vertexIndex];
|
||||
|
||||
// Check if it is safe to put vertex candidate to cycle.
|
||||
if (isSafe(adjacencyMatrix, verticesIndices, currentCycle, vertexCandidate)) {
|
||||
// Add candidate vertex to cycle path.
|
||||
currentCycle.push(vertexCandidate);
|
||||
|
||||
// Try to find other vertices in cycle.
|
||||
hamiltonianCycleRecursive({
|
||||
adjacencyMatrix,
|
||||
vertices,
|
||||
verticesIndices,
|
||||
cycles,
|
||||
cycle: currentCycle,
|
||||
});
|
||||
|
||||
// BACKTRACKING.
|
||||
// Remove candidate vertex from cycle path in order to try another one.
|
||||
currentCycle.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Graph} graph
|
||||
* @return {GraphVertex[][]}
|
||||
*/
|
||||
function hamiltonianCycle(graph) {
|
||||
// Gather some information about the graph that we will need to during
|
||||
// the problem solving.
|
||||
const verticesIndices = graph.getVerticesIndices();
|
||||
const adjacencyMatrix = graph.getAdjacencyMatrix();
|
||||
const vertices = graph.getAllVertices();
|
||||
|
||||
// Define start vertex. We will always pick the first one
|
||||
// this it doesn't matter which vertex to pick in a cycle.
|
||||
// Every vertex is in a cycle so we can start from any of them.
|
||||
const startVertex = vertices[0];
|
||||
|
||||
// Init cycles array that will hold all solutions.
|
||||
const cycles = [];
|
||||
|
||||
// Init cycle array that will hold current cycle path.
|
||||
const cycle = [startVertex];
|
||||
|
||||
// Try to find cycles recursively in Depth First Search order.
|
||||
hamiltonianCycleRecursive({
|
||||
adjacencyMatrix,
|
||||
vertices,
|
||||
verticesIndices,
|
||||
cycles,
|
||||
cycle,
|
||||
});
|
||||
|
||||
// Return found cycles.
|
||||
return cycles;
|
||||
}
|
||||
|
||||
|
||||
function findAllPaths(startVertex, paths = [], path = []) {
|
||||
// Clone path.
|
||||
const currentPath = [...path];
|
||||
|
||||
// Add startVertex to the path.
|
||||
currentPath.push(startVertex);
|
||||
|
||||
// Generate visited set from path.
|
||||
const visitedSet = currentPath.reduce((accumulator, vertex) => {
|
||||
const updatedAccumulator = {
|
||||
...accumulator
|
||||
};
|
||||
updatedAccumulator[vertex.getKey()] = vertex;
|
||||
|
||||
return updatedAccumulator;
|
||||
}, {});
|
||||
|
||||
// Get all unvisited neighbors of startVertex.
|
||||
const unvisitedNeighbors = startVertex.getNeighbors().filter((neighbor) => {
|
||||
return !visitedSet[neighbor.getKey()];
|
||||
});
|
||||
|
||||
// If there no unvisited neighbors then treat current path as complete and save it.
|
||||
if (!unvisitedNeighbors.length) {
|
||||
paths.push(currentPath);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
// Go through all the neighbors.
|
||||
for (let neighborIndex = 0; neighborIndex < unvisitedNeighbors.length; neighborIndex += 1) {
|
||||
const currentUnvisitedNeighbor = unvisitedNeighbors[neighborIndex];
|
||||
findAllPaths(currentUnvisitedNeighbor, paths, currentPath);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[][]} adjacencyMatrix
|
||||
* @param {object} verticesIndices
|
||||
* @param {GraphVertex[]} cycle
|
||||
* @return {number}
|
||||
*/
|
||||
function getCycleWeight(adjacencyMatrix, verticesIndices, cycle) {
|
||||
let weight = 0;
|
||||
|
||||
for (let cycleIndex = 1; cycleIndex < cycle.length; cycleIndex += 1) {
|
||||
const fromVertex = cycle[cycleIndex - 1];
|
||||
const toVertex = cycle[cycleIndex];
|
||||
const fromVertexIndex = verticesIndices[fromVertex.getKey()];
|
||||
const toVertexIndex = verticesIndices[toVertex.getKey()];
|
||||
weight += adjacencyMatrix[fromVertexIndex][toVertexIndex];
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* BRUTE FORCE approach to solve Traveling Salesman Problem.
|
||||
*
|
||||
* @param {Graph} graph
|
||||
* @return {GraphVertex[]}
|
||||
*/
|
||||
function bfTravellingSalesman(graph) {
|
||||
// Pick starting point from where we will traverse the graph.
|
||||
const startVertex = graph.getAllVertices()[0];
|
||||
|
||||
// BRUTE FORCE.
|
||||
// Generate all possible paths from startVertex.
|
||||
const allPossiblePaths = findAllPaths(startVertex);
|
||||
|
||||
// Filter out paths that are not cycles.
|
||||
const allPossibleCycles = allPossiblePaths.filter((path) => {
|
||||
/** @var {GraphVertex} */
|
||||
const lastVertex = path[path.length - 1];
|
||||
const lastVertexNeighbors = lastVertex.getNeighbors();
|
||||
|
||||
return lastVertexNeighbors.includes(startVertex);
|
||||
});
|
||||
|
||||
// Go through all possible cycles and pick the one with minimum overall tour weight.
|
||||
const adjacencyMatrix = graph.getAdjacencyMatrix();
|
||||
const verticesIndices = graph.getVerticesIndices();
|
||||
let salesmanPath = [];
|
||||
let salesmanPathWeight = null;
|
||||
for (let cycleIndex = 0; cycleIndex < allPossibleCycles.length; cycleIndex += 1) {
|
||||
const currentCycle = allPossibleCycles[cycleIndex];
|
||||
const currentCycleWeight = getCycleWeight(adjacencyMatrix, verticesIndices, currentCycle);
|
||||
|
||||
// If current cycle weight is smaller then previous ones treat current cycle as most optimal.
|
||||
if (salesmanPathWeight === null || currentCycleWeight < salesmanPathWeight) {
|
||||
salesmanPath = currentCycle;
|
||||
salesmanPathWeight = currentCycleWeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the solution.
|
||||
return salesmanPath;
|
||||
}
|
Loading…
Reference in new issue