import javax.swing.*; import java.awt.*; import java.util.Random; // https://en.wikipedia.org/wiki/Lin–Kernighan_heuristic public class LinKernighan2 extends JFrame { Random rnd = new Random(1); int n = rnd.nextInt(300) + 250; double[] x = new double[n]; double[] y = new double[n]; int[] bestState; double bestDist = Double.POSITIVE_INFINITY; { for (int i = 0; i < n; i++) { x[i] = rnd.nextDouble(); y[i] = rnd.nextDouble(); } } public void linKernighan() { int[] curState = optimize(getRandomPermutation(n)); double curDist = eval(curState); updateBest(curState, curDist); for (boolean improved = true; improved; ) { improved = false; for (int rev = -1; rev <= 1; rev += 2) { for (int i = 0; i < n; i++) { int[] p = new int[n]; for (int j = 0; j < n; j++) p[j] = curState[(i + rev * j + n) % n]; boolean[][] added = new boolean[n][n]; double cost = eval(p); double delta = -dist(x[p[n - 1]], y[p[n - 1]], x[p[0]], y[p[0]]); for (int k = 0; k < n; k++) { double best = Double.POSITIVE_INFINITY; int bestPos = -1; for (int j = 1; j < n - 2; j++) { if (added[p[j]][p[j + 1]]) continue; double addedEdge = dist(x[p[n - 1]], y[p[n - 1]], x[p[j]], y[p[j]]); if (delta + addedEdge > 0) continue; double removedEdge = dist(x[p[j]], y[p[j]], x[p[j + 1]], y[p[j + 1]]); double cur = addedEdge - removedEdge; if (best > cur) { best = cur; bestPos = j; } } if (bestPos == -1) break; added[p[n - 1]][p[bestPos]] = true; added[p[bestPos]][p[n - 1]] = true; delta += best; reverse(p, bestPos + 1, n - 1); double closingEdge = dist(x[p[n - 1]], y[p[n - 1]], x[p[0]], y[p[0]]); if (curDist > cost + delta + closingEdge) { curDist = cost + delta + closingEdge; curState = p.clone(); updateBest(curState, curDist); improved = true; break; } } } } } updateBest(curState, curDist); } void updateBest(int[] curState, double curDist) { if (bestDist > curDist) { bestDist = curDist; bestState = curState.clone(); repaint(); } } // reverse order from i to j static void reverse(int[] p, int i, int j) { int n = p.length; while (i != j) { int t = p[j]; p[j] = p[i]; p[i] = t; i = (i + 1) % n; if (i == j) break; j = (j - 1 + n) % n; } } double eval(int[] state) { double res = 0; for (int i = 0, j = state.length - 1; i < state.length; j = i++) res += dist(x[state[i]], y[state[i]], x[state[j]], y[state[j]]); return res; } static double dist(double x1, double y1, double x2, double y2) { double dx = x1 - x2; double dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); } int[] getRandomPermutation(int n) { int[] res = new int[n]; for (int i = 0; i < n; i++) { int j = rnd.nextInt(i + 1); res[i] = res[j]; res[j] = i; } return res; } int[] optimize(int[] p) { int[] res = p.clone(); for (boolean improved = true; improved; ) { improved = false; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i == j || (j + 1) % n == i) continue; int i1 = (i - 1 + n) % n; int j1 = (j + 1) % n; double delta = dist(x[res[i1]], y[res[i1]], x[res[j]], y[res[j]]) + dist(x[res[i]], y[res[i]], x[res[j1]], y[res[j1]]) - dist(x[res[i1]], y[res[i1]], x[res[i]], y[res[i]]) - dist(x[res[j]], y[res[j]], x[res[j1]], y[res[j1]]); if (delta < -1e-9) { reverse(res, i, j); improved = true; } } } } return res; } // visualization code public LinKernighan2() { setContentPane(new JPanel() { protected void paintComponent(Graphics g) { super.paintComponent(g); if (bestState == null) return; ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); ((Graphics2D) g).setStroke(new BasicStroke(3)); int w = getWidth() - 5; int h = getHeight() - 30; for (int i = 0, j = n - 1; i < n; j = i++) g.drawLine((int) (x[bestState[i]] * w), (int) ((1 - y[bestState[i]]) * h), (int) (x[bestState[j]] * w), (int) ((1 - y[bestState[j]]) * h)); g.drawString(String.format("length: %.3f", eval(bestState)), 5, h + 20); } }); setSize(new Dimension(600, 600)); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setVisible(true); new Thread(this::linKernighan).start(); } public static void main(String[] args) { new LinKernighan2(); } }