#include #ifdef __EMSCRIPTEN__ #include #include #endif #include #include #include #include #include struct City { double x; double y; }; struct State{ float* weights; uint8_t* adjacencyMatrix; float lowerBound; bool finished = false; std::vector> paths; }; struct SplitState { bool wasSplittable = true; State firstState; State secondState; }; double reduceWeights(float* weights, int numberOfCities) { double totalReduced = 0; for (int row = 0; row < numberOfCities; row++) { double smallestOnRow = 1e100; for (int column = 0; column < numberOfCities; column++) { if (weights[row * numberOfCities + column] < smallestOnRow) { smallestOnRow = weights[row * numberOfCities + column]; } } if (smallestOnRow > 1e50) { continue; } totalReduced += smallestOnRow; for (int column = 0; column < numberOfCities; column++) { weights[row * numberOfCities + column] -= smallestOnRow; } } for (int column = 0; column < numberOfCities; column++) { double smallestOnColumn = 1e100; for (int row = 0; row < numberOfCities; row++) { if (weights[row * numberOfCities + column] < smallestOnColumn) { smallestOnColumn = weights[row * numberOfCities + column]; } } if (smallestOnColumn > 1e50) { continue; } totalReduced += smallestOnColumn; for (int row = 0; row < numberOfCities; row++) { weights[row * numberOfCities + column] -= smallestOnColumn; } } return totalReduced; } std::pair findPivotPoint(float* weights, int numberOfCities) { int bestRow = -1; int bestColumn = -1; double bestIncrease = -1; for (int testRow = 0; testRow < numberOfCities; testRow++) { for (int testColumn = 0; testColumn < numberOfCities; testColumn++) { // Only look for pivot cells that have a value of zero if (weights[testRow * numberOfCities + testColumn] > 0.0001) { continue; } double smallestOnRow = 1e100; double smallestOnColumn = 1e100; for (int otherColumn = 0; otherColumn < numberOfCities; otherColumn++) { if (otherColumn == testColumn) { continue; } if (weights[testRow * numberOfCities + otherColumn] < smallestOnRow) { smallestOnRow = weights[testRow * numberOfCities + otherColumn]; } } for (int otherRow = 0; otherRow < numberOfCities; otherRow++) { if (otherRow == testRow) { continue; } if (weights[otherRow * numberOfCities + testColumn] < smallestOnColumn) { smallestOnColumn = weights[otherRow * numberOfCities + testColumn]; } } double totalIncrease = smallestOnRow + smallestOnColumn; if (totalIncrease > bestIncrease) { bestIncrease = totalIncrease; bestRow = testRow; bestColumn = testColumn; } } } return std::pair(bestRow, bestColumn); } int getDegreeOfNode(int nodeNumber, uint8_t* adjacencyMatrix, int numberOfCities) { int degree = 0; for (int column = 0; column < numberOfCities; column++) { degree += adjacencyMatrix[nodeNumber * numberOfCities + column]; } return degree; } void multiplyAdjacencyMatrices(int** matrix, int** multiplier, int size) { int** temporary = new int*[size]; for (int row = 0; row < size; row++) { temporary[row] = new int[size]; for (int column = 0; column < size; column++) { int result = 0; for (int k = 0; k < size; k++) { result += matrix[row][k] * multiplier[k][column]; } temporary[row][column] = result; } } // Copy into original matrix for (int row = 0; row < size; row++) { for (int column = 0; column < size; column++) { matrix[row][column] = temporary[row][column]; } delete [] temporary[row]; } delete [] temporary; } void disallowSubloops(State &state, int pivotRow, int pivotColumn, int numberOfCities) { // All points with degree one are endpoint int totalDegree = 0; for (int node = 0; node < numberOfCities; node++) { int degree = getDegreeOfNode(node, state.adjacencyMatrix, numberOfCities); totalDegree += degree; if (degree == 2) { for (int otherNode = 0; otherNode < numberOfCities; otherNode++) { state.weights[node * numberOfCities + otherNode] = 1e100; state.weights[otherNode * numberOfCities + node] = 1e100; } } } // Add this path to the existing paths std::pair path(pivotRow, pivotColumn); auto iterator = state.paths.begin(); while (iterator != state.paths.end()) { std::pair otherPath = *iterator; bool expandsPath = false; if (path.first == otherPath.first) { expandsPath = true; path = std::pair(path.second, otherPath.second); } else if (path.first == otherPath.second) { expandsPath = true; path = std::pair(path.second, otherPath.first); } else if (path.second == otherPath.first) { expandsPath = true; path = std::pair(path.first, otherPath.second); } else if (path.second == otherPath.second) { expandsPath = true; path = std::pair(path.first, otherPath.first); } if (expandsPath) { iterator = state.paths.erase(iterator); } else { iterator++; } } state.paths.push_back(path); // There needs to be n edges in a loop, and so the total degree should be 2*n. If we're getting close to this, don't // block of the ability to finish a loop if (state.paths.size() == 1 && totalDegree == 2*(numberOfCities - 1)) { // Finish the loop int startingNode = state.paths.at(0).first; int endingNode = state.paths.at(0).second; state.adjacencyMatrix[startingNode * numberOfCities + endingNode] = 1; state.adjacencyMatrix[endingNode * numberOfCities + startingNode] = 1; state.finished = true; return; } for (std::pair path : state.paths) { state.weights[path.first * numberOfCities + path.second] = 1e100; state.weights[path.second * numberOfCities + path.first] = 1e100; } } State createNewStateOnPivot(int pivotRow, int pivotColumn, State originalState, int numberOfCities) { uint8_t* adjacencyMatrixCopy = new uint8_t[numberOfCities * numberOfCities]; float* weightsCopy = new float[numberOfCities * numberOfCities]; for (int row = 0; row < numberOfCities; row++) { for (int column = 0; column < numberOfCities; column++) { adjacencyMatrixCopy[row * numberOfCities + column] = originalState.adjacencyMatrix[row * numberOfCities + column]; weightsCopy[row * numberOfCities + column] = originalState.weights[row * numberOfCities + column]; } } adjacencyMatrixCopy[pivotRow * numberOfCities + pivotColumn] = 1; adjacencyMatrixCopy[pivotColumn * numberOfCities + pivotRow] = 1; for (int column = 0; column < numberOfCities; column++) { weightsCopy[pivotRow * numberOfCities + column] = 1e100; } for (int row = 0; row < numberOfCities; row++) { weightsCopy[row * numberOfCities + pivotColumn] = 1e100; } weightsCopy[pivotColumn * numberOfCities + pivotRow] = 1e100; State pivotedState; pivotedState.adjacencyMatrix = adjacencyMatrixCopy; pivotedState.weights = weightsCopy; pivotedState.lowerBound = originalState.lowerBound; for (std::pair path : originalState.paths) { pivotedState.paths.push_back(std::pair(path.first, path.second)); } disallowSubloops(pivotedState, pivotRow, pivotColumn, numberOfCities); return pivotedState; } SplitState splitState(State state, int numberOfCities) { SplitState splitState; double reduction = reduceWeights(state.weights, numberOfCities); state.lowerBound += reduction; std::pair pivotPoint = findPivotPoint(state.weights, numberOfCities); if (pivotPoint.first == -1 || pivotPoint.second == -1) { splitState.wasSplittable = false; delete [] state.adjacencyMatrix; delete [] state.weights; return splitState; } State pivotedState = createNewStateOnPivot(pivotPoint.first, pivotPoint.second, state, numberOfCities); // Disallow the pivot in the original state state.weights[pivotPoint.first * numberOfCities + pivotPoint.second] = 1e100; splitState.firstState = pivotedState; splitState.secondState = state; return splitState; } double getActualWeight(uint8_t* adjacencyMatrix, double* weights, int numberOfCities) { double totalWeight = 0; for (int row = 0; row < numberOfCities; row++) { for (int column = 0; column < numberOfCities; column++) { totalWeight += adjacencyMatrix[row * numberOfCities + column] * weights[row * numberOfCities + column]; } } return totalWeight / 2; } class StateComparator { public: bool operator()(State one, State two) { return one.lowerBound > two.lowerBound; } }; std::vector findShortestPath(std::vector> jsWeights) { int numberOfCities = jsWeights.size(); double* weights = new double[numberOfCities * numberOfCities]; for (int row = 0; row < numberOfCities; row++) { for (int column = 0; column < numberOfCities; column++) { weights[row * numberOfCities + column] = jsWeights.at(row).at(column); if (column == row) { weights[row * numberOfCities + column] = 1e100; } } } // Create a state State initialState; initialState.weights = new float[numberOfCities * numberOfCities]; initialState.adjacencyMatrix = new uint8_t[numberOfCities * numberOfCities]; for (int row = 0; row < numberOfCities; row++) { for (int column = 0; column < numberOfCities; column++) { initialState.adjacencyMatrix[row * numberOfCities + column] = 0; initialState.weights[row * numberOfCities + column] = weights[row * numberOfCities + column]; } } initialState.lowerBound = 0; std::priority_queue, StateComparator> queue; queue.push(initialState); double currentBest = 1e100; uint8_t* bestAdjacenyMatrix = NULL; while (!queue.empty()) { State nextState = queue.top(); queue.pop(); if (nextState.lowerBound > currentBest) { break; } if (nextState.finished) { double actualWeight = getActualWeight(nextState.adjacencyMatrix, weights, numberOfCities); if (actualWeight < currentBest) { currentBest = actualWeight; bestAdjacenyMatrix = nextState.adjacencyMatrix; } else { delete [] nextState.adjacencyMatrix; } delete [] nextState.weights; continue; } // If we're not finished, split the state and add the new ones to the queue SplitState split = splitState(nextState, numberOfCities); if (split.wasSplittable) { queue.push(split.firstState); queue.push(split.secondState); } } // Clear up remaining queue for memory while (!queue.empty()) { State stateToDelete = queue.top(); queue.pop(); delete [] stateToDelete.adjacencyMatrix; delete [] stateToDelete.weights; } std::vector path; int current = 0; int previous = -1; path.push_back(current); bool firstTime = true; while (current != 0 || firstTime) { firstTime = false; for (int column = 0; column < numberOfCities; column++) { if (bestAdjacenyMatrix[current * numberOfCities + column] == 1 && column != previous) { previous = current; current = column; break; } } path.push_back(current); } return path; } #ifdef __EMSCRIPTEN__ EMSCRIPTEN_BINDINGS(my_module) { emscripten::register_vector("WeightsRow"); emscripten::register_vector>("Weights"); emscripten::register_vector("Path"); emscripten::function("findShortestPath", &findShortestPath); } #endif