mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-14 06:52:59 +08:00
Add topological sorting.
This commit is contained in:
parent
fc53c7de5d
commit
e73dc2dfd7
@ -71,7 +71,7 @@
|
|||||||
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
|
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
|
||||||
* [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
* [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||||
* [Kruskal’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
* [Kruskal’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||||
* Topological Sorting
|
* [Topological Sorting](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/topological-sorting) - DFS method
|
||||||
* Eulerian path, Eulerian circuit
|
* Eulerian path, Eulerian circuit
|
||||||
* Strongly Connected Component algorithm
|
* Strongly Connected Component algorithm
|
||||||
* Shortest Path Faster Algorithm (SPFA)
|
* Shortest Path Faster Algorithm (SPFA)
|
||||||
|
55
src/algorithms/graph/topological-sorting/README.md
Normal file
55
src/algorithms/graph/topological-sorting/README.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Topological Sorting
|
||||||
|
|
||||||
|
In the field of computer science, a topological sort or
|
||||||
|
topological ordering of a directed graph is a linear ordering
|
||||||
|
of its vertices such that for every directed edge `uv` from
|
||||||
|
vertex `u` to vertex `v`, `u` comes before `v` in the ordering.
|
||||||
|
|
||||||
|
For instance, the vertices of the graph may represent tasks to
|
||||||
|
be performed, and the edges may represent constraints that one
|
||||||
|
task must be performed before another; in this application, a
|
||||||
|
topological ordering is just a valid sequence for the tasks.
|
||||||
|
|
||||||
|
A topological ordering is possible if and only if the graph has
|
||||||
|
no directed cycles, that is, if it is a [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph)
|
||||||
|
(DAG). Any DAG has at least one topological ordering, and algorithms are
|
||||||
|
known for constructing a topological ordering of any DAG in linear time.
|
||||||
|
|
||||||
|
![Directed Acyclic Graph](https://upload.wikimedia.org/wikipedia/commons/c/c6/Topological_Ordering.svg)
|
||||||
|
|
||||||
|
A topological ordering of a directed acyclic graph: every edge goes from
|
||||||
|
earlier in the ordering (upper left) to later in the ordering (lower right).
|
||||||
|
A directed graph is acyclic if and only if it has a topological ordering.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
![Topologic Sorting](https://upload.wikimedia.org/wikipedia/commons/0/03/Directed_acyclic_graph_2.svg)
|
||||||
|
|
||||||
|
The graph shown above has many valid topological sorts, including:
|
||||||
|
|
||||||
|
- `5, 7, 3, 11, 8, 2, 9, 10` (visual left-to-right, top-to-bottom)
|
||||||
|
- `3, 5, 7, 8, 11, 2, 9, 10` (smallest-numbered available vertex first)
|
||||||
|
- `5, 7, 3, 8, 11, 10, 9, 2` (fewest edges first)
|
||||||
|
- `7, 5, 11, 3, 10, 8, 9, 2` (largest-numbered available vertex first)
|
||||||
|
- `5, 7, 11, 2, 3, 8, 9, 10` (attempting top-to-bottom, left-to-right)
|
||||||
|
- `3, 7, 8, 5, 11, 10, 2, 9` (arbitrary)
|
||||||
|
|
||||||
|
## Application
|
||||||
|
|
||||||
|
The canonical application of topological sorting is in
|
||||||
|
**scheduling a sequence of jobs** or tasks based on their dependencies. The jobs
|
||||||
|
are represented by vertices, and there is an edge from `x` to `y` if
|
||||||
|
job `x` must be completed before job `y` can be started (for
|
||||||
|
example, when washing clothes, the washing machine must finish
|
||||||
|
before we put the clothes in the dryer). Then, a topological sort
|
||||||
|
gives an order in which to perform the jobs.
|
||||||
|
|
||||||
|
Other application is **dependency resolution**. Each vertex is a package
|
||||||
|
and each edge is a dependency of package `a` on package 'b'. Then topological
|
||||||
|
sorting will provide a sequence of installing dependencies in a way that every
|
||||||
|
next dependency has its dependent packages to be installed in prior.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Wikipedia](https://en.wikipedia.org/wiki/Topological_sorting)
|
||||||
|
- [Topological Sorting on YouTube by Tushar Roy](https://www.youtube.com/watch?v=ddTC4Zovtbc)
|
@ -0,0 +1,53 @@
|
|||||||
|
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
|
||||||
|
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
|
||||||
|
import Graph from '../../../../data-structures/graph/Graph';
|
||||||
|
import topologicalSort from '../topologicalSort';
|
||||||
|
|
||||||
|
describe('topologicalSort', () => {
|
||||||
|
it('should do topological sorting on graph', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
const vertexD = new GraphVertex('D');
|
||||||
|
const vertexE = new GraphVertex('E');
|
||||||
|
const vertexF = new GraphVertex('F');
|
||||||
|
const vertexG = new GraphVertex('G');
|
||||||
|
const vertexH = new GraphVertex('H');
|
||||||
|
|
||||||
|
const edgeAC = new GraphEdge(vertexA, vertexC);
|
||||||
|
const edgeBC = new GraphEdge(vertexB, vertexC);
|
||||||
|
const edgeBD = new GraphEdge(vertexB, vertexD);
|
||||||
|
const edgeCE = new GraphEdge(vertexC, vertexE);
|
||||||
|
const edgeDF = new GraphEdge(vertexD, vertexF);
|
||||||
|
const edgeEF = new GraphEdge(vertexE, vertexF);
|
||||||
|
const edgeEH = new GraphEdge(vertexE, vertexH);
|
||||||
|
const edgeFG = new GraphEdge(vertexF, vertexG);
|
||||||
|
|
||||||
|
const graph = new Graph(true);
|
||||||
|
|
||||||
|
graph
|
||||||
|
.addEdge(edgeAC)
|
||||||
|
.addEdge(edgeBC)
|
||||||
|
.addEdge(edgeBD)
|
||||||
|
.addEdge(edgeCE)
|
||||||
|
.addEdge(edgeDF)
|
||||||
|
.addEdge(edgeEF)
|
||||||
|
.addEdge(edgeEH)
|
||||||
|
.addEdge(edgeFG);
|
||||||
|
|
||||||
|
const sortedVertices = topologicalSort(graph);
|
||||||
|
|
||||||
|
expect(sortedVertices).toBeDefined();
|
||||||
|
expect(sortedVertices.length).toBe(graph.getAllVertices().length);
|
||||||
|
expect(sortedVertices).toEqual([
|
||||||
|
vertexB,
|
||||||
|
vertexD,
|
||||||
|
vertexA,
|
||||||
|
vertexC,
|
||||||
|
vertexE,
|
||||||
|
vertexH,
|
||||||
|
vertexF,
|
||||||
|
vertexG,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
47
src/algorithms/graph/topological-sorting/topologicalSort.js
Normal file
47
src/algorithms/graph/topological-sorting/topologicalSort.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import Stack from '../../../data-structures/stack/Stack';
|
||||||
|
import depthFirstSearch from '../depth-first-search/depthFirstSearch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Graph} graph
|
||||||
|
*/
|
||||||
|
export default function topologicalSort(graph) {
|
||||||
|
// Create a set of all vertices we want to visit.
|
||||||
|
const unvisitedSet = {};
|
||||||
|
graph.getAllVertices().forEach((vertex) => {
|
||||||
|
unvisitedSet[vertex.getKey()] = vertex;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a set for all vertices that we've already visited.
|
||||||
|
const visitedSet = {};
|
||||||
|
|
||||||
|
// Create a stack of already ordered vertices.
|
||||||
|
const sortedStack = new Stack();
|
||||||
|
|
||||||
|
const dfsCallbacks = {
|
||||||
|
enterVertex: ({ currentVertex }) => {
|
||||||
|
// Add vertex to visited set in case if all its children has been explored.
|
||||||
|
visitedSet[currentVertex.getKey()] = currentVertex;
|
||||||
|
|
||||||
|
// Remove this vertex from unvisited set.
|
||||||
|
delete unvisitedSet[currentVertex.getKey()];
|
||||||
|
},
|
||||||
|
leaveVertex: ({ currentVertex }) => {
|
||||||
|
// If the vertex has been totally explored then we may push it to stack.
|
||||||
|
sortedStack.push(currentVertex);
|
||||||
|
},
|
||||||
|
allowTraversal: ({ nextVertex }) => {
|
||||||
|
return !visitedSet[nextVertex.getKey()];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Let's go and do DFS for all unvisited nodes.
|
||||||
|
while (Object.keys(unvisitedSet).length) {
|
||||||
|
const currentVertexKey = Object.keys(unvisitedSet)[0];
|
||||||
|
const currentVertex = unvisitedSet[currentVertexKey];
|
||||||
|
|
||||||
|
// Do DFS for current node.
|
||||||
|
depthFirstSearch(graph, currentVertex, dfsCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedStack.toArray();
|
||||||
|
}
|
@ -9,6 +9,10 @@ export default class LinkedList {
|
|||||||
this.tail = null;
|
this.tail = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} value
|
||||||
|
* @return {LinkedList}
|
||||||
|
*/
|
||||||
prepend(value) {
|
prepend(value) {
|
||||||
// Make new node to be a head.
|
// Make new node to be a head.
|
||||||
this.head = new LinkedListNode(value, this.head);
|
this.head = new LinkedListNode(value, this.head);
|
||||||
@ -16,6 +20,10 @@ export default class LinkedList {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} value
|
||||||
|
* @return {LinkedList}
|
||||||
|
*/
|
||||||
append(value) {
|
append(value) {
|
||||||
const newNode = new LinkedListNode(value);
|
const newNode = new LinkedListNode(value);
|
||||||
|
|
||||||
@ -34,6 +42,10 @@ export default class LinkedList {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} value
|
||||||
|
* @return {LinkedListNode}
|
||||||
|
*/
|
||||||
delete(value) {
|
delete(value) {
|
||||||
if (!this.head) {
|
if (!this.head) {
|
||||||
return null;
|
return null;
|
||||||
@ -67,6 +79,12 @@ export default class LinkedList {
|
|||||||
return deletedNode;
|
return deletedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} findParams
|
||||||
|
* @param {*} findParams.value
|
||||||
|
* @param {function} [findParams.callback]
|
||||||
|
* @return {LinkedListNode}
|
||||||
|
*/
|
||||||
find({ value = undefined, callback = undefined }) {
|
find({ value = undefined, callback = undefined }) {
|
||||||
if (!this.head) {
|
if (!this.head) {
|
||||||
return null;
|
return null;
|
||||||
@ -91,6 +109,9 @@ export default class LinkedList {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {LinkedListNode}
|
||||||
|
*/
|
||||||
deleteTail() {
|
deleteTail() {
|
||||||
if (this.head === this.tail) {
|
if (this.head === this.tail) {
|
||||||
const deletedTail = this.tail;
|
const deletedTail = this.tail;
|
||||||
@ -116,6 +137,9 @@ export default class LinkedList {
|
|||||||
return deletedTail;
|
return deletedTail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {LinkedListNode}
|
||||||
|
*/
|
||||||
deleteHead() {
|
deleteHead() {
|
||||||
if (!this.head) {
|
if (!this.head) {
|
||||||
return null;
|
return null;
|
||||||
@ -133,6 +157,9 @@ export default class LinkedList {
|
|||||||
return deletedHead;
|
return deletedHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {LinkedListNode[]}
|
||||||
|
*/
|
||||||
toArray() {
|
toArray() {
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
|
|
||||||
@ -145,6 +172,10 @@ export default class LinkedList {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function} [callback]
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
toString(callback) {
|
toString(callback) {
|
||||||
return this.toArray().map(node => node.toString(callback)).toString();
|
return this.toArray().map(node => node.toString(callback)).toString();
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,16 @@ export default class Stack {
|
|||||||
this.linkedList = new LinkedList();
|
this.linkedList = new LinkedList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return !this.linkedList.tail;
|
return !this.linkedList.tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {LinkedListNode}
|
||||||
|
*/
|
||||||
peek() {
|
peek() {
|
||||||
if (!this.linkedList.tail) {
|
if (!this.linkedList.tail) {
|
||||||
return null;
|
return null;
|
||||||
@ -17,15 +23,35 @@ export default class Stack {
|
|||||||
return this.linkedList.tail.value;
|
return this.linkedList.tail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
push(value) {
|
push(value) {
|
||||||
this.linkedList.append(value);
|
this.linkedList.append(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {LinkedListNode}
|
||||||
|
*/
|
||||||
pop() {
|
pop() {
|
||||||
const removedTail = this.linkedList.deleteTail();
|
const removedTail = this.linkedList.deleteTail();
|
||||||
return removedTail ? removedTail.value : null;
|
return removedTail ? removedTail.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {*[]}
|
||||||
|
*/
|
||||||
|
toArray() {
|
||||||
|
return this.linkedList
|
||||||
|
.toArray()
|
||||||
|
.map(linkedListNode => linkedListNode.value)
|
||||||
|
.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function} [callback]
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
toString(callback) {
|
toString(callback) {
|
||||||
return this.linkedList.toString(callback);
|
return this.linkedList.toString(callback);
|
||||||
}
|
}
|
||||||
|
@ -62,4 +62,16 @@ describe('Stack', () => {
|
|||||||
expect(stack.pop().value).toBe('test2');
|
expect(stack.pop().value).toBe('test2');
|
||||||
expect(stack.pop().value).toBe('test1');
|
expect(stack.pop().value).toBe('test1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be possible to convert stack to array', () => {
|
||||||
|
const stack = new Stack();
|
||||||
|
|
||||||
|
expect(stack.peek()).toBeNull();
|
||||||
|
|
||||||
|
stack.push(1);
|
||||||
|
stack.push(2);
|
||||||
|
stack.push(3);
|
||||||
|
|
||||||
|
expect(stack.toArray()).toEqual([3, 2, 1]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user