online compiler and debugger for c/c++

code. compile. run. debug. share.
Source Code   
Language
const { performance } = require("node:perf_hooks"); const fs = require("node:fs"); // funkcja do parsowania pliku .tsp function parseTSP(fileContent) { const lines = fileContent.split("\n"); const nodes = new Map(); let isNodeSection = false; for (let line of lines) { line = line.trim(); if (line === "NODE_COORD_SECTION") { isNodeSection = true; continue; } if (line === "EOF") { break; } if (isNodeSection) { const parts = line.split(/\s+/); if (parts.length === 3) { const nodeId = parseInt(parts[0]); const x = parseFloat(parts[1]); const y = parseFloat(parts[2]); nodes.set(nodeId, { x, y }); } } } return nodes; } // odczytujemy dane z pliku att48.tsp ze zbioru TSPLIB // att48 zawiera współrzędne stolic 48 kontynentalnych stanów USA // dla naszych testów będziemy używać jedynie wycinek danych const data = fs.readFileSync("att48.tsp", "utf8"); const nodes = parseTSP(data); // funkcja obliczająca odległość między dwoma wierzchołkami // liczymy tutaj odległość zgodnie z instrukcją z TSPLIB function distance(a, b) { const nodeA = nodes.get(a); const nodeB = nodes.get(b); if (!nodeA || !nodeB) { throw new Error(`Nie znaleziono wierzchołków ${a} i/lub ${b}`); } const xd = nodeA.x - nodeB.x; const yd = nodeA.y - nodeB.y; const rij = Math.sqrt((xd * xd + yd * yd) / 10.0); const tij = Math.round(rij); return tij < rij ? tij + 1 : tij; } let iterations = 0; // algorytm Kruskala do znalezienia MST function mst(nodes) { // lista wierzchołków const edges = []; // rekonstruujemy listę krawędzi (unikalne dla naszego problemu) for (let i = 0; i < nodes.length; i++) { for (let j = i + 1; j < nodes.length; j++) { iterations++; edges.push({ from: nodes[i], to: nodes[j], weight: distance(nodes[i], nodes[j]), }); } } // sortujemy krawędzie rosnąco według wag // skorzystamy z sortowania wbudowanego w JS, które zapewni złożoność O(n log n) edges.sort((a, b) => { iterations++; return a.weight - b.weight; }); // do przechowania drzew będziemy używać struktury zbiorów rozłącznych (DSU) // do jej zrobienia wykorzystamy wbudowaną w JS strukturę Map // w tej mapie przechowamy mapowanie wierzchołka do jego rodzica w drzewie const parent = new Map(); // a tutaj przybliżoną wysokość drzewa const rank = new Map(); // funkcja szukająca korzenia drzewa, w którym znajduje się wierzchołek function find(node) { // jeśli parent.get(node) === node to node jest korzeniem drzewa if (parent.get(node) !== node) { // jeśli nie, to rekurencyjnie szukamy dalej korzenia // używamy set, ponieważ dokonujemy tzw. kompresji ścieżki, aby następne wyszukania były szybsze parent.set(node, find(parent.get(node))); } // zwracamy korzeń drzewa return parent.get(node); } // funkcja łącząca dwa drzewa w jedno function union(node1, node2) { // najpierw szukamy korzeni drzwa const root1 = find(node1); const root2 = find(node2); // jeśli faktycznie mamy do czynienia z oddzielnymi drzewami... if (root1 !== root2) { // teraz musimy zdecydować, który korzeń stanie się rodzicem drugiego // wykorzystamy do tego znane nam wysokości drzewa // chcemy zawsze podłączyć wyższe do niższego if (rank.get(root1) > rank.get(root2)) { parent.set(root2, root1); } else if (rank.get(root1) < rank.get(root2)) { parent.set(root1, root2); } else { // w tym przypadku nie ma znaczenia, które podłączymy do któego parent.set(root2, root1); // jednak musimy zwiększyć wysokość drzewa w tym przypadku rank.set(root1, rank.get(root1) + 1); // w pozostałych przypadkach nie ma to już znaczenia // ponieważ wysokość drzewa nie ulegnie zmianie } } } // wracamy do algorytmu Kruskala // w tym momencie inicjalizujemy las, tworząc jednoelementowe drzewa for (const node of nodes) { iterations++; parent.set(node, node); rank.set(node, 0); } // inicjalizujemy tablicę krawędzi opisujących MST const mst = []; // teraz przechodzimy po wszystkich krawędziach for (const edge of edges) { iterations++; // sprawdzamy, czy dana krawędź połączyłaby ze sobą dwa drzewa if (find(edge.from) !== find(edge.to)) { // jeśli tak, to dodajemy ją do MST mst.push(edge); // i aktualizujemy drzewa union(edge.from, edge.to); } } // zwracamy listę krawędzi drzewa return mst; } // funkcja znajdująca minimalne dopasowanie doskonałe function perfectMatch(oddNodes) { // zmienna w której przechowamy wynikową listę krawędzi const result = []; // zbiór do którego będziemy odkładać wykorzystane już wierzchołki const used = new Set(); // przeitetujemy po wszystkich wierzchołkach i poszukamy najbliższej do niego pary for (let i = 0; i < oddNodes.length; i++) { // sprawdzamy wierzchołek tylko wtedy, jeśli nie jest połączony w parę if (!used.has(oddNodes[i])) { // zmienne pomocnicze do szukania minimum let bestMatch = null; let bestDistance = Number.POSITIVE_INFINITY; // szukamy najbliższego wierzchołka z niepołączonych w pary for (let j = i + 1; j < oddNodes.length; j++) { iterations++; if (!used.has(oddNodes[j])) { let d = distance(oddNodes[i], oddNodes[j]); if (d < bestDistance) { bestDistance = d; bestMatch = oddNodes[j]; } } } // po znalezieniu pary dodajemy ją do wyniku // oraz do listy wykorzystanych wierzchołków if (bestMatch !== null) { result.push({ from: oddNodes[i], to: bestMatch, weight: bestDistance, }); used.add(oddNodes[i]); used.add(bestMatch); } } } // zwracamy listę krawędzi return result; } // funkcja tworząca identyfikator krawędzi function getEdgeId(node1, node2) { // mamy graf nieskierowany, więc musimy zapewnić kolejność zapisu wierzchołków return node1 < node2 ? `${node1}-${node2}` : `${node2}-${node1}`; } // funkcja znajdująca cykl Eulera function getEulerianCircuit(multigraph) { // mapa, która będzie nam przechowywać listę sąsiedztwa const adjList = new Map(); // oraz zbiór, w którym będziemy przechowywać odwiedzone krawędzie const visitedEdges = new Set(); // najwygodniejszą strukturą grafową do algorytmu Fleury'ego // jest lista sąsiedztwa — do niej przekonwertujmy nasz multigraf // w tym celu musimy przeiterować po wszystkich krawędziach multigrafu // a jest on zapisany jako lista krawędzi for (const edge of multigraph) { iterations++; // jeśli nie mamy w mapie któregoś z wierzchołków, to tworzymy im puste listy if (!adjList.has(edge.from)) { adjList.set(edge.from, []); } if (!adjList.has(edge.to)) { adjList.set(edge.to, []); } // dodajemy krawędzie do list odpowiadających wierzchołkom adjList.get(edge.from).push(edge.to); adjList.get(edge.to).push(edge.from); } // zmienna, która przechowa nam rezultat algorytmu // uwaga! przechowamy kolejność odwiedzania wierzchołków, nie krawędzi // taki sposób będzie dla nas bardziej przydatny const result = []; // stos (jako tablica), gdzie przechowamy wierzchołki, które mamy odwiedzić // inicjujemy go od razu wierzchołkiem początkowym pierwszej krawędzi const stack = [multigraph[0].from]; // w tym miejscu zaczyna się implementacja algorytmu Fleury'ego // wykonujemy go tak długo, jak na stosie mamy wciąż wierzchołki do odwiedzenia while (stack.length > 0) { iterations++; // pobieramy wierzchołek ze szczytu stosu // dla wygody, za szczyt uznajemy koniec tablicy const node = stack.at(-1); // zmienna, w której przechowamy informację, czy odwiedziliśmy krawędzie let hasVisited = false; // pobieramy listę krawędzi aktualnego wierzchołka const neighbors = adjList.get(node); // przechodzimy po wszystkich sąsiadujących wierzchołkach for (const next of neighbors) { // generujemy identyfikator krawędzi, aby sprawdzić w zbiorze czy jest odwiedzona const edgeId = getEdgeId(node, next); // sprawdzamy, czy krawędź nie została odwiedzona if (!visitedEdges.has(edgeId)) { // przechodzimy przez nieodwiedzoną krawędź // najpierw oznaczamy ją jako odwiedzoną visitedEdges.add(edgeId); // i dodajemy na stos wierzchołek do którego prowadzi krawędź stack.push(next); // dodatkowo oznaczamy, że wierzchołek jeszcze powinniśmy zostawić hasVisited = true; // i przerywamy wykonanie pętli, żeby przejść do następnego wierzchołka break; } } // sprawdzamy, czy odwiedziliśmy jakąś krawędź if (!hasVisited) { // jeśli nie, to usuwamy wierzchołek ze szczytu stosu // i dodajemy go do wyniku result.push(stack.pop()); } } // zwracamy listę wierzchołków return result; } // funkcja zwracająca wierzchołki nieparzystego stopnia function getOddDegreeNodes(edges) { // mapa przechowująca stopnie wierzchołków const degree = new Map(); // iterujemy po kolejnych krawędziach for (const edge of edges) { iterations++; // zwiększamy stopnie obu wierzchołków połączonych aktualną krawędzią degree.set(edge.from, (degree.get(edge.from) || 0) + 1); degree.set(edge.to, (degree.get(edge.to) || 0) + 1); } // tablica, w której przechowamy wierzchołki nieparzystego stopnia const result = []; // iterujemy po wszystkich wierzchołkach for (const [node, value] of degree) { iterations++; if (value % 2 !== 0) { // dodajemy wierzchołek o nieparzystym stopniu result.push(node); } } // zwracamy wynik return result; } // funkcja przekształcająca cykl Eulera na ścieżkę Hamiltona // cykl eulera jest zapisany jako lista odwiedzanych po kolei wierzchołków function convertToHamiltonianPath(nodes) { // zbiór przechowujący odwiedzone wierzchołki const visited = new Set(); // zmienna przechowująca wynikową ścieżkę const result = []; // iterujemy po kolejnych wierzchołkach cyklu Eulera for (const node of nodes) { iterations++; // dodajemy do wyniku tylko nieodwiedzone wierzchołki if (!visited.has(node)) { result.push(node); // pamiętamy, żeby oznaczyć wierzchołek jako odwiedzony visited.add(node); } } // zwracamy wynik return result; } // funkcja do obliczenia długości trasy function calculatePathLength(nodes) { let result = 0; // sumujemy w pętli dystanse między kolejnymi wierzchołkami for (let i = 0; i < nodes.length - 1; i++) { result += distance(nodes[i], nodes[i + 1]); } return result; } // funkcja znajdująca najkrótszą ścieżkę w grafie // nodes jest typu number[] function tsp(nodes) { iterations = 0; const start = performance.now(); // najpierw budujemy MST const tree = mst(nodes); // wyciągamy z niego wierzchołki o nieparzystym stopniu const oddDegreeNodes = getOddDegreeNodes(tree); // i tworzymy pary minimalnego dopssowania doskonałego const perfectMatching = perfectMatch(oddDegreeNodes); // tworzymy multigraf z MST i dopasowanych par const multigraph = [...tree, ...perfectMatching]; // tworzymy cykl Eulera const eulerianCircuit = getEulerianCircuit(multigraph); // i przekształcamy go na ścieżkę Hamiltona const hamiltonianPath = convertToHamiltonianPath(eulerianCircuit); const end = performance.now(); // dokładamy na koniec ścieżki pierwszy wierzchołek const path = [...hamiltonianPath, hamiltonianPath[0]]; // liczymy długość trasy const length = calculatePathLength(path); // zwracamy rezultat return { path, length, iterations, time: end - start, }; } const allVertices = [...nodes.keys()]; for (let i = 2; i <= 12; i++) { console.log(`${i} wierzchołków:`, tsp(allVertices.slice(0, i))); }
NAME : att48 COMMENT : 48 capitals of the US (Padberg/Rinaldi) TYPE : TSP DIMENSION : 48 EDGE_WEIGHT_TYPE : ATT NODE_COORD_SECTION 1 6734 1453 2 2233 10 3 5530 1424 4 401 841 5 3082 1644 6 7608 4458 7 7573 3716 8 7265 1268 9 6898 1885 10 1112 2049 11 5468 2606 12 5989 2873 13 4706 2674 14 4612 2035 15 6347 2683 16 6107 669 17 7611 5184 18 7462 3590 19 7732 4723 20 5900 3561 21 4483 3369 22 6101 1110 23 5199 2182 24 1633 2809 25 4307 2322 26 675 1006 27 7555 4819 28 7541 3981 29 3177 756 30 7352 4506 31 7545 2801 32 3245 3305 33 6426 3173 34 4608 1198 35 23 2216 36 7248 3779 37 7762 4595 38 7392 2244 39 3484 2829 40 6271 2135 41 4985 140 42 1916 1569 43 7280 4899 44 7509 3239 45 10 2676 46 6807 2993 47 5185 3258 48 3023 1942 EOF

Compiling Program...

Command line arguments:
Standard Input: Interactive Console Text
×

                

                

Program is not being debugged. Click "Debug" button to start program in debug mode.

#FunctionFile:Line
VariableValue
RegisterValue
ExpressionValue