|
34 | 34 | "source": [ |
35 | 35 | "We want to complete a knight's tour on a square board. A knight's tour is a sequence of moves (like the knight in chess) on the board such that each position on the board is visited exactly once.\n", |
36 | 36 | "\n", |
37 | | - "The $1 \\times 1$ board is trivial, there is a single trivial tool. It is immediately clear that there are no tours for $2 \\times 2$ and $3 \\times 3$ boards. Maybe less trivial to see, there is also no tour for a $4 \\times 4$ board. An example of a knight's tour on a $8 \\times 8$ board is given below:\n", |
| 37 | + "The $1 \\times 1$ board is trivial, there is a single trivial tour. It is immediately clear that there are no tours for $2 \\times 2$ and $3 \\times 3$ boards. Maybe less trivial to see, there is also no tour for a $4 \\times 4$ board. An example of a knight's tour on a $8 \\times 8$ board is given below:\n", |
38 | 38 | "\n", |
39 | 39 | "$$\n", |
40 | 40 | " \\begin{array}{cccccccc}\n", |
|
47 | 47 | " 31 & 60 & 51 & 42 & 29 & 8 & 13 & 64 \\\\\n", |
48 | 48 | " 50 & 43 & 30 & 61 & 14 & 63 & 28 & 7 \\\\\n", |
49 | 49 | " \\end{array}\n", |
50 | | - "$$" |
| 50 | + "$$\n", |
| 51 | + "\n", |
| 52 | + "In theory, it would be possible to solve this problem by brute force, i.e., generate permutation of $\\{1, \\ldots, n^2\\}$ and verify whether they form a valid knight's tour, stopping when the first one is found. Unfortunately, the number of permutations grows very fast as $O(n^2!)$. This is prohibitive in practice even for small boards." |
| 53 | + ] |
| 54 | + }, |
| 55 | + { |
| 56 | + "cell_type": "code", |
| 57 | + "execution_count": 2, |
| 58 | + "id": "fbe71852-8a4a-429b-af16-f25632dff99b", |
| 59 | + "metadata": {}, |
| 60 | + "outputs": [ |
| 61 | + { |
| 62 | + "name": "stdout", |
| 63 | + "output_type": "stream", |
| 64 | + "text": [ |
| 65 | + "4x4 board: 2.09e+13\n", |
| 66 | + "5x5 board: 1.55e+25\n", |
| 67 | + "6x6 board: 3.72e+41\n", |
| 68 | + "7x7 board: 6.08e+62\n", |
| 69 | + "8x8 board: 1.27e+89\n", |
| 70 | + "9x9 board: 5.80e+120\n" |
| 71 | + ] |
| 72 | + } |
| 73 | + ], |
| 74 | + "source": [ |
| 75 | + "for n in range(4, 10):\n", |
| 76 | + " print(f'{n}x{n} board: {math.factorial(n**2):.2e}')" |
| 77 | + ] |
| 78 | + }, |
| 79 | + { |
| 80 | + "cell_type": "markdown", |
| 81 | + "id": "4fbd2879-bc58-4d7a-b509-d3d037cd98bc", |
| 82 | + "metadata": {}, |
| 83 | + "source": [ |
| 84 | + "We will have to find a more efficient approach." |
| 85 | + ] |
| 86 | + }, |
| 87 | + { |
| 88 | + "cell_type": "markdown", |
| 89 | + "id": "597510a9-220c-4f26-ac26-8f8624e4f608", |
| 90 | + "metadata": {}, |
| 91 | + "source": [ |
| 92 | + "# Backtracking" |
| 93 | + ] |
| 94 | + }, |
| 95 | + { |
| 96 | + "cell_type": "markdown", |
| 97 | + "id": "a4697336-97da-4616-832d-2f3c547f40af", |
| 98 | + "metadata": {}, |
| 99 | + "source": [ |
| 100 | + "A technique that is useful for a problem of this type is backtracking. Make legal moves until you get stuck or cover the board. In the former case, backtrack to the last position where you could choose, and take another option. This requires bookkeeping of which options where tried at some point in time." |
51 | 101 | ] |
52 | 102 | }, |
53 | 103 | { |
54 | 104 | "cell_type": "markdown", |
55 | 105 | "id": "f39d8b7d-ab28-45e2-8593-ab95314efe9f", |
56 | 106 | "metadata": {}, |
57 | 107 | "source": [ |
58 | | - "# Implementation" |
| 108 | + "## Implementation" |
59 | 109 | ] |
60 | 110 | }, |
61 | 111 | { |
|
244 | 294 | }, |
245 | 295 | { |
246 | 296 | "cell_type": "code", |
247 | | - "execution_count": 5, |
| 297 | + "execution_count": 3, |
248 | 298 | "id": "31e73e9c-6f07-4033-a1c8-919db0086eaa", |
249 | 299 | "metadata": {}, |
250 | 300 | "outputs": [], |
|
718 | 768 | "id": "ef5767e0-5114-4b30-8878-80f0150cd583", |
719 | 769 | "metadata": {}, |
720 | 770 | "source": [ |
721 | | - "# Performance" |
| 771 | + "## Performance" |
722 | 772 | ] |
723 | 773 | }, |
724 | 774 | { |
|
798 | 848 | "id": "c3693282-403e-4fa6-ba41-479774d8dbc3", |
799 | 849 | "metadata": {}, |
800 | 850 | "source": [ |
801 | | - "The runtime increases rapidly." |
| 851 | + "The runtime increases rapidly, and patience runs out for a $9 \\times 9$ board. Can we do better?" |
| 852 | + ] |
| 853 | + }, |
| 854 | + { |
| 855 | + "cell_type": "markdown", |
| 856 | + "id": "fb6418f5-7fa3-4807-90dc-5d4b9f0f7727", |
| 857 | + "metadata": {}, |
| 858 | + "source": [ |
| 859 | + "# Wansdorff's algorithm" |
| 860 | + ] |
| 861 | + }, |
| 862 | + { |
| 863 | + "cell_type": "markdown", |
| 864 | + "id": "835bb1f2-2f60-4943-9fc2-9ada4b88acf6", |
| 865 | + "metadata": {}, |
| 866 | + "source": [ |
| 867 | + "It turns out we can, or rather, Wansdorff could. He proposed a heuristic that works excellently in practice.\n", |
| 868 | + "\n", |
| 869 | + "\n", |
| 870 | + "1. Set P to be a random initial position on the board\n", |
| 871 | + "1. Mark the board at P with the move number “1”\n", |
| 872 | + "1. Do following for each move number from 2 to the number of squares on\n", |
| 873 | + " the board:\n", |
| 874 | + " * let S be the set of positions accessible from P.\n", |
| 875 | + " * Set P to be the position in S with minimum accessibility\n", |
| 876 | + " * Mark the board at P with the current move number\n", |
| 877 | + "1. Return the marked board — each square will be marked with the move\n", |
| 878 | + " number on which it is visited." |
| 879 | + ] |
| 880 | + }, |
| 881 | + { |
| 882 | + "cell_type": "markdown", |
| 883 | + "id": "ea9f45dc-12f9-4c65-b5d8-164ea5b45150", |
| 884 | + "metadata": {}, |
| 885 | + "source": [ |
| 886 | + "## Implementation" |
| 887 | + ] |
| 888 | + }, |
| 889 | + { |
| 890 | + "cell_type": "code", |
| 891 | + "execution_count": 4, |
| 892 | + "id": "43a3ac96-8353-4828-a0fb-ed5f10421136", |
| 893 | + "metadata": {}, |
| 894 | + "outputs": [], |
| 895 | + "source": [ |
| 896 | + "def compute_initial_accessibility(n):\n", |
| 897 | + " return [len(moves) for moves in compute_all_moves(n)]" |
| 898 | + ] |
| 899 | + }, |
| 900 | + { |
| 901 | + "cell_type": "code", |
| 902 | + "execution_count": 11, |
| 903 | + "id": "57130a92-ba10-4c43-a7da-b55f32c36d49", |
| 904 | + "metadata": {}, |
| 905 | + "outputs": [], |
| 906 | + "source": [ |
| 907 | + "def print_accessibility(accessibility):\n", |
| 908 | + " n = math.isqrt(len(accessibility))\n", |
| 909 | + " for i in range(n):\n", |
| 910 | + " print(' '.join(map(lambda x: f'{x:4d}', accessibility[i*n:(i + 1)*n])))" |
| 911 | + ] |
| 912 | + }, |
| 913 | + { |
| 914 | + "cell_type": "code", |
| 915 | + "execution_count": 12, |
| 916 | + "id": "5e4758f3-fa22-4566-bf89-8e90dd563fa7", |
| 917 | + "metadata": {}, |
| 918 | + "outputs": [ |
| 919 | + { |
| 920 | + "name": "stdout", |
| 921 | + "output_type": "stream", |
| 922 | + "text": [ |
| 923 | + " 2 3 4 3 2\n", |
| 924 | + " 3 4 6 4 3\n", |
| 925 | + " 4 6 8 6 4\n", |
| 926 | + " 3 4 6 4 3\n", |
| 927 | + " 2 3 4 3 2\n" |
| 928 | + ] |
| 929 | + } |
| 930 | + ], |
| 931 | + "source": [ |
| 932 | + "print_accessibility(compute_initial_accessibility(5))" |
| 933 | + ] |
| 934 | + }, |
| 935 | + { |
| 936 | + "cell_type": "code", |
| 937 | + "execution_count": 23, |
| 938 | + "id": "fabc1605-b986-4d77-9756-5744f122ba30", |
| 939 | + "metadata": {}, |
| 940 | + "outputs": [], |
| 941 | + "source": [ |
| 942 | + "def wansdorff(n):\n", |
| 943 | + " all_moves = compute_all_moves(n)\n", |
| 944 | + " accessibility =[len(moves) for moves in compute_all_moves(n)]\n", |
| 945 | + " board = [-1]*n\n", |
| 946 | + " pos = 0\n", |
| 947 | + " board[0] = 1\n", |
| 948 | + " for move_nr in range(2, n**2 + 1):\n", |
| 949 | + " pos = min(all_moves[pos], accessibility[k] for k in all_moves[pos] if k not in board)\n", |
| 950 | + " board[pos] = move_nr\n", |
| 951 | + " return board " |
| 952 | + ] |
| 953 | + }, |
| 954 | + { |
| 955 | + "cell_type": "code", |
| 956 | + "execution_count": 24, |
| 957 | + "id": "1cd3254d-c09f-4c57-909f-08fd3f4e9bf5", |
| 958 | + "metadata": {}, |
| 959 | + "outputs": [ |
| 960 | + { |
| 961 | + "ename": "IndexError", |
| 962 | + "evalue": "list assignment index out of range", |
| 963 | + "output_type": "error", |
| 964 | + "traceback": [ |
| 965 | + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", |
| 966 | + "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)", |
| 967 | + "Cell \u001b[0;32mIn[24], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m print_accessibility(wansdorff(\u001b[38;5;241m5\u001b[39m))\n", |
| 968 | + "Cell \u001b[0;32mIn[23], line 9\u001b[0m, in \u001b[0;36mwansdorff\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m move_nr \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[38;5;241m2\u001b[39m, n\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m2\u001b[39m \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m):\n\u001b[1;32m 8\u001b[0m pos \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(accessibility[k] \u001b[38;5;28;01mfor\u001b[39;00m k \u001b[38;5;129;01min\u001b[39;00m all_moves[pos] \u001b[38;5;28;01mif\u001b[39;00m k \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m board)\n\u001b[0;32m----> 9\u001b[0m board[pos] \u001b[38;5;241m=\u001b[39m move_nr\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m board\n", |
| 969 | + "\u001b[0;31mIndexError\u001b[0m: list assignment index out of range" |
| 970 | + ] |
| 971 | + } |
| 972 | + ], |
| 973 | + "source": [ |
| 974 | + "print_accessibility(wansdorff(5))" |
802 | 975 | ] |
803 | 976 | }, |
804 | 977 | { |
|
0 commit comments