1. 程式人生 > >【基礎練習】【BFS+A*】codevs1225八數碼難題題解

【基礎練習】【BFS+A*】codevs1225八數碼難題題解

一點 說明 優先 data- push 練習 bool csdn tarjan

題目描寫敘述 Description

Yours和zero在研究A*啟示式算法.拿到一道經典的A*問題,可是他們不會做,請你幫他們.
問題描寫敘述

在3×3的棋盤上,擺有八個棋子,每一個棋子上標有1至8的某一數字。棋盤中留有一個空格,空格用0來表示。空格周圍的棋子能夠移到空格中。要求解的問題是:給出一種初始布局(初始狀態)和目標布局(為了使題目簡單,設目標狀態為123804765)。找到一種最少步驟的移動方法,實現從初始布局到目標布局的轉變。

輸入描寫敘述 Input Description

輸入初試狀態,一行九個數字,空格用0表示

輸出描寫敘述 Output Description

僅僅有一行,該行僅僅有一個數字,表示從初始狀態到目標狀態須要的最少移動次數(測試數據中無特殊無法到達目標狀態數據)

例子輸入 Sample Input

283104765

例子輸出 Sample Output

4

血淚史,血淚史啊TUT

從昨天上午搗鼓,到今天一直跳了半個上午才弄好。然後我發現,我的代碼能力真的很低下。不編譯感覺什麽事兒都沒有,一調試N個錯誤,還都是細節上的粗心。邏輯上的失誤。思路轉換的時候忘了回溯什麽的。看來我真得好好磨練代碼能力了。

每一個題目都要自己敲,一不小心可能就有新的發現。


這道題思路還是比較簡單的。之前一直糾結於如何存狀態。事實上就用樸素的二維數組就能夠。3*3不大。也不用傳遞數組形參或者實參,直接swap,回溯的時候再swap回來就能夠了。


詳細方法是:每次向上下左右四個方向交換空位和數字。判重,計算估價函數,進入優先隊列,更新空位坐標和已走步數,直到找到解。

以下是一些細節上的說明:

1.判重 這個剛開始準備用哈希表,從沒寫過哈希表,但後來聽了裏奧神犇的建議用了set 並不慢 就這道題而言。每一個數字都是有0-9這九個數字組成,一共僅僅有三十萬種狀態,況且假設8個數字確定了最後一位也就確定了,用哈希是非常好的,不easy出現重疊沖突。

可是用set更加簡明易行,直接計算數字insert進去就能夠了,查找時能夠用count函數檢查。

2.估價函數eva:eva是evaluate 估價(動詞) 或者evaluation 估價(名詞)的縮寫。

這次順便學習了數學上和國際象棋上的三個距離:

曼哈頓距離:兩點橫坐標差點絕對值和縱坐標差的絕對值之和。

名字來源於曼哈頓的街道都是矩形網格狀的,從某點到還有一點的距離就是曼哈頓距離。

歐拉距離:兩點間的直線距離。計算公式是兩點左邊差絕對值的平方和。相當於已知三角形直角邊求三角形最長邊

切比雪夫距離:兩點橫縱坐標差絕對值中較大的一個。

曼哈頓距離是求和。這裏是取max。(讓我想起某個諸城一中講的離散化的題目)


原本最準確應當採用曼哈頓距離。由於格子僅僅能上下左右移動。可是曼哈頓距離的計算我僅僅會最樸素的四次方求解,不知有沒有簡單方法。其實,在這裏我們也能夠用像騎士精神那道題裏出現的推斷與目標狀態不同的各自有多少,這個是平方的復雜度,可是經模擬,效果是相同的。它相同滿足估價函數值均不大於實際值。

因為估價函數滿足假設估價函數值均不大於實際值,那麽向估價函數小的方向進展一定能達到最優解。這一條我不知道怎樣證明但已被證明。因此我們能夠採用統計有多少個不同格子的估價做法。

3.一些細節。關於這道題目中我犯的N個錯誤

A.註意常量數組所實用的是大括號!。!(pas是小括號)

const int t[3][3]=
{{1,2,3},
{8,0,4},
{7,6,5}};

B.優先隊列的重載。因為優先隊列默認大的在前,假設想要讓eva小的在前,應該把大於號重載成小於號。因為涉及STL(優先隊列)。應當在後面加上const

bool operator < (node b) const
    {
        return eva>b.eva;
    }

C.關於set版哈希的處理,這裏是把每一個狀態轉化為一個int數字放入集合中

bool gethash(node &now)
{
    int he=0;
    for (int i=0;i<3;i++)
    {
        for (int j=0;j<3;j++)
        {
            he=he*10+now.a[i][j];
        }
    }
    if (hash.count(he)) return false;
    hash.insert(he);
    return true;
    //Èç¹ûûÓÐÕâ¸öÊý¾Í¹þÏ£ÁË·ñÔò·µ»Øfalse 
}
再看以下原始狀態的數字轉化

for (int i=0;i<3;i++)
    {
        for (int j=0;j<3;j++)
        {
            e.a[i][j]=s[i*3+j]-'0';
            if (e.a[i][j]==0)
            {
                e.x=i;
                e.y=j;
            }
            he=he*10+e.a[i][j];
        }
    }

註意,以下是吧讀入的字符轉換為數字,而上面的數組中存儲的本來就是數字,直接計算就能夠了。然而我上面那個寫成了now.a[i][j]-‘0‘ 於是全是負數···於是set不會報反復···於是死循環···

D.bfs的細節問題(單步了N次TUT)

void bfs()
{
    while (!q.empty())
    {
        if (ok) return;
        node now=q.top();
        q.pop();
        int nx,ny;
        for (int i=0;i<4;i++)
        {
            nx=now.x+xx[i];
            ny=now.y+yy[i];
            if (nx<0||nx>2||ny<0||ny>2) continue;
            swap(now.a[nx][ny],now.a[now.x][now.y]);
            if (!gethash(now))
			{
				swap(now.a[nx][ny],now.a[now.x][now.y]);
				continue;
			} //Çó¹þÏ£ ×¢ÒâÈç¹û²»³ÉÁ¢Ó¦·µ»ØÔ­À´µÄÖµ£¡£¡£¡ 
            now.x=nx;
            now.y=ny;
            now.id++;
            now.eva=geteva(now);//Çó¹À¼Ûº¯Êý 
            if (now.eva==now.id)
            {
                printf("%d\n",now.id);//×îÓÅÐÔ£¨²½Êý×îÉÙ£©³ÉÁ¢Ô­Àí£º¹À¼Ûº¯ÊýÖµ²»´óÓÚʵ¼ÊÖµ£¬Ôò½â±Ø¶¨×îÓÅ 
                ok=true;
                break;
            }
            q.push(now);
            now.x-=xx[i];
            now.y-=yy[i];
            swap(now.a[nx][ny],now.a[now.x][now.y]);
            now.id--;
        }
    }
}

這是BFS部分的代碼。代碼應該非常好懂。

可是···

第一次。發現我改變了now之後沒有回溯,於是非常多狀態一時半會兒找不到。TLE···

第二次,發現空格的坐標在每一個狀態中並沒有更新,於是空格根本無法到達角格,輸出無解(“= =”)···

第三次,發現我並沒有把now這個節點的狀態全然恢復到原來。id並沒有--。導致步數紊亂僅僅增不減,WA···

第四次。發現假設出現了已經有過的狀態。雖然判重沒有問題仍然進入隊列且其它狀態被忽略···細致一看原來gethash返回false後直接continue並沒有回溯。這樣now在下一次循環的狀態就延續了這個已經出現過的狀態···死循環···

第五次,發現eva函數的返回值不大對,事實上應該返回已走步數+估價函數。可是我沒有加已走步數,這道題小數據不受影響,可是後果慘重···

第六次,發現空格坐標更新後沒有回溯···於是又調試···

凡此種種,吐血一上午,幾近崩潰,於是和大家爭論tarjan究竟應該讀什麽,後來聽了音頻。有道上念作tar zhen 恩 能夠接受 然而我們居然一直沒有看出前面的那個expression居然是深度廣度優先搜索···

總之,吸取教訓。以後要好好提高代碼能力了

昨天上知乎又受到了教育·1··還是應該努力學習,好好讀書。爭取成為一名優秀的技宅和出色的學霸···rio君的故事好勵誌,所以還是要努力努力···文史哲個人修養也要提高。技術能力也要提高,家務能力也要提高,我要十項全能···什麽鬼反正某人說過懂編程懂project又懂歌劇的人是非常有魅力的···

然後膜拜一下維特根斯坦···維特根斯坦這種名字可能真的要讓人如雷貫耳不勝惶恐。恩我要努力成為這種文青。然而如今我卻是這樣想的:維特根斯坦小時候和希特勒在一起好萌啊【PIA飛 好吧這說明窩離文青還有非常遙遠的距離又萌又腐咩【什麽鬼 這些書總是要讀的 既然提到維特根斯坦就順便拜一拜羅素這種奇妙人物吧 這些大家族果然修養各種不凡啊···

然後再膜拜一下香濃,啊不正確。香農。

Shannon這個讓我最先想到的是愛爾蘭Limerick的那條河,多美的名字,好像去那兒···= =扯回來,既然隔壁小喬他們承認,那看來繼圖靈和馮諾依曼之後,香農也是相當偉大的人物了。有時候認為諾依曼費曼這種天才全才又是性情中人永遠僅僅能被我們膜拜的,然而我們很多其它人終究不是天才,所以我還是開開心心地過我的生活吧。即使我們不是知識的開創者,我們也能夠做知識的追隨者。這並沒有錯。並且。我們也許沒有如此耀眼的才華,但我們為何不能做一個這種性情中人呢。性情不是他們的專利。我們僅僅要敢想敢做也是能夠的。又想到某劇裏評價大學的一句話。如此充滿理想主義與縱欲輕狂的一個地方。我認為,既然年輕就應該懷抱著理想主義,這種人生才幹多彩。我讀維特根斯坦,深深地體會到這樣一點:僅僅要敢想敢做就能夠。我們年輕。更不能在思想上受到束縛,我們應該敢去想,不管對錯。茨威格十幾歲的時候,生活在歐洲的心臟,在哪個二十世紀的黎明年代,維也納的年輕人正是在這樣一種文化價值全方位興旺的環境熏陶下變得格外早熟而富有思想力。年輕的評論家們從不吝惜用屬於自己的思想大膽解讀文化,解讀世界。維也納和柏林那群十五六歲的少年的見地已經令整個歐洲震動。我們也許沒有那傳承千年的文化環境。沒有圖書館和音樂會,但我們也能夠努力去做。

文化的庸俗是由於人們甚至不願去想。不願去思考,僅僅是像蟲豸一樣碌碌於匆忙的地鐵線上,年輕人為考試而反復在競爭中功利地生活,底層人們為生存而掙紮為物質而拼命,上層人們滿足於安逸舒適的生活。奉行中庸之道和犬儒主義。把這些當做現實主義的生存之道。非常少有人再天真地做著自己所想的事情。理想主義也許在復雜的現實中早已磨滅,但我不希望在年輕人身上看不到這些理想主義的光輝。至少是對自身價值的承認和追求。我們應該思考,我們應該學習,我們應該進步,我們應該快樂。

好像不知不覺弄出來一篇奇怪的作文···還是抓緊回到正題把···

那麽上代碼吧


——臥龍躍馬終黃土,人事音書漫寂寥

【基礎練習】【BFS+A*】codevs1225八數碼難題題解