1. 程式人生 > >非遞迴棧式回溯與遞迴法的幾個問題

非遞迴棧式回溯與遞迴法的幾個問題

1.迷宮求解

2.八皇后

非遞迴

#include <stdio.h>
#include <math.h>
#include <malloc.h>

void nQueens(int *x, int n);     /*求解n皇后問題*/
int place(int *x, int k);         /*判斷是否可以在第k行第x[k]列擺放皇后*/
void printSolution(int *x, int n);  /*輸出求解結果*/

int main()
{
    int n;
    int *x;                        /*存放求解結果的陣列首地址*/
    scanf("%d", &n);
    x=(int*)malloc(sizeof(int)*(n+1));  /*動態分配陣列空間, x[0]空閒*/
    nQueens(x, n);
    return 0;
}

/*如果一個皇后能放在第k行第x[k]列,則返回真(1),否則返回假(0)*/
int place(int *x, int k)
{
    int i;
    /*對前k-1行,逐行考察*/
    for(i=1; i<k; i++)
    {
        /*如果前k-1行中有某行的皇后與第k行的在同一列或同一斜線,返回0*/
        if((x[i]==x[k])||(fabs(x[i]-x[k])==fabs(i-k)))
            return 0;
    }
    /*能執行下一句,說明在第k行第x[k]列擺放皇后,不會互相攻擊*/
    return 1;
}

/*求解在n×n的棋盤上,放置n個皇后,使其不能互相攻擊*/
void nQueens(int *x, int n)
{
    int k;
    k = 1;    /*k是當前行*/
    x[k] = 0;  /*x[k]是當前列,進到迴圈中,立刻就會執行x[k]++,而選擇了第1列*/
    while(k>0)/*當將所有可能的解嘗試完後,k將變為0,結束求解過程*/
    {
        x[k]++;                      /*移到下一列*/
        while(x[k]<=n && !place(x, k))   /*逐列考察,找出能擺放皇后的列x[k]*/
            x[k]++;
        if(x[k]<=n)                   /*找到一個位置可以擺放皇后*/
        {
            if(k==n)                  /*是一個完整的解,輸出解*/
                printSolution(x, n);
            else  /*沒有完成最後一行的選擇,是部分解,轉向下一行*/
            {
                k++;    /*接著考察下一行*/
                x[k]=0;  /*到迴圈開始執行x[k]++後,下一行將從第1列開始考察*/
            }
        }
        else  /*對應x[k]>n的情形,這一行已經沒有再試的必要,回溯到上一行*/
            k--;  /*上一行在原第x[k]列的下1列開始考察*/
    }
}

/*輸出求解結果*/
void printSolution(int *x, int n)
{
    int i, j;
    for (i = 1; i <= n; i++)    /*輸出第i行*/
    {
        for (j=1; j<=n; j++)
        {  
            if (j == x[i])    /*第x[i]列輸出Q,其他列輸出*號 */
                printf("Q");
            else
                printf("*");
        }
        printf("\n");
    }
    printf("\n");
}

3.矩形對角點最短路徑

問題描述,每次只能向上下左右四個方向移動一步,求從點(x,y)到(m,n)最短步數的走法。

分析:從題意可知要使步數最少,則必須是往右或者往上走,且最大步數肯定為(m-x)+ (n-y);如此則有兩種解法,遞迴的樹深度搜索,非遞迴的棧式回溯。下面列出程式碼。

方法一:遞迴求解。

int num = 0;
void Search(int x, int y, int m, int n){
	if(x==m||y==n){//不走回頭路,所以只要或就行
		num++;
		return;
	}
	search(x+1,y,m,n);//右走
	search(x,y+1,m,n);//上週
}

方法二:棧式回溯

int search(int x, int y, int m, int n){//回溯法
        //if(m == n == 1)
        //    return 1;
        int f[m+n-1] = {0};
        int sum = 0;
        int k = 1;
        while(k>0){
             if(x == m -1){
                sum++;
                k--;
                x--;
                continue;
            }
           else if(y == n - 1){
                 sum++;
                 k--;
                 y--;
                continue;
            }
           if(f[k] == 0){
                f[k]++;
                k++;
                x++;
                }
           else if(f[k] == 1){
                f[k]++;
                k++;
                y++;
                //continue;
            }
          else if(f[k] == 2){
              f[k] = 0;//清0,方便再次進入
              if(f[k-1] == 2)//判斷回溯方向
                  y--;
              else
                  x--;
              k--;
            }    
        }
        return sum;
    }

動態規劃法:由遞迴式可知狀態轉移方程為s[i][j] = s[i-1][j] + s[i][j],i,j>0.程式碼如下:

int s[101][101] = {0};
     int uniquePaths(int m, int n) {//遞迴法
        //return search(0, 0, m, n);
         //vector<int> temp(n,0);
        // vector<vector<int>> vt_rt(m,temp);
         int max = m>n?m:n;
         for(int i = 1; i <= max; i++)//初始化
         {
             s[1][i] = 1;
             s[i][1] = 1;
         }
         for(int i = 2; i <= m; i++)
             for(int j = 2; j <= n; j++)
                 s[i][j] = s[i-1][j] + s[i][j-1];
         return s[m][n];    
    }