經典計算機算法設計方法(1) -- 遞歸與迭代轉化


漢諾塔是一個經典的遞歸算法案例,下面來描述問題:

漢諾(Hanoi)塔問題:古代有一個梵塔,塔內有三個座A、B、C,A座上有64個盤子,盤子大小不等,大的在下,小的在上(如圖)。有一個和尚想把這64個盤子從A座移到C座,但每次只能允許移動一個盤子,並且在移動過程中,3個座上的盤子始終保持大盤在下,小盤在上。在移動過程中可以利用B座,要求打印移動的步驟。

怎么解決這個問題呢,如果你已經很熟悉了,那就不用細究了,不過其實其分析過程也是很有代表性的(先從簡單的開始,並向復雜擴展):


這個問題在盤子比較多的情況下,很難直接寫出移動步驟。我們可以先分析盤子比較少的情況。假定盤子從大向小依次為:盤子1,盤子2,...,盤子64。
如果只有一個盤子,則不需要利用B座,直接將盤子從A移動到C。
如果有2個盤子,可以先將盤子1上的盤子2移動到B;將盤子1移動到c;將盤子2移動到c。這說明了:可以借助B將2個盤子從A移動到C,當然,也可以借助C將2個盤子從A移動到B。
如果有3個盤子,那么根據2個盤子的結論,可以借助c將盤子1上的兩個盤子從A移動到B;將盤子1從A移動到C,A變成空座;借助A座,將B上的兩個盤子移動到C。這說明:可以借助一個空座,將3個盤子從一個座移動到另一個。
如果有4個盤子,那么首先借助空座C,將盤子1上的三個盤子從A移動到B;將盤子1移動到C,A變成空座;借助空座A,將B座上的三個盤子移動到C。

得出的結論是,指定一個空座,指定一個源座,指定可以借助的座,可以將源座上所有的盤子都移動到空座上。而且小數量的盤子的移動方法可以被多數量的盤子的移動方法所利用。算法的表示是在移動n個盤子從A到C時,先將n-1個盤子從A移動到B,然后移動第n個盤子到C,然后再將n-1個盤子從B移動到C。可以很容易用遞歸實現,下面是算法的實現:

 

#include <iostream>
using  namespace std;

void hanoi( int n, char src, char resource, char dest)
{
        if(n> 1)
        {
                hanoi(n- 1, src, dest, resource);
                cout<< " Move "<<n<< " from "<<src<< " to "<<dest<<endl;
                hanoi(n- 1, resource, src, dest);
        }
        else
        {
                cout<< " Move "<<n<< " from "<<src<< " to "<<dest<<endl;
        }
}

int main( int argc, char **argv)
{
        int N;
        cout<< " Please Input a number: "<<endl;
        cin>>N;
        hanoi(N, ' A ', ' B ', ' C ');
}

 

應該說,漢諾塔的遞歸算法是非常簡單的。 但是都說遞歸算法都可以變成迭代算法,那漢諾塔的迭代算法又如何實現呢?

先從理論上看看要設計好迭代算法,應該注意哪些方面:

一、確定迭代變量。在可以用迭代算法解決的問題中,至少存在一個直接或間接地不斷由舊值遞推出新值的變量,這個變量就是迭代變量。

二、建立迭代關系式。所謂迭代關系式,指如何從變量的前一個值推出其下一個值的公式(或關系)。迭代關系式的建立是解決迭代問題的關鍵,通常可以使用遞推或倒推的方法來完成。

三、對迭代過程進行控制。在什么時候結束迭代過程?這是編寫迭代程序必須考慮的問題。不能讓迭代過程無休止地重復執行下去。迭代過程的控制通常可分為兩種情況:一種是所需的迭代次數是個確定的值,可以計算出來;另一種是所需的迭代次數無法確定。對於前一種情況,可以構建一個固定次數的循環來實現對迭代過程的控制;對於后一種情況,需要進一步分析出用來結束迭代過程的條件。

通俗一點去理解呢,就是如果要設計迭代算法,一定要搞清楚迭代變量是什么,在什么范圍內迭代,迭代時狀態的遷移是怎樣完成的。舉個簡單的例子,

for(int i= 0;I <1000;i++);

這是一個非常簡單的迭代過程,迭代變量是i,范圍是從0到1000的一個數列,狀態遷移是+1。

其實很多時候迭代范圍都可以理解稱為一個數據結構,按某種順序將這個數據結構從某個點遍歷都另外一個點。上面的小例子可以理解為數組的遍歷。所以設計迭代算法時一定要知道你在遍歷一個什么樣的數據結構.

下面我們看看漢諾塔的迭代算法如何實現:

1. 我們先來分析一下移動的過程:先將n-1個盤子從A移動到B,把第n個盤子從A移動到C,把n-1個盤子從B移動到C。這本是遞歸的過程,但我們要想知道迭代操作的最終的數據結構,就一定要將其展開為原子操作。下面用圖來解釋:

tree

這個圖看起來就會更容易明白了,移動盤子的三個步驟就跟一個數一樣,第一步移動若干盤子是左子樹;第二步移動一個盤子相當於根;第三步移動若干盤子又相當於右子樹。而且每棵子樹都可以按照同樣的步驟分解,一直到全部是移動一個盤子的操作,至此,才算把整個數據結構展開了,我們也應該知道這是一個什么樣的數據結構了:二叉樹。這個二叉樹的深度是N,也就是盤子的個數,根節點表示盤子N的操作,孩子是N-1時的移動,. . . . 。移動的過程就是先左子樹,根節點,右子樹,也就是前序遍歷。然后我們再看每個節點的表達式是什么?畫個圖,看了一下,要找到每個節點的表達式是不容易的。不過,我們應該可以發現一個重要線索:只要最左面的節點知道了,就可以知道父節點和兄弟節點的表達式了。比如左孩子是m,A,B,那父節點是m+1,A,C, 右孩子是m,B,C。 所以關鍵就是要知道最左孩子的表達式。列出N = 1, 2,3 時的操作順序可以發現,最左孩子只是在A,B和A,C之間交替。而且N為偶數時,為A,B;N為奇數時為A,C。至此我們對我們要迭代的數據結構就足夠清楚了。下面就是實現一個滿二叉樹的前序遍歷算法了。代碼實現如下所示:

 

#include <iostream>
#include <stack>

using  namespace std;
class TreeNode
{
public:
        TreeNode( int N, char _src, char _dest){ plateNum = N;src =_src; dest = _dest;}
        int plateNum;
        char src;
        char dest;
};

void hanoi( int N)
{
        stack<TreeNode *> *_stack = new stack<TreeNode *>();
        _stack->push( new TreeNode(N, ' A ', ' C '));
        TreeNode * current = _stack->top();
        while(!_stack->empty())
        {
                while(current->plateNum > 0)
                {
                        current = new TreeNode(current->plateNum- 1,current->src, ' A '+ ' B '+ ' C ' - current->dest - current->src);
                        _stack->push(current);
                }
                current = _stack->top();
                _stack->pop();
                cout<< " Move "<<(current->plateNum+ 1)<< " from "<<current->src<< " to "<<current->dest<<endl;
                if(current->plateNum != 0)
                {
                        TreeNode* old = current;
                        current = new TreeNode(current->plateNum- 1, ' A '+ ' B '+ ' C ' - current->dest - current->src, current->dest);
                        _stack->push(current);
                        delete old;
                }
        }
        delete _stack;
}

int main( int argc, char **argv)
{
        int N;
        char src = ' A ',resource= ' B ',dest = ' C ';
        bool moved = false;
        cout<< " Please input a number: "<<endl;
        cin>>N;
        hanoi(N- 1);
}

 

另外一種實現: http://shb0629.blog.163.com/blog/static/42201810200932331159721/

還有一種實現:http://www.cppblog.com/goal00001111/archive/2006/06/07/8252.html

還有一種:http://blog.sina.com.cn/s/blog_48e3f9cd01000474.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM