Optimization of BackTracking algorithm for n queen problem

来源:互联网 发布:python量化投资 编辑:程序博客网 时间:2024/06/05 14:35

I think it is necessary to analyze the efficiency class before optimize it. It is difficult to estimate the efficiency class accurately because the each of recurrence running time is different. So I use a approximate method to estimate the number of nodes generated. The lower bound should be 1+n+n(n-3)+n(n-3)(n-6)+…+ n(n-3)(n-6)…(n-3i)…1 (i<n/3). The upper bound should be 1+n+n(n-2)+n(n-2)(n-4)+…+ n(n-2)(n-4)…(n-2i)…1(i<n/2). Apparently the number of nodes has been generated is between the lower bound and upper bound. Notice that there is no way to improving efficiency on the basis of two bounds unless the algorithm has a revolutionary remodification. Then I limit the optimization to local modification.

The disadvantage mainly is: 1) Using a class to stands for the chess board is wonderful but efficient. The recurrence will keep data of object of chess board that contain several vectors. 2) Before move(), it is advised that check the feasibility of moving as possible in order to avoid the expansive recurrence rather than check it after recurrence.

For situation 1, using delegation is a good idea. That is, a class to encapsulate chess_board class and using a pointer of the class points to chess_board object, all operations is delegated by this class with its pointer. No matter how depth of recurrence has, chess_board object is never need to keep states. Then recurrence will keep only a pointer rather than object. As test proving, this modification improves algorithm performance by a large factor (important: it must get rid of solutions output sentences (last two sentences in write function) because output function consumes almost all time for n larger than 9).

For situation 2, if the checking before move() is in charge, then duplicate_array never has value 2 in it, so change it definition to vector<bool> and removing equality comparisons in move and un_move functions. Note that it is impossible to making this check perfect or else algorithm can generate the permutation directly. The goal of this check function should be perfect as possible as we can. For the sake of confusion, it is wised to rename it by another name: legal_position(int j). There are two matter legal_position should care: 1) j can’t be the number already existed. That is, new chess can’t be the same column with chesses already moved. 2) j can’t be the i+1 or i-1 that i is last moved j. Condition 1) can be achieved by duplicate_array itself and 2) can be achieved by two integral variable.

In addition, the vector<pair<int,int> > does not contribute anything in computation, it is just a container of result. It is necessary to eliminating this redundant container and modifying the implementation of output_functor.

Codes that improved are listed below:

 

 

In the definition of class:

 

 

class chess_board

{

     vector<int> array;

     vector<bool> duplicate_array;

     int integral_check_1;

     int integral_check_2;

public:

     int n;

     int cur_i;

     int number_solution;

 

 

     chess(int dim):integral_check_1(-30000),integral_check_2(-30000),n(dim),cur_i(0),number_solution(0)

     {

         array.resize(n,-30000);

         duplicate_array.resize(n,0);

     }

void move(int j)

     {

         array[cur_i]=j;

         integral_check_1=j+1;

         integral_check_2=j-1;

         //if(duplicate_array[j]==0) because before this move(), legal_pos() already prove that duplicate_array cannt be 0

         duplicate_array[j]=1;

}

void un_move(int j)

     {

         array[cur_i]=-30000;

         integral_check_1=-30000;

         integral_check_2=-30000;

         //if(duplicate_array[j]==1)

            duplicate_array[j]=0;

}

bool legal_pos(int j)

     {

         if(duplicate_array[j] == 0 && j != integral_check_1 && j != integral_check_2)

              return true;

         else

              return false;

}

void write()

     {

         number_solution++;

         for_each(array.begin(), array.end(), output_functor<int>());

         cout<<endl;

}

 

 

////////////////////////////////////////////////////////////////////////////////////////////

 

 

In the definition of output_functor:

 

 

template<class T>

struct output_functor : public unary_function<T,void>

{

     output_functor():n(0){}

     void operator()(const T& p)  //note that this operator() modifying n, so throw off const

     {

         cout<<"("<<n<<","<<p<<")"<<"  ";

         n++;

     }

     int n;

};

 

 

The BackTracking function:

 

 

void BackTracking(chess_board_delegation& c)

{

     if(c.check() == false)

     {c.set_cur_i(c.get_cur_i()-1);return;}

     else

     {

         if(c.get_cur_i() == c.get_n())

         {

              c.write();

              c.set_cur_i(c.get_cur_i()-1);            

return;

         }

         else

         {

              for(int j=0; j<c.get_n(); j++)

              {

                   if(c.legal_pos(j) == false)

                       continue;

                   c.move(j);

                   c.set_cur_i(c.get_cur_i()+1);

                   BackTracking(c);

                   c.un_move(j);

              }

              c.set_cur_i(c.get_cur_i()-1);

         }

     }

}

 

 

 

////////////////////////////////////////////////////////////////////////////////////////////

 

 

class chess_board_delegation

{

     chess_board* cbp;

public:

     chess_board_delegation(chess_board* p):cbp(p){}

     int get_n(){return cbp->n;}

     void set_cur_i(int i){cbp->cur_i = i;}

     int get_cur_i(){return cbp->cur_i;}

     void move(int j){cbp->move(j);}

     void un_move(int j){cbp->un_move(j);}

     bool legal_pos(int j){return cbp->legal_pos(j);}

     void write(){cbp->write();}

     bool check(){return cbp->check();}

     int get_number_solution(){return cbp->number_solution;}

};

 

 

////////////////////////////////////////////////////////////////////////////////////////////

 

 

main function:

 

 

int main()

{

     int n;

     cout<<"input the number of queen";

     cin>>n;

     chess_board a(n);

     chess_board_delegation c(&a);

     clock_t t1 = clock();

     BackTracking(c);

     cout<<c.get_number_solution();

     clock_t t2 = clock();

     cout<<"time took:"<<double(t2-t1)/CLOCKS_PER_SEC;

     system("pause");

     return 0;

}

 

 

The efficiency improvement is concerned with n, the larger n is, the more improvement has. Another general way to optimize program is that decrease the operations in nest loop. In this algorithm, there is a nest loop with two for(;;). So naturally sentences in nest loop should be considered. Review rules of permutation for n queen problem, if the p[i]+d larger than n-1 or p[i]-d smaller than 0, the subsequent equality comparison in loop is useless. It is wised the loop is terminated in such situation. And there is another discovery that is the times of loop is inefficient. Or say, a little bug.J

 

 

The codes 1):

bool check_permutation(const vector<int>& p)

{

     int flag1,flag2,range;

     range = cur_i+1;

     for(int i=0; i<range; i++)

     {

         for(int j=i+1, d=1; j<=range; j++,d++)

         {

              flag1 = p[i]+d; flag2 = p[i]-d;

              if(flag1 < n)

              {

                   if(flag1 == p[j])

                       return false;

              }

              if(flag2 >= 0)

              {

                   if(flag2 == p[j])

                       return false;

              }

         }

     }

     return true;

}

 

 

The speed is supposed to faster than original theoretically. But I found it is slower a bit than follow codes:

 

 

The codes 2):

bool check_permutation(const vector<int>& p)

{

int range = cur_i+1;

     for(int i=0; i<range; i++)

     {

          for(int j=i+1, d=1; j<=range; j++,d++)

         {

              if(p[i]+d == p[j] || p[i]-d == p[j])

                   return false;

         }

     }

     return true;

}

 

 

Why? To simplifying the computation, limit the times of comparison in the path from root to leaf that generates a solution is correct. Comparison of codes 1) is about 2(n+(n-1)+...+1). Before analyze Codes 2) comparisons, there is a more important job to do, consider codes2 in structural manner. To avoid unnecessary equality comparison, it using an equality comparison to avoid an operation that is also an equality comparison sentence, it is doubtful. So called “improvement” brings some additional operation that is two assignments (Using p[i]+-d directly still has problem of double operation of +/- and array reference) so that efficiency has not been improved. Note that it is in nest loop so that price is high. That is why code1 that is more elaborate designed slower than simpler code1. Assuming price of checking flag1,2 is zero, then comparisons of codes2 is between (n+(n-1)+...+1) and 2(n+(n-1)+...+1). It should larger than 1.5(n+(n-1)+...+1) and I guess it is about 1.8or1.9(n+(n-1)+...+1). But to avoid unnecessary equality comparison, it is must use another equality comparison, so it is impossible to attain above times of comparisons. I hope I can give an accurate analysis, may be in next article.