九宮格重排問題 代碼及通俗講解

是數據結構的課設(@.@)
課設要求以下爲全部代碼:

//編譯運行環境:VC++ 6.0   
//win10 10.0.18362
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

long int fac[10] = { 1,1,2,6,24,120,720,5040,40320,362880 };//階乘表,康託展開定位位置時會用到

char step[363880] = { 0 };         // 9!=362880,記錄每一種情況是否被走過

char method[4][2] = { {-1,0},{0,1},{1,0},{0,-1} };   //4種移動方法

struct node {
    char data[9];
    int  step_p;
    int way;
};

struct node save_step[363880];               //保存移動數據,最多有 9!個移動數據

char   endnode[9];          //終點座標

long int    end_location;          //記錄終點的location

long int locate(char s[], int n) 
{
    int i, temp;
    long int num = 0;
    for (i = 0; i < n; i++)
    {
        temp = 0;
        for (int j = i + 1; j < n; j++)
            if (s[j] < s[i])  //判斷幾個數小於它
                temp++;
        num += fac[n - i - 1] * temp;
    }
    return  num;
}

//檢查移動後的情況
int check(int i, char data[]) 
{
    int x, y;
    long int num;

    for (int j = 0; j < 9; j++)
        if (data[j] == 0)
        {
            //計算空格移動後的座標
            x = j % 3 + method[i][0];
            y = j / 3 + method[i][1];

            //判斷移動後的位置是否越界
            if (x < 0 || x>2 || y < 0 || y > 2)
                return 0;

            //判斷移動後的位置是否已走過
            data[j] = data[x + y * 3];
            data[x + y * 3] = 0;
            num = locate(data, 9);
            if (step[num] == 1)       //已被走過,也不是終點
            {
                return 0;
            }

            if (memcmp(endnode, data, 9) == 0)  //是終點
            {
                return 2;
            }

            step[num] = 1;
            return 1;
        }
}

long int start = 0, end = 0;
long int bfs()							
{
    long int next_end = end;           //next_end是走完下一步的位置,end是現在的位置
    int flag;
    char temp[9];

    for (; start <= end; start++)
    {
        for (int i = 0; i < 4; i++)      //4種走法,依次走
        {
            memcpy(temp, (char*)save_step[start].data, 9);

            flag = check(i, temp);     //檢查這一步走的結果

            if (flag)   //如果沒被走過,也沒越界
            {
                memcpy((char*)save_step[++next_end].data, temp, 9);
                save_step[next_end].way = i;                    //記錄移動方向
                save_step[next_end].step_p = start;             //記錄步數位置     

                if (flag == 2)  //找到終點
                {
                    end_location = next_end;          //記錄終點步數位置.
                    return 1;                      //找到終點,返回1,依次遞加,看一共遞歸了多少層
                }
            }
        }
    }
    start = end + 1;
    end = next_end;
    return (1 + bfs());
}

int getmode(char num[])
{
	int count;
	for(int i=0; i<9; i++)
	{
		for(int j=i; j<9; j++)
		{
			if(num[i] > num[j])
				count++;
		}
	}
	count = count%2;
	return count;
}
int solvableornot()
{
	int startstate, endstate;
	startstate = getmode(endnode);
	endstate = getmode(save_step[0].data);
	if(startstate == endstate)
		return 1;
	else return 0;
}

int main()
{
    long int count = 0;         //步數
    system("color a");

    //輸入
    int i;
    printf("請輸入起點九宮格:\n");
    for (i = 0; i < 9; i++)
    {
        scanf("%1d", &endnode[i]);
    }
    printf("請輸入目標九宮格:\n");
    for (i = 0; i < 9; i++)
    {
        scanf("%1d", &save_step[0].data[i]);
    }

	//判斷是否有解
	int solvable = solvableornot();			
	if(!solvable)
	{
		printf("unsoloved\n");
		exit(1);
	}

	//求解
    count = bfs();
    printf("共需要%d步\n依次爲:\n", count);
    long int tempend=end_location;
	int tempcount;
    for (tempcount = 0; tempcount < count; tempcount++)
    {
        switch (save_step[tempend].way)
        {
        case 0:printf("右"); break;
        case 1:printf("上"); break;
        case 2:printf("左"); break;
        case 3:printf("下"); 
        }
        tempend = save_step[tempend].step_p;
    }
    printf("\n按任意鍵開始動態演示");
    system("pause >nul");

    //依次輸出每一步
    for (tempcount = 0; tempcount <= count; tempcount++)
    {
        system("cls");
        printf("第%d/%d步\n", tempcount,count);
        printf("\t+———————+\n");
        printf("\t|  %d |  %d |  %d |\n", save_step[end_location].data[0], save_step[end_location].data[1], save_step[end_location].data[2]);
        printf("\t|———————|\n");
        printf("\t|  %d |  %d |  %d |\n", save_step[end_location].data[3], save_step[end_location].data[4], save_step[end_location].data[5]);
        printf("\t|———————|\n");
        printf("\t|  %d |  %d |  %d |\n", save_step[end_location].data[6], save_step[end_location].data[7], save_step[end_location].data[8]);
        printf("\t+———————+\n");
        end_location = save_step[end_location].step_p;
        printf("\n");
        system("pause");
    }
    printf("演示結束,按任意鍵退出");
    system("pause >nul");
    return 0;
}

以下從主函數開始對每個部分進行詳細講解:

變量count用來存儲求解出的步數
system(“color a”);是終端命令,將字題顏色變成淡綠色
i用於循環
下面的兩個循環分別用於輸入起點和終點,(爲了顯示路徑方便)其中endnode是終點,save_step[0].data[]是起點。
save_step[]數組的定義可以在前面找到,這是一個結構體數組,用於存儲每一個步的信息。其中每一個數據存儲的內容包括:data[9]:此步的九宮格;step_p:此步的位置;way:此步的移動方向;

輸入結束後,判斷此九宮格問題通過移動是否可解:
solvable用於表示問題可解與否;
調用solvableornot()函數進行判斷;
solvableornot()的原理涉及到高等代數中排列的相關知識,詳細請參考:https://wenku.baidu.com/view/d68955a0aef8941ea76e05ed.html
在solvableornot()函數中,startstate變量表示起始點爲奇排列還是偶排列,endstate表示終點爲奇排列還是偶排列。分別經getmode()函數判斷後,根據排列的對換相關知識,若兩者相同,則有解,返回1。反之則無解,返回0;

回到主函數,無解則輸出無解並退出,有解則進入bfs()函數進行計算。

bfs()函數上面定義的start和end用於表示位置。函數中的next_end用於表示
第一層循環依次取需要遍歷但還未遍歷的步的位置,第二層循環走出上下左右這四步。
memcpy函數將走出一步前九宮格的內容傳遞給temp[];然後用check()函數判斷這一步的情況.

跟進check()函數;首先用x,y來表示移動後0(即空格)的座標,然後判斷此次移動是否出界。若出界,直接返回0,表示此次移動不合法。之後的幾行,判斷此位置是否已經走過了,

跟進locate函數;此函數使用康託展開,用於定位傳入的參數在所有0~8九個數字的排列組合中排的順序,也就是在step[]數組中應占的位置(step[]數組的定義可以在前面找到,用於表示對應位置是否已被訪問)。並將這個位置返回。
康託展開可參考:https://baike.baidu.com/item/%E5%BA%B7%E6%89%98%E5%B1%95%E5%BC%80/7968428?fr=aladdin

回到check()函數;判斷step[num]是否爲1,若爲1則表示該結點已被訪問過,此路徑可拋棄,並返回0;若未訪問過,則判斷此節點是否爲終點,是則返回2;不是則將該結點的標誌置1,表示已訪問,並返回1;

回到bfs()函數;若此次移動合法,則將此次移動後的信息記錄到存儲每一步信息的結構體數組save_step[]中;若找到終點,則返回1,每次遞歸都返回1,最終主函數中調用的bfs()就將返回遞歸的層數,也就是步數。
若不是終點,就將start和next_end都置爲下一個需要遍歷但還未遍歷的結點,然後遞歸調用bfs(),進行下一次移動。
最終找到終點,返回1,並在每一次返回時+1,最終返回到主函數,就是一共走的步數。

PS
step[]數組用於存儲對應的結點是否已經走過,由於0-8所有數的排列組合一共有9!種情況,所以要有9!個元素。

如果我有任何理解錯誤的地方,還請大佬不吝指出!


向大佬低頭.gif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章