diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..fceb480
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..35af608
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [2021] [Samuel C. Donovan]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a87286d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+# Held-Karp implementation for solving the TSP
+
+This is my implementation of the Held-Karp dynamic programming algorithm. It is an exact algorithm, and as such, returns the optimal path and cost for a given data set.
+
+## Why Held-Karp?
+The problem for which this project was designed for required an exact solution for all n <= 20 in less than one minute.
+This algorithm has a proposed time complexity of O(n^2 * 2n). For n = 20: 20^2 * (2*20) ≈ 16000, which is drastically lower than one minute. Though my implementation runs quite a bit slower than this, (for n = 20, it runs in approx. 20 seconds) it will still run in less than one minute for n <= 20, and will find the shortest path and its cost.
+For n > 20, the running time starts to get exponentially larger. It becomes infeasible to run this implementation on n > 30, where the running time is several hours long. This is not necessarily due to an issue with my implementation, but is more of an issue with the algorithm and the TSP in general, as the TSP is an NP-Hard problem and as such, finding exact solutions becomes increasingly more expensive the larger n is.
diff --git a/bin/HashTable.class b/bin/HashTable.class
new file mode 100644
index 0000000..80535b9
Binary files /dev/null and b/bin/HashTable.class differ
diff --git a/bin/HeldKarp.class b/bin/HeldKarp.class
new file mode 100644
index 0000000..5cfcc11
Binary files /dev/null and b/bin/HeldKarp.class differ
diff --git a/bin/Main.class b/bin/Main.class
new file mode 100644
index 0000000..5e58a84
Binary files /dev/null and b/bin/Main.class differ
diff --git a/bin/Node.class b/bin/Node.class
new file mode 100644
index 0000000..8968656
Binary files /dev/null and b/bin/Node.class differ
diff --git a/data/test1-2020.txt b/data/test1-2020.txt
new file mode 100644
index 0000000..cd30e8b
--- /dev/null
+++ b/data/test1-2020.txt
@@ -0,0 +1,12 @@
+1 110 40
+2 2 63
+3 172 25
+4 182 68
+5 192 39
+6 144 81
+7 76 31
+8 192 73
+9 16 63
+10 80 78
+11 70 79
+12 36 8
\ No newline at end of file
diff --git a/data/test1-21.txt b/data/test1-21.txt
new file mode 100644
index 0000000..76e8493
--- /dev/null
+++ b/data/test1-21.txt
@@ -0,0 +1,12 @@
+1 85 93
+2 80 35
+3 38 19
+4 65 9
+5 38 5
+6 52 80
+7 76 84
+8 94 41
+9 99 63
+10 30 47
+11 53 98
+12 80 43
\ No newline at end of file
diff --git a/data/test2-2020.txt b/data/test2-2020.txt
new file mode 100644
index 0000000..b84d983
--- /dev/null
+++ b/data/test2-2020.txt
@@ -0,0 +1,14 @@
+1 -34 85
+2 49 184
+3 85 18
+4 72 164
+5 -22 -8
+6 -8 0
+7 -53 172
+8 -84 58
+9 19 73
+10 69 26
+11 29 4
+12 -6 151
+13 67 90
+14 0 77
\ No newline at end of file
diff --git a/data/test2-21.txt b/data/test2-21.txt
new file mode 100644
index 0000000..da440ab
--- /dev/null
+++ b/data/test2-21.txt
@@ -0,0 +1,14 @@
+1 -63 35
+2 99 91
+3 73 0
+4 27 20
+5 -6 -52
+6 48 -146
+7 -19 -127
+8 4 -22
+9 -36 -28
+10 91 -191
+11 -24 -38
+12 82 -169
+13 82 31
+14 -37 -16
\ No newline at end of file
diff --git a/data/test3-2020.txt b/data/test3-2020.txt
new file mode 100644
index 0000000..6c38046
--- /dev/null
+++ b/data/test3-2020.txt
@@ -0,0 +1,18 @@
+1 10834 18658
+2 30756 3345
+3 19970 24600
+4 14669 25177
+5 29650 8784
+6 24669 18572
+7 3349 10191
+8 6553 8852
+9 4889 2771
+10 13418 28914
+11 30610 25659
+12 13919 18570
+13 -16 23422
+14 20855 21944
+15 15444 29757
+16 24671 11060
+17 4399 19731
+18 0 0
\ No newline at end of file
diff --git a/data/test3-21.txt b/data/test3-21.txt
new file mode 100644
index 0000000..2e8b0d1
--- /dev/null
+++ b/data/test3-21.txt
@@ -0,0 +1,17 @@
+1 26378 4886
+2 30564 320
+3 12319 16264
+4 3251 28593
+5 4817 21078
+6 25713 3087
+7 8323 628
+8 26627 4370
+9 10504 19168
+10 5242 2316
+11 31388 65
+12 23941 27464
+13 30708 1016
+14 15295 22466
+15 9403 24404
+16 15454 27513
+17 16349 13418
\ No newline at end of file
diff --git a/data/test4-2020.txt b/data/test4-2020.txt
new file mode 100644
index 0000000..f969267
--- /dev/null
+++ b/data/test4-2020.txt
@@ -0,0 +1,32 @@
+1 27985 147343
+2 20160 3508
+3 27960 214746
+4 14294 25899
+5 26573 3634
+6 773 11455
+7 14381 36932
+8 5279 72300
+9 14349 11721
+10 21645 69622
+11 25098 31683
+12 3595 273115
+13 1533 129427
+14 22560 111589
+15 5798 129816
+16 9629 58991
+17 25167 3126
+18 27305 248606
+19 7109 121380
+20 25743 36309
+21 6773 102159
+22 14012 35326
+23 6030 2565
+24 5849 78210
+25 22839 133162
+26 22688 60633
+27 8192 91821
+28 25685 71043
+29 21098 125138
+30 27342 13142
+31 22831 219085
+32 19628 22899
\ No newline at end of file
diff --git a/data/test4-21.txt b/data/test4-21.txt
new file mode 100644
index 0000000..da307a8
--- /dev/null
+++ b/data/test4-21.txt
@@ -0,0 +1,27 @@
+1 72998 904383
+2 17182 401303
+3 42526 103033
+4 33855 446405
+5 38508 281016
+6 88516 456574
+7 79887 508613
+8 44426 328114
+9 14075 59229
+10 3342 981173
+11 18366 400905
+12 92080 564993
+13 43136 259655
+14 15968 820490
+15 27865 942274
+16 9564 263346
+17 65022 596732
+18 45058 619031
+19 32666 238232
+20 51384 672851
+21 88632 343223
+22 66179 840350
+23 76844 529953
+24 19202 711999
+25 12022 731249
+26 94124 453766
+27 60316 3439
\ No newline at end of file
diff --git a/data/train1.txt b/data/train1.txt
new file mode 100644
index 0000000..3d505f8
--- /dev/null
+++ b/data/train1.txt
@@ -0,0 +1,4 @@
+1 1 1
+2 5 5
+3 10 3
+4 2 7
\ No newline at end of file
diff --git a/data/train2.txt b/data/train2.txt
new file mode 100644
index 0000000..11df281
--- /dev/null
+++ b/data/train2.txt
@@ -0,0 +1,8 @@
+1 38 20
+2 39 26
+3 40 25
+4 36 23
+5 38 13
+6 37 20
+7 41 9
+8 36 -5
\ No newline at end of file
diff --git a/data/train3.txt b/data/train3.txt
new file mode 100644
index 0000000..21bab1a
--- /dev/null
+++ b/data/train3.txt
@@ -0,0 +1,9 @@
+1 16 96
+2 46 94
+3 20 32
+4 22 43
+5 25 57
+6 22 16
+7 20 87
+8 17 16
+9 66 97
\ No newline at end of file
diff --git a/src/HashTable.java b/src/HashTable.java
new file mode 100644
index 0000000..7e5d9ab
--- /dev/null
+++ b/src/HashTable.java
@@ -0,0 +1,243 @@
+/**
+ * HashTable.java
+ *
+ * @author Samuel C. Donovan
+ * Created: 01/11/2021
+ * Updated: 10/12/2021
+ *
+ * HashTable class, responsible for storing and retrieving
+ * subsets in relatively fast time (O(1) if there are few collisions)
+ */
+public class HashTable {
+
+ int size;
+ int total; /* total number of nodes currently in the table,
+ used to check if a resize is required */
+ Node[] table;
+
+ /**
+ * HashTable constructor, sets the initial size of the hash
+ * table and then fills the table with null
+ *
+ * @param size Initial hash table size
+ */
+ public HashTable(int size) {
+ this.size = size;
+ this.total = 0; /* sets total to 0 as nothing has yet been inserted */
+ this.table = new Node[size];
+
+ for (int pos = 0; pos < size; pos++)
+ table[pos] = null; /* set every position in the table to null */
+ }
+
+ /**
+ * Creates a hash code for the given subset. Ideally this hash code would be unique
+ * to help avoid collisions but as the number of cities for the TSP increases,
+ * it becomes more and more unlikely that the hash codes will be unique. Originally,
+ * this hash function used bitshifting to try to generate a unique value but I found this
+ * caused many more collisions than the current method, which involves prime multiplication.
+ *
+ * @param subset Subset to be hashed
+ * @return int Hash code of the subset
+ */
+ public int hash(int[] subset) {
+
+ long hashCode = 17; /* hash code starts at a prime, this helps increase uniqueness */
+ int hashPrime = 31;
+
+ /* for each value in the subset, multiple the hash code by hashPrime, 31.
+ Using primes has been found to increase uniqueness of hash functions */
+ for (int pos = 0; pos < subset.length; pos++)
+ hashCode += hashCode * hashPrime + subset[pos] + pos;
+
+ /* the hashCode can get so large that it exceeds the long max,
+ and so in order to make it a valid table position, it is multiplied
+ by -1 if it is less than 0. This is a workaround and as such is not ideal. */
+ if (hashCode < 0)
+ hashCode *= -1;
+
+ hashCode = Long.parseUnsignedLong("" + hashCode);
+
+ /* mod by the size of the table to ensure that the hash code
+ is within the range of the table size */
+ hashCode = hashCode % size;
+
+ /* the hash must be cast to an int because java primitive arrays do not
+ allow longs to be used as their size or indexes. this is not optimal
+ as it means there will be more collisions but does not drastically
+ affect the performance of this hash table so will suffice for this implementation */
+ int hash = (int) hashCode;
+ return hash;
+ }
+
+ /**
+ * Finds the position of a subset in the table. The method used for this is linear probing
+ * i.e. if the subset is not at the hash code value, it linearly searches from that hash code
+ * position until the subset is found. Whilst this may seem slow, with a good hash function
+ * there should be few collisions and so most subsets should be at their hash code value
+ *
+ * @param subset Subset to search for
+ * @return int The position of the subset in the table. Returns
+ * the size of the table (out of bounds) if the subset is not found
+ */
+ public int position(int[] subset) {
+ int hashCode = hash(subset);
+ int pos = hashCode; // set current index to the hash code of the subset
+
+ /* probes through the table until the subset is found, then returns
+ that index. Must run at least once. */
+ do {
+
+ if (table[pos] != null && arrayEquals(subset, table[pos].subset))
+ return pos;
+
+ /* if the end of the hash table has been reached, loop back round to the start of the table */
+ if (pos == size - 1)
+ pos = 0;
+ else
+ pos++;
+
+ } while (table[pos] != null && pos != hashCode);
+
+ return size; // returns out of bounds if subset not found
+ }
+
+ /**
+ * Search uses the position function to search for a subset
+ *
+ * @param subset Subset to search for
+ * @return Node The node associated to the subset or null if it does not exist
+ */
+ public Node search(int[] subset) {
+ int pos = position(subset);
+
+ /* if the index is out of bounds, the subset
+ does not exist in the table */
+ if (pos == size)
+ return null;
+ else
+ return table[pos];
+ }
+
+ /**
+ * Puts a new node into the table, uses linear probing if there is a collision
+ *
+ * @param subset Subset of the new node
+ * @param cost Cost of the new node
+ * @param previousCity Previous city of the new node
+ */
+ public void put(Node newNode) {
+
+ int pos = hash(newNode.subset); // get hash code of new subset
+
+ /* linear probe until an empty position is found or a subset
+ that is equal to the new subset is found (this should not happen).
+ Only runs if position at the hash code of the new subsets is not empty */
+ while (table[pos] != null && !arrayEquals(newNode.subset, table[pos].subset)) {
+
+ if (pos == size - 1)
+ pos = 0;
+ else
+ pos++;
+ }
+
+ if (table[pos] != null && arrayEquals(newNode.subset, table[pos].subset)) {
+ table[pos].cost = newNode.cost; /* updates cost of subset if it
+ already exists in the table */
+ } else {
+
+ /* insert the new node at the position in the table */
+ table[pos] = newNode;
+ total++;
+
+ }
+
+ /* if the current total number of nodes is 50% of the size,
+ double the size of the table. This helps to avoid collisions and
+ maintain near constant time for get and put */
+ if (total >= size / 2)
+ resize(size * 2);
+
+ }
+
+ /**
+ * Resizes the table based on the current total, helps with avoiding collisions.
+ * This is a costly operation and will drastically slow down the Held-Karp algorithm
+ * if it is used many times, but this can be avoided if the initial size is set appropriately
+ *
+ * @param newSize New size of the hash table
+ */
+ public void resize(int newSize) {
+ Node[] tempTable = table; // store table in temporary array
+ int[] tempSubset;
+ double tempCost;
+ int tempPreviousCity;
+
+ int oldSize = size;
+
+ /* create new table with the new size and fill it with null */
+ table = new Node[newSize];
+ for (int pos = 0; pos < newSize; pos++)
+ table[pos] = null;
+
+ this.size = newSize; /* set size to new size */
+
+ /* fill new table with all of the nodes from the old table */
+ for (int pos = 0; pos < oldSize; pos++) {
+ if (tempTable[pos] != null) {
+
+ tempSubset = tempTable[pos].subset;
+ tempCost = tempTable[pos].cost;
+ tempPreviousCity = tempTable[pos].previousCity;
+
+ tempTable[pos] = null;
+
+ put(new Node(tempSubset, tempCost, tempPreviousCity));
+ total--; /* minus from total because the put function will increase the total */
+ }
+ }
+ }
+
+ /**
+ * Helper function that checks if two arrays are equal
+ *
+ * @param originalArray The first array
+ * @param comparisonArray The array to compare to
+ * @return boolean True if the arrays have equal elements in the same positions, false otherwise
+ */
+ public boolean arrayEquals(int[] originalArray, int[] comparisonArray) {
+
+ if (originalArray.length != comparisonArray.length)
+ return false; // if array lengths aren't equal, returns false
+
+ int count = 0;
+
+ for (int pos = 0; pos < originalArray.length; pos++) {
+
+ /* add one to count if elements in the same position are equal */
+ if (originalArray[pos] == comparisonArray[pos])
+ count++;
+ }
+
+ /* returns true if count is equal to the length of the original array */
+ return count == originalArray.length ? true : false;
+ }
+
+ /**
+ * toString override
+ *
+ * @return String Contains all nodes in the table
+ */
+ @Override
+ public String toString() {
+ String output = "";
+
+ for (int pos = 0; pos < size; pos++) {
+ if (table[pos] != null)
+ output += pos + " = " + table[pos] + "\n";
+ }
+
+ return output;
+ }
+
+}
diff --git a/src/HeldKarp.java b/src/HeldKarp.java
new file mode 100644
index 0000000..731a2d1
--- /dev/null
+++ b/src/HeldKarp.java
@@ -0,0 +1,294 @@
+
+import java.util.ArrayList;
+
+/**
+ * HeldKarp.java
+ *
+ * @author Samuel C. Donovan
+ * Created: 20/10/2021
+ * Updated: 10/11/2021
+ *
+ * HeldKarp class, contains all necessary methods for this implementation of the
+ * Held-Karp algorithm. This implementation is based on the pseudocode
+ * found in Ayoub Abraich's paper: PROJET: PROBLÈME DU VOYAGEUR DE COMMERCE.
+ */
+public class HeldKarp {
+
+ HashTable hashTable;
+ double[][] distanceMatrix;
+ ArrayList set = new ArrayList<>();
+
+ public HeldKarp(double[][] distanceMatrix) {
+ this.distanceMatrix = distanceMatrix;
+
+ /* if the number of cities is less than 15, the hash table size
+ is 1 bitshift by the number of cities, and then squared.
+ if the number of cities is more than 15, to avoid resizing,
+ the size is set to Integer.MAX - 2 billion. Whilst this is not
+ a robust method for solving the issue of resizing, for the purposes
+ of this implementation, it ensures that for N > 15, the algorithm runs
+ in a relatively fast amount of time */
+ int hashTableSize = distanceMatrix.length < 15 ? (1 << distanceMatrix.length) * (1 << distanceMatrix.length)
+ : Integer.MAX_VALUE - 2000000000;
+
+ this.hashTable = new HashTable(hashTableSize);
+
+ /* add all of the cities into the set */
+ for (int city = 1; city < distanceMatrix.length; city++)
+ set.add(city);
+ }
+
+ /**
+ * This is the main function of the algorithm, which follows the steps of
+ * the pseudocode, as described above. This theoretical time complexity
+ * of this algorithm is O(n^2 * 2n), so becomes impractical after about 20 cities.
+ * This implementation is a bottom-up approach, the smaller tasks are computed first and used
+ * to save time when computing the larger tasks later.
+ */
+ public void solveTSP() {
+
+ ArrayList> subsets;
+ int firstCity = 0;
+
+ /* put all of the sets with 1 city in them into the hash table.
+ Their costs are the cost from 0 to that city, and previous city is 0 */
+ for (int city = 1; city < distanceMatrix.length; city++) {
+
+ int[] currentSet = { city }; /* make a subset containing the current city */
+
+ hashTable.put(new Node(currentSet, distanceMatrix[firstCity][city], firstCity));
+ }
+
+ /* this is the main loop of the algorithm, it directly follows the pseudocode.
+ it starts from subsetSize 2, as all subsets of size 1 were previously inserted
+ into the hash table */
+ for (int subsetSize = 2; subsetSize < distanceMatrix.length; subsetSize++) {
+
+ subsets = getSubsetsOfSize(subsetSize);
+
+ /* it then loops through all subsets of size subsetSize */
+ for (ArrayList subset : subsets) {
+
+ /* for each city in the subset, find the minimum cost combination/subset
+ for that city, and put it into the hash table. the first iterations of thse nested loops will
+ be the most costly, calculating the minimum for a large number of combinations, but
+ these calculations are used for subsequent subsets to drastically save time */
+ for (int city : subset) {
+
+ hashTable.put(findMinimumCostSet(city, subset));
+ }
+ }
+ }
+ /* after all subsets and combinations have been put into the hash table,
+ the first city is added back to the main set and backtracking is used to
+ retrieve the shortest path and the best cost */
+ set.add(0, firstCity);
+
+ Node bestNode = findMinimumCostSet(0, set);
+ int[] bestSubset = bestNode.subset;
+ double bestCost = bestNode.cost;
+
+ findBestPath(bestSubset);
+ System.out.println("Cost = " + bestCost);
+ }
+
+ /**
+ * Calling function of the recursive subset function, passes the
+ * subset size to that recursive function
+ *
+ * @param subsetSize The required size of the subsets
+ * @return ArrayList containing all of the subsets
+ */
+ public ArrayList> getSubsetsOfSize(int subsetSize) {
+ ArrayList> subsets = new ArrayList<>();
+ getSubsetsRecursive(0, subsetSize, subsets, new ArrayList<>());
+ return subsets;
+ }
+
+ /**
+ * Recursive function for finding all subsets of a given size.
+ * Called in the getSubsetsOfSize() function.
+ *
+ * @param nextPos Next position in the set; increases by one during every recursive call
+ * @param subsetSize Current subset size, used to ensure all subsets are of the input size
+ * @param subsets List containing all created subsets
+ * @param currentSubset The current subset that is being created
+ */
+ public void getSubsetsRecursive(int nextPos, int subsetSize, ArrayList> subsets,
+ ArrayList currentSubset) {
+
+ /* if the current subsets' size is equal to the specified size,
+ the subset is complete and can be added to the list of subsets */
+ if (currentSubset.size() == subsetSize) {
+ subsets.add(new ArrayList<>(currentSubset));
+ return; // break out of this instance of the recursive call
+ }
+
+ /* for each index, this loop adds the current city into the current subset,
+ and then calls this method on the next city. Once that instance of the recursive
+ call has reached the specified subset size, it finishes and this loop moves
+ back onto the original recursive call, repeating until all subsets have been added */
+ for (int currentPos = nextPos; currentPos < set.size(); currentPos++) {
+
+ currentSubset.add(set.get(currentPos));
+
+ /* add one to currentPos to get the next city */
+ getSubsetsRecursive(currentPos + 1, subsetSize, subsets, currentSubset);
+
+ /* remove the last city in the current subset to ensure that all subsets are the
+ correct size, and to move onto the next city after all of the subsets that have
+ the current city as their first element have been created */
+ currentSubset.remove(currentSubset.size() - 1);
+ }
+ }
+
+ /**
+ * Creates all of the "combinations" of a given array. These combinations
+ * are created by getting an element from the array and setting it as the
+ * first element in the combination, then adding the rest of the elements to
+ * the combination (e.g. a combination for {1, 2, 4, 5} could be {4, 1, 2, 5}).
+ * Aside from the first position, order does not matter.
+ *
+ * @param array int array to get combinations for
+ * @return int[][] all combinations for the given array.
+ */
+ public static int[][] combinations(int[] array) {
+ int[][] combinations = new int[array.length][array.length];
+
+ int currentPos = 0;
+ for (int row = 0; row < array.length; row++) {
+ currentPos = 0;
+
+ /* set the first city of the current combination */
+ combinations[row][currentPos] = array[row];
+
+ for (int column = 0; column < array.length; column++) {
+ /* if the current column is the same as the current row,
+ continue to the next city */
+ if (row == column)
+ continue;
+
+ /* add the rest of the cities to the current combination */
+ combinations[row][++currentPos] = array[column];
+ }
+ }
+ return combinations;
+ }
+
+ /**
+ * Finds the minimum cost of all combinations of a given subset, that lead to
+ * the specified city
+ *
+ * @param city The destination city
+ * @param subset Subset to search through
+ * @return Node containing the subset, cost and previous city of the minimum subset
+ */
+ public Node findMinimumCostSet(int city, ArrayList subset) {
+
+ Node hashNode;
+ double cost = Double.POSITIVE_INFINITY, combinationCost;
+ int previousCity = 0, firstCity = 0;
+
+ ArrayList tempSet = new ArrayList(subset);
+
+ /* remove destination city from subset and convert it to a primitive array.
+ this is the array that will be used to generate different combinations */
+ tempSet.remove(Integer.valueOf(city));
+ int[] setMinusCity = listToArray(tempSet);
+
+ /* set the first position to the destination city, then convert it to a primitive array.
+ this is the subset that will be put into the hash table */
+ tempSet.add(0, city);
+ int[] currentSet = listToArray(tempSet);
+
+ /* for all combinations of the subset without the current city in,
+ find the combination with the lowest cost */
+ int[][] combinations = combinations(setMinusCity);
+
+ for (int[] combination : combinations) {
+
+ hashNode = hashTable.search(combination);
+
+ /* combination cost is equal to the cost of the subset in the hash table
+ plus the cost from the destination city in that subset to the destination city
+ of the current set */
+ combinationCost = hashNode.cost + distanceMatrix[hashNode.subset[0]][city];
+
+ if (combinationCost < cost) {
+ cost = combinationCost;
+ previousCity = hashNode.subset[0];
+
+ /* if the destination city is the first city in the path,
+ current set is equal to this combination. This means that
+ the current set would be the best subset, and would have the
+ optimum cost */
+ if (city == firstCity)
+ currentSet = combination;
+ }
+ }
+ return new Node(currentSet, cost, previousCity);
+
+ }
+
+ /**
+ * Prints the best path using the final subset, and backtracks through the
+ * previous cities to form the shortest path.
+ *
+ * @param finalSubset The final subset retrieved from the algorithm. This subset
+ * will contain all cities aside from the first city (0 in this case)
+ */
+ public void findBestPath(int[] finalSubset) {
+ int[] previousSubset;
+ int[] currentSubset = finalSubset;
+ int currentCity;
+
+ /* print the path, starting from 0 (the paths always start from 0 in
+ this implementation), and then print the first city from the final subset */
+ System.out.print("Path = 0 -> " + finalSubset[0] + " -> ");
+
+ /* these loops handle the backtracking of Held-Karp; they search
+ the hash table for the current subset, retrieve its previous
+ city, remove that city from the current subset and repeat until
+ the subset is empty */
+ for (int size = finalSubset.length - 1; size > 0; size--) {
+
+ /* find the current path and print its previous city to
+ form part of the shortest path */
+ currentCity = hashTable.search(currentSubset).previousCity;
+ System.out.print(currentCity + " -> ");
+
+ previousSubset = currentSubset;
+
+ currentSubset = new int[size]; // set currentSubset to an empty
+ // array of the current size
+ currentSubset[0] = currentCity;
+
+ for (int index1 = 1, index2 = index1; index1 < currentSubset.length; index1++, index2++) {
+
+ /* fill current subset with cities from previous subset, minus the current city */
+ if (previousSubset[index2] == currentCity) {
+ index1--;
+ continue;
+ }
+ currentSubset[index1] = previousSubset[index2];
+ }
+ }
+ System.out.print("0"); /* print 0 again to form a complete circuit in the path */
+ System.out.println();
+ }
+
+ /**
+ * Helper function that converts an ArrayList to a primitive int array
+ *
+ * @param listToConvert ArrayList to be converted
+ * @return int[] Converted int array
+ */
+ public int[] listToArray(ArrayList listToConvert) {
+ int[] convertedArray = new int[listToConvert.size()];
+
+ for (int pos = 0; pos < listToConvert.size(); pos++)
+ convertedArray[pos] = listToConvert.get(pos);
+
+ return convertedArray;
+ }
+}
diff --git a/src/Main.java b/src/Main.java
new file mode 100644
index 0000000..6726dd0
--- /dev/null
+++ b/src/Main.java
@@ -0,0 +1,95 @@
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Scanner;
+
+/**
+ * Main.java
+ *
+ * @author Samuel C. Donovan
+ *
+ * INSTRUCTIONS:
+ * Line 25 searches for a file in the current user directory, where this project is being run.
+ * To run this on different files, locate the String variable 'dataFile' below (line 25),
+ * and change the last section of text to the file name. It must include the file extension (i.e. .txt).
+ * Then, run the program and a distance matrix will be created from the data file, and the Held-Karp
+ * algorithm will be run on that matrix, printing out the best path, cost and running time
+ * after it is finished.
+ */
+public class Main {
+
+ public static void main(String[] args) {
+
+ long startTime = System.nanoTime(); /* start the timer */
+
+ String dataFile = System.getProperty("user.dir") + File.separator + "data" + File.separator + "test3-21.txt";
+ System.out.println("Loading from " + dataFile);
+
+ File file = new File(dataFile); /* create file object to use a scanner on */
+
+ double[][] distanceMatrix = new double[0][0];
+
+ try {
+ /* convert the file to a string so it can be split by newlines */
+ String fileString = new Scanner(file).useDelimiter("\\A").next();
+
+ /* split the file on newline to get each line of data */
+ String[] fileArray = fileString.split("\\n");
+
+ /* distance matrix lengths are equal to the number of cities/lines in the file */
+ distanceMatrix = new double[fileArray.length][fileArray.length];
+
+ /* delimit the file by either spaces or tabs */
+ String fileDelimiter = fileArray[0].contains(" ") ? " " : "\\t";
+
+ String[] fromCityData, toCityData;
+
+ System.out.println("File loaded.");
+
+ for (int fromCity = 0; fromCity < fileArray.length; fromCity++) {
+
+ /* split current line by the file delimiter, and replace any \n found.
+ this forms the data for the fromCities */
+ fromCityData = fileArray[fromCity].trim().replaceAll("\n ", "").split(fileDelimiter);
+
+ for (int toCity = 0; toCity < fileArray.length; toCity++) {
+
+ /* do the same for the toCity's data, a nested loop is required
+ to get the data for both cities and calculate the distance between them */
+ toCityData = fileArray[toCity].trim().replaceAll("\n ", "").split(fileDelimiter);
+
+ /* if the fromCity and toCity are the same, the distance is infinity */
+ if (fromCity == toCity)
+ distanceMatrix[fromCity][toCity] = Double.POSITIVE_INFINITY;
+ else
+ /* otherwise, calculate the Euclidean distance for the two cities and
+ put that distance into the distance matrix */
+ distanceMatrix[fromCity][toCity] = Math.sqrt(Math
+ .pow((Integer.parseInt(toCityData[1]) - Integer.parseInt(fromCityData[1])), 2)
+ + Math.pow((Integer.parseInt(toCityData[2]) - Integer.parseInt(fromCityData[2])), 2));
+ }
+ }
+
+ } catch (FileNotFoundException fileNotFound) { /* if file is not found, stop the program */
+ System.out.println("File not found at " + dataFile);
+ return;
+ } finally {
+
+ /* construct a HeldKarp object on the distance matrix, and then call the solveTSP()
+ function which will find the best path and its cost */
+ HeldKarp heldKarp = new HeldKarp(distanceMatrix);
+
+ System.out.println("Running Held-Karp.\n");
+ heldKarp.solveTSP();
+
+ /* calculate running time of the algorithm */
+ long endTime = System.nanoTime();
+ long totalTime = endTime - startTime;
+
+ double seconds = (double) totalTime / 1000000000.0;
+
+ System.out.println("Running time = " + totalTime + " nano seconds, " + seconds + " seconds");
+ }
+
+ }
+
+}
diff --git a/src/Node.java b/src/Node.java
new file mode 100644
index 0000000..a0aaa66
--- /dev/null
+++ b/src/Node.java
@@ -0,0 +1,44 @@
+/**
+ * Node.java
+ *
+ * @author Samuel C. Donovan
+ * Created: 29/11/2021
+ * Updated: 10/12/2021
+ *
+ * Node holds the subset along with its associated cost
+ * and previous city. These are stored in the hash table,
+ * with the subset as their key.
+ */
+public class Node {
+
+ int[] subset;
+ double cost;
+ int previousCity; /* the city that this subset comes from,
+ used for backtracking and forming a path */
+
+ /**
+ * Node constructor
+ *
+ * @param subset Subset of cities
+ * @param cost Associated cost with that subset
+ * @param previousCity City which this subset comes from
+ */
+ public Node(int[] subset, double cost, int previousCity) {
+ this.subset = subset;
+ this.cost = cost;
+ this.previousCity = previousCity;
+ }
+
+ /**
+ * toString override, prints out the subset as well as its cost and previous
+ * city
+ */
+ @Override
+ public String toString() {
+ String output = "[";
+ for (int pos = 0; pos < subset.length; pos++)
+ output += subset[pos] + ", ";
+
+ return output += "], cost= " + cost + ", prev city= " + previousCity;
+ }
+}