Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backtracking solution for programming exercise (fitting pipes)

I'm reviewing a programming problem from a local programming contest.

You can download the problem here (pdf). It's in dutch but the pictures will help to understand it.

You receive a m*m grid as input with some pipes and some missing spots (the questionmarks). The remaining pipes have to be placed in the grid so that they connect with the others.

Each pipe is represented as a letter (see picture on page 2). The letter 'A' has value 1, 'B' has value 2, ..

I tried solving it with backtracking (it doesn't quite work yet). But since the grid can be 10x10 this will be too slow. Can someone suggest a better (faster) solution/approach?

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

#define sz(a) int((a).size())
#define pb push_back

int m, found;
string letters;
vector<int> done;
vector<string> a;

int ok(char letter, int c, int r)
{
    int val = letter - 'A' + 1;

    //checking if no side goes outside
    if (r == 0 && (val & 1))
        return 0;
    if (r == m - 1 && (val & 4))
        return 0;
    if (c == 0 && (val & 8))
        return 0;
    if (c == m - 1 && (val & 2))
        return 0;

    //check if the side is connected the other pipe on the grid
    if (r > 0 && a[r - 1][c] != '?' && (a[r - 1][c] & 4) && !(val & 1))
        return 0;
    if (c > 0 && a[r][c - 1] != '?' && (a[r][c - 1] & 2) && !(val & 8))
        return 0;
    if (r < m - 1 && a[r + 1][c] != '?' && (a[r + 1][c] & 1) && !(val & 4))
        return 0;
    if (c < m - 1 && a[r][c + 1] != '?' && (a[r][c + 1] & 8) && !(val & 2))
        return 0;

    return 1;
}

void solve(int num_placed, int pos)
{
    if (found) return;

    //done
    if (num_placed == sz(letters)) {
        for (int i = 0; i < m; ++i)
            cout << a[i] << endl;
        found = 1;
        return;
    }

    int c = pos % m;
    int r = pos / m;
    if (a[r][c] != '?')
        solve(num_placed, pos + 1);

    //try all the pipes on this position
    for (int i = 0; i < sz(letters); ++i) {
        if (!done[i] && ok(letters[i], c, r)) {
            a[r][c] = letters[i];
            done[i] = 1;
            solve(num_placed + 1, pos + 1);
            done[i] = 0;
            a[r][c] = '?';
        }
    }
}

int main()
{
    freopen("input.txt", "r", stdin);

    int n;
    cin >> n;

    while (n--) {
        cin >> m;
        cin >> letters;

        cout << m << endl;
        a.clear();
        for (int i = 0; i < m; ++i) {
            string line;
            cin >> line;
            a.pb(line);
        }

        done = vector<int>(sz(letters), 0);

        found = 0;
        solve(0, 0);
    }

    return 0;
}
like image 750
Jasper Avatar asked Mar 13 '12 17:03

Jasper


People also ask

What is backtracking programming?

Backtracking is an algorithmic technique that considers searching in every possible combination for solving a computational problem. It is known for solving problems recursively one step at a time and removing those solutions that that do not satisfy the problem constraints at any point of time.

What is backtracking algorithm with example?

Backtracking is an algorithmic technique for solving problems recursively by trying to build a solution incrementally, one piece at a time, removing those solutions that fail to satisfy the constraints of the problem at any point of time (by time, here, is referred to the time elapsed till reaching any level of the ...

What is back tracking methodology and when can we use back tracking explain with an example?

Backtracking is a technique based on algorithm to solve problem. It uses recursive calling to find the solution by building a solution step by step increasing values with time. It removes the solutions that doesn't give rise to the solution of the problem based on the constraints given to solve the problem.

What is difference between backtracking and dynamic programming?

What are the differences between dynamic programming and backtracking? Dynamic programming emphasizes on overlapping subproblems, while backtracking focus on all or some solutions. Dynamic programming relies on the principle of optimality, while backtracking uses a brute force approach.


2 Answers

original reply

do you have to write all the code yourself or are you interested in exploring other tools? because i would suggest looking at constraint propagation / linear programming. you already have a lot of boundary constraints - that the outer edges cannot have pipes, plus the inner edges - so i imagine this will work quite efficiently. also, the constraints look like they will be simple equalities, so it should be pretty easy to set up.

i don't have enough experience with this to give more details here (although if i have time in the next week i may give it a go at some point), but if this kind of thing is interesting there's a bit more background in another answer i wrote some time ago.

ps interesting question; thanks for posting this.

[edit: if you cannot use other libraries then you can do the constraint propagation yourself. there's a wonderful article by norvig that shows how to do this for sudoku. i would strongly suggest reading that - i think you will see how to carry the techniques across, even though it's sudoku and python.]

updated reply (2012-04-06 - updated with blog references; old comments were buggy)

a depth-first search, where the next empty cell is filled with each available, consistent tile, and the consistency check includes both edge constraints (no pipes off the edge) and nearest neighbours, is quite efficient.  i have an unoptimized implementation in clojure that will solve the smaller example in around 0.4ms (1000 in 360ms after JVM warmup) and the larger in 3ms (cedric van goethem reports 1ms for an optimised - but still OO - java implementation, which seems reasonable).  it can solve a 10x10 puzzle (concentric circles of tubes with no initial hints) in 12s.

i also spent time looking at a "smart" approach, which tracks constraints on each cell, much like in norvig's paper above. and then i tried using choco. all this is described in much more detail in blog posts here (i did have more details in an update to this answer, but they were based on buggy code - the blog has more, better information). the source is also available for download.

the general conclusion from all this was that direct search is fine up to 10x10. after that, more smarts may help, but it's difficult to be sure because generating test cases is hard (they tend to be ambiguous, even when lots of cells are given).

like image 65
35 revs Avatar answered Oct 20 '22 17:10

35 revs


Nice problem. I've found a solution in O(n·m·8^m), which seems to be sufficent.

  1. Focus on the border between the first and the second row. There are 2^m possibilities (outgoing line or not, for each side). This will be the upper border line, the lower border line will be no connection at each side.

  2. For each pair of lower border line and upper border line (which will be 2^m·2^m = 4^m pairs), calculate each row that fits in. If you come from the left, you are given the left side, the top side and the bottom side, so you only have 2 possibilities. If the tile you look at is fixed in the map, check that it fits in and abort otherwise. Call this recursively and you get 2^m rows each or 4^m*2^m=8^m in total.

  3. While the last step was pure brute force, we use DP this time. Safe the tuple (border, bricks used, row) in an array of arrays. array[0] will contain a single tuple (empty-border, no-brick-used, nothing). array[n] contains all 8^m generated rows in row n (starting at 1) combined with each item in array[n-1] it fits on (i.e. the border of the item is the same as the lower border of the row) Notice that if you smartly combine this condition with the loop in step 2, this costs you nothing.

  4. Remove all tuples which needs more bricks of one sort than available and sort the array. Then continue on step 2 until all rows are handled.

  5. If you have finished, check in array[n] for the tuple (empty-border, all-bricks-used, row). Since your task description implies that it exists, print out its row. Then look at the lower border of row and search for that in array[n-1] and print it too, and so on.

Hope you understand my English.

like image 37
ipc Avatar answered Oct 20 '22 15:10

ipc