汉诺塔的非递归算法

265 篇文章 1 订阅

Update on 2014-10-03:

所有递归转非递归都可以依赖于栈模拟,而依赖于栈关键问题在于弄清是先序、中序还是后序遍历的树,而这里显然是一个汉诺塔显然是一个中序遍历的结果,所以非递归的代码如下所示:

先给一个错误的代码,错误代码错误的关键在于遍历完左子树没有明确应该做什么事,千万不要以为转非递归就是调用的时候压栈

 

#include <iostream>
#include <math.h>
#include <stack>
using namespace std;
void Hanoi(char src, char des, char via, int n)
{
  if(n == 1)
  {
    cout << n <<" : "<< src <<" --> " <<des << endl;
    return;
  }
  Hanoi(src, via, des, n - 1);
  cout << n <<" : "<< src <<" --> " <<des << endl;
  Hanoi(via, des, src, n - 1);
}
struct Node {
  char src, des, via;
	int n;
	Node(char s, char d, char v, int n0 = 0):src(s), des(d), via(v), n(n0) {
	}
};
void Hanoi2(char src, char des, char via, int n)
{
  stack<Node> sta;
	
	while (n > 0 || !sta.empty()) {
		while (n > 0) {
		  sta.push(Node(src, des, via, n--));
			swap(des, via);
		}
		if (!sta.empty()) {
		  Node tmp = sta.top();
			sta.pop();
			src = tmp.src, des = tmp.des, via = tmp.via, n = tmp.n;
			cout << n <<" : "<< src <<" --> " <<des << endl;
			if (n > 1)
				sta.push(Node(via, des, src, --n));
			else
				n = 0;
		}
	}
}

int main()
{
  int n;
  cout<<"recusive:"<< endl;
  Hanoi('A','C','B', 3);
  cout << endl;
  cout<<"normal:"<<endl;
  Hanoi2('A','C','B', 3);

  return 0;
}


正确的代码:

#include <iostream>
#include <math.h>
#include <stack>
using namespace std;
void Hanoi(char src, char des, char via, int n)
{
  if(n == 1)
  {
    cout << n <<" : "<< src <<" --> " <<des << endl;
    return;
  }
  Hanoi(src, via, des, n - 1);
  cout << n <<" : "<< src <<" --> " <<des << endl;
  Hanoi(via, des, src, n - 1);
}
struct Node {
  char src, des, via;
	int n;
	Node(char s, char d, char v, int n0 = 0):src(s), des(d), via(v), n(n0) {
	}
};
void Hanoi2(char src, char des, char via, int n)
{
  stack<Node> sta;
	
	while (n > 0 || !sta.empty()) {
		while (n > 0) {
		  sta.push(Node(src, des, via, n--));
			swap(des, via);
		}
		if (!sta.empty()) {
		  Node tmp = sta.top();
			sta.pop();
			src = tmp.src, des = tmp.des, via = tmp.via, n = tmp.n;
			cout << n <<" : "<< src <<" --> " <<des << endl;
			swap(src,via), --n;			
		}
	}
}

int main()
{
  int n;
  cout<<"recusive:"<< endl;
  Hanoi('A','C','B', 4);
  cout << endl;
  cout<<"normal:"<<endl;
  Hanoi2('A','C','B', 4);

  return 0;
}

用Python更简洁直观的思路:

from itertools import  permutations

def h1(src, des, via, n):
    if (n == 0):
        return
    #Before recursive call of left child, des and via swap for left child going deeper
    h1(src, via, des, n-1)
    #After recursive call, print will be done for left child
    print("{0}:{1}-->{2}".format(n, src, des))
    #Before recursive call of right child, via and src swap for right child going deeper
    h1(via, des, src, n-1)
    #After recursive call of right child, nothing will be done again

def h2(src, des, via, n):
    stack = []
    while (stack or n > 0):
        #Deeper for left child
        while (n > 0):
            stack.append({'src': src, 'des': via, 'via':des, 'n': n-1, 'child':'left'})
            des, via = via, des
            n = n - 1
        if (stack):
            top = stack.pop()
            if (top['child'] == 'left'):
                src, des, via, n, child = top['src'], top['via'], top['des'], (top['n'] + 1), top['child']
                print("{0}:{1}-->{2}".format(n, src, des))
                stack.append({'src': via, 'des': des, 'via': src, 'n': n - 1, 'child': 'right'})
                n = 0
            elif (top['child'] == 'right'):
                src, des, via, n, child = top['src'], top['des'], top['via'], (top['n'] + 1), top['child']
                n = n - 1

h1('A', 'C', 'B', 3)
print('------------------')
h2('A', 'C', 'B', 3)

 

 

 

以下是转自网上的递推的算法:

转自: http://ahauhs.blog.163.com/blog/static/298541892008819426616/

 

 

汉诺塔问题介绍:

在印度,有这么一个古老的传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片,一次只移动一片,不管在哪根针上,小片必在大片上面。当所有的金片都从梵天穿好的那根针上移到另外一概针上时,世界就将在一声霹雳中消灭,梵塔、庙宇和众生都将同归于尽。

 

递归算法:

定义 void Hanoi(char src, char des, char via, int n)

表示把n个盘子从src上借助via移动到des上。

显然有

     void Hanoi(char src, char des, char via, int n)

      {

          Hanoi(src, via, des, n - 1);

          Move(src, des, n); //把第n个盘子直接从src移动到des

          Hanoi(via,des, src, n - 1);

      }

 

根据递归算法,设f(n)为n个盘子要移动的次数。

那么显然 f(n + 1) = 2*f(n) + 1  ->  [f(n + 1) + 1] = 2*[f(n) + 1]

f(1) = 1,-> f(n) + 1 = (1 + 1)^n -> f(n) = 2^n - 1。

f(64)= 2^64-1=18446744073709551615   

假如每秒钟一次,共需多长时间呢?一年大约有 31536926 秒,计算表明移完这些金片需要5800多亿年,比地球寿命还要长,事实上,世界、梵塔、

庙宇和众生都已经灰飞烟灭。

 

非递归算法:

定义从小到大的盘子序号分别为1,2,……n。

可以用一个1到2^n - 1的2进制序列可以模拟出n个盘子的汉诺塔过程中被移动的盘子的序号序列。

即给定一个n,我们通过0到2^n - 1序列可以判断出任意一步应该移动那个盘子。

判断方法:第m步移动的盘子序号是m用二进制表示的最低位bit为1的位置。

 

证明: n = 1,显然成立。

假设n = k 成立。

n = k + 1时,对应序列1到2^(k+1) - 1,显然这个序列关于2^k左右对称。

假设我们要把k + 1个盘子从A移动C。

那么2^k可以对应着Move(k + 1, A, C)。 1 到 2^k - 1 根据假设可以

对应Hanoi(A, B, C, k)。至于2^k + 1 到 2^(k + 1) - 1把最高位的1去掉对应序列变成1到2^k - 1,显然2^k + 1 到 2^(k + 1) - 1和1到2^k - 1这两个序列中的对应元素的最低位bit为1的位置相同。因此2^k + 1 到 2^(k + 1) - 1可以对应Hanoi(B, C,A,k)。

所以对n = k + 1也成立。

 

下面讨论第m步应该移动对应的盘子从哪到哪?

定义顺序为 A->B->C->A, 逆序为C->B->A->C。

 

性质对n个盘子的汉诺塔,任意一个盘子k(k <= n)k在整个汉诺塔的移动过程中要么一直顺序的,要么一直逆序的。而且如果k在n个盘子移动过程的顺序和k - 1(如果k > 1)以及k + 1(如果k < n)的顺序是反序。

比如:n = 3

1 A->C

2 A->B

1 C->B

3 A->C

1 B->A

2 B->C

1 A->C

其中1的轨迹A->C->B->A>C逆序,2的轨迹A->B->C顺序,3的轨迹A->C逆序

     

证明:假设n <= k成立

对于n = k + 1 根据递归算法

Hanoi(A,C,B,k + 1) = Hanoi(A, B, C, k) + Move(A, C, k + 1) + Hanoi(B, C,A,k);

整个过程中盘子k + 1只移动一次A->C为逆序对应着2^k。

对于任意盘子m < k + 1,

m盘子的移动由两部分组成一部分是前半部分Hanoi(A, B, C, k)以及后半部分的Hanoi(B, C,A,k)组成。显然有如果m在Hanoi(A, C, B, k)轨迹顺序的话,则m在Hanoi(A, B, C, k)以及Hanoi(B, C,A,k)都是逆序。反之亦然。这两部分衔接起来就会证明m在Hanoi(A,C,B,k)和Hanoi(A,C,B,k + 1)中是反序的。

同时有Hanoi塔中最大的盘子永远是逆序且只移动1步,A->C。

这样的话:

m = k + 1,在Hanoi(A,C,B,k + 1)中是逆序。

m = k,由于在Hanoi(A,C,B,k)中是逆序的,所以Hanoi(A,C,B,k + 1)中是顺序的。

m = k - 1,由于在Hanoi(A,C,B,k - 1)是逆序的,所以Hanoi(A,C,B,k)是顺序的,所以Hanoi(A,C,B,k + 1)是逆序的。

依次下去……

结论得证。

总结:在n个汉诺中n, n - 2, n - 4……是逆序移动,n - 1, n - 3,n - 5……是顺序移动。

 

有了以上结论,非递归的程序就很好写了。写了个递归和非递归比较程序:

 

 

 

#include <iostream>
#include <math.h>
using namespace std;
void Hanoi(char src, char des, char via, int n)
{
  if(n == 1)
  {
    cout << n <<" : "<< src <<" --> " <<des << endl;
    return;
  }
  Hanoi(src, via, des, n - 1);
  cout << n <<" : "<< src <<" --> " <<des << endl;
  Hanoi(via, des, src, n - 1);
}
int main()
{
  int n;
  cin >> n;
  cout<<"recusive:"<< endl;
  Hanoi('A','C','B', n);
  cout << endl;
  cout<<"normal:"<<endl;
  char order[2][256], pos[64];
  order[0]['A'] = 'B';
  order[0]['B'] = 'C';
  order[0]['C'] = 'A';
  order[1]['A'] = 'C';
  order[1]['B'] = 'A';
  order[1]['C'] = 'B';
  //0是顺序 1是逆序
  int index[64];
  //确定轨迹的顺序还是逆序
  int i, j, m;
  for (i = n; i > 0; --i)
    index[i] = (n - i + 1)%2;

  memset(pos, 'A', sizeof(pos));
  for(i = 1; i < (1 << n); i ++){
    for (m = 1, j = i; (j&1) == 0; j = j>>1, ++m);      
    cout << m <<" : "<< pos[m]  <<" --> " << order[index[m]][pos[m]] << endl;        
    pos[m] = order[index[m]][pos[m]];
  }

  return 0;
}

 

My friend version:

 

 

#include <iostream>
#include <stack>
using namespace std;
class Solution {
 public:
  unsigned int upperlimit;
  void dfs(int& count, unsigned int row, unsigned int ld, unsigned int rd) {
    if (row == upperlimit)
      ++count;
    unsigned int pos = (row | ld | rd) & upperlimit; // the bit 1 simplify the position can't place the queue any more
    unsigned int p = (~pos) & upperlimit,digit = 0;
    while (p) {
      digit = p - (p & (p - 1)); // Find the rightest 1
      //digit = p&(~p + 1);
      dfs(count, row + digit, (ld + digit) >> 1, (rd + digit) << 1);
      p -= digit;
    }
  }
  int totalNQueens(int n) {
    upperlimit = 0;
    int count = 0;
    for (int i = 0; i < n; ++i)
      upperlimit |= (1<<i);
    dfs(count, 0, 0, 0);
    return count;
  }
};
struct node {
	int val;
	int target;
	int mark;
	bool in;
	int place;
	node(int n, int t, int m, int p): val(n), target(t), mark(m), place(p), in(true) {}
};
void Hanoi(char src, char des, char via, int n)  
{  
  if(n == 1) {  
    cout << n <<" : "<< src <<" --> " <<des << endl;  
    return;  
  }  
  Hanoi(src, via, des, n - 1);  
  cout << n <<" : "<< src <<" --> " <<des << endl;  
  Hanoi(via, des, src, n - 1);  
}
class HanoiNode {
public:
  char src, des, via; 
  int n, mark, in;
  HanoiNode(char src, char des, char via, int n, int mark)
    :src(src),des(des),via(via),n(n),mark(mark),in(1){}
};
void HanoiIterate(char src, char des, char via, int n) {
  stack<HanoiNode> s;
  s.push(HanoiNode(src, des, via, n, 1));
  while (!s.empty()) {
    HanoiNode top = s.top();
    s.pop();
    if (top.n == 1) {
      cout << top.n <<" : "<< top.src <<" --> " <<top.des << endl;  
      if (top.mark == 0) {
        cout << top.n + 1 <<" : "<< top.src <<" --> " <<top.via << endl;  
        cout << top.n <<" : "<< top.des <<" --> " <<top.via << endl;
      }
    }
    else {
      if (top.in) {
        top.in = false;
        s.push(top);
        s.push(HanoiNode(top.src, top.via, top.des, top.n - 1, 0));
      }
      else {
        if (top.mark == 0) {
          cout << top.n + 1 <<" : "<< top.src <<" --> " <<top.via << endl;  
          s.push(HanoiNode(top.des, top.via, top.src, top.n, 1));
        }
      }
    }
  }
}


void Hanoi2(int n, int target, int place) {
  if (n == 1) {
    cout << "place plate " << 1 << " to " << target << endl;
    return;      
  }
  Hanoi2(n - 1, 6 - target - place, place);
  cout << "place plate " << n << " to " << target << endl;
  Hanoi2(n - 1, target, 6 - target - place);
}
int main() {

  Solution ss;
  int res = ss.totalNQueens(3);

  //Hanoi2(3,3,1);
  Hanoi('A', 'C', 'B', 3);
  cout << "------------------------" <<endl;
  HanoiIterate('A', 'C', 'B', 3);
	int N;
	cin >> N;
	stack<node> s;
	s.push(node(N, 3, 1, 1));
	while(!s.empty()) {
		node top = s.top();
		s.pop();
		if(top.val == 1) {//top.val = 1,执行第一个递归函数,只有一句,然后执行第二个递归函数
			cout << "place plate " << 1 << " to " << top.target << endl;
			if(top.mark == 0) {
				cout << "place plate " << (top.val + 1) << " to " << (6 - top.place - top.target) << endl;
				cout << "place plate " << 1 << " to " << (6 - top.place - top.target) << endl;
			}
		}
		else {
      if(top.in) {
				top.in = false;
				s.push(top);
				s.push(node(top.val - 1, 6 - top.place - top.target, 0, top.place));
			}
			else {//top.in = false表示递归函数执行完
				if(top.mark == 0) {//mark = 0表示执行完Hanoi2(n - 1, 6 - target - place, place); mark = 1表示执行完Hanoi2(n - 1, target, 6 - target - place);
					cout << "place plate " << (top.val + 1) << " to " << (6 - top.place - top.target) << endl;
					s.push(node(top.val, 6 - top.place - top.target, 1, top.target));
				}
			}
		}
	}
  return 0;
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值