遞迴總結(待完善)
阿新 • • 發佈:2019-01-03
一、遞迴函式的原理
一個直接呼叫自己或者通過一系列的呼叫語句間接呼叫自己的函式,稱做遞迴函式。
原理:系統用棧儲存未完成的工作,在適當的時候從棧中取出並執行。 系統儲存了工作的資料和狀態,資料就是函式的區域性變數,狀態就是程式指標。
有很多遞迴的問題,許多遞迴問題有類似的模式。一種好的判斷是否可以用遞迴解決問題的線索是看這個問題能否分解為子問題。當你聽見一個問題以下面句子開頭的話,它經常(並不是全部)可以用遞迴解決。例如:“設計一個演算法來計算第n個。。。”,“寫一段程式碼列出前n個。。”,“實現一個方法計算全部的。。。”等。
使用遞迴的一般方法:
1、先考慮一下這個問題依賴幾個子問題。比如說二叉樹類的問題,可能是依賴兩個子問題。
2、解決基本問題。如f(0),f(1)。
3、解決f(2)
4、思考怎麼用f(2)推匯出f(3)。這就是理解經過什麼處理能夠用f(2)解決f(3)
5、推匯出f(n)的推導公式。
注意的問題:
1、所有可以用遞迴實現的問題,都可以用迭代實現(雖然程式碼會變得很複雜)。在你準備用遞迴實現問題的時候,先問問自己,如果用迭代實現問題會有多複雜。權衡一下。
2、遞迴在空間利用率方面非常低。每一層遞迴都在棧中新增一層,這就意味著,如果你用時間複雜度為O(n)的演算法,空間複雜度也可能是O(n)。這是很浪費的。
體會:很多遞迴的問題都有一個類似樹形(或者圖形,一層一層,下層是上層的子問題或者父問題)的狀態結構,這是一個解題的突破口。
1. 和遞迴函式的原理相同,只不過是把由系統負責儲存工作資訊變為程式自己儲存,這樣能減少儲存資料的冗餘(主要是節省了區域性變數的空間),提高儲存效率。
2. 把程式要完成的工作分成兩類:手頭工作和儲存在棧中的待完成的工作。手頭工作指程式正在做的工作。由於某些工作不能一步完成,必須暫緩完成,於是可把它儲存在棧中,這就是待完成的工作。
3. 手頭工作必須有其結束條件,不能永遠做下去;儲存的待完成工作必須含有完成該項工作的所有必要資訊。
4. 程式必須有秩序地完成各項工作。如,可把手頭工作恰當處理(直接處理或暫時儲存)後,才能繼續接手下一步的工作。
5. 待完成工作必須轉換成手頭工作才能處理。
三、漢諾塔
遞迴實現
#include <iostream>
#include <iomanip>
using namespace std;
void move(int *a,int *b)
{
int temp;
int i=0;
while(a[i++]!=0);
i=i-2;
temp = a[i];
a[i]=0;
i=0;
while(b[i++]!=0);
i=i-1;
b[i]=temp;
}
void hanoi(int n,int* x,int* y, int * z)
{
if(n==0)
return;
else
{
hanoi(n-1,x,z,y); //把n-1個盤子從x移到y上
move(x,z); //然後把x最上面的盤子從x移到z上
hanoi(n-1,y,x,z); //最後把n-1個盤子從y移到z上;
}
}
int main()
{
int a[10]={4,3,2,1};
int b[10]={0};
int c[10]={0};
hanoi(4,a,b,c);
for(int i=0;i<4;++i)
{
printf("%d ",c[i]);
}
printf("\n");
return 0;
}
棧實現:
#include <stdio.h>
#define N 25
typedef struct HANOIDATA
{
int a[10];
int b[10];
int c[10];
int n;
}HANOIDATA;
void move(int *a,int *b)
{
int temp;
int i=0;
while(a[i++]!=0);
i=i-2;
temp = a[i];
a[i]=0;
i=0;
while(b[i++]!=0);
i=i-1;
b[i]=temp;
}
void swap(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
void swaphan(int *a,int *b)
{
int i=0;
while(i<10)
{
swap(&(a[i]),&(b[i]));
i++;
}
}
void hanoi(HANOIDATA hanoiData)
{
HANOIDATA stack[N];
int top = -1; // stack pointer
while (hanoiData.n || top != -1) // 存在手頭工作或待完成工作
{
while (hanoiData.n) // 處理手頭工作直到無現成的手頭工作,
// 即下次的手頭工作必須從棧中取得
{
hanoiData.n --;
stack[++top] = hanoiData; // 儲存待完成工作
swaphan(hanoiData.b,hanoiData.c); // 新的手頭工作
}
if (top != -1) // 存在待完成工作
{
hanoiData = stack[top--]; // 從棧中取出
move(hanoiData.a, hanoiData.c); // 直接處理
swaphan(hanoiData.c,hanoiData.a);// 未處理完的轉換成手頭工作
swaphan(hanoiData.b,hanoiData.c);
// stack[++top]=hanoiData;
}
}
}
int main()
{
int a[10]={4,3,2,1};
int b[10]={0};
int c[10]={0};
HANOIDATA hanoiData;
for(int j=0;j<10;j++)
{
hanoiData.b[j]=0;
hanoiData.c[j]=0;
if(j<4)
{
hanoiData.a[j]=4-j;
}
else
hanoiData.a[j]=0;
}
hanoiData.n=4;
hanoi(hanoiData);
int i;
for(i=0;i<4;++i)
{
printf("%d ",hanoiData.a[i]);
}
for( i=0;i<4;++i)
{
printf("%d ",hanoiData.b[i]);
}
for(i=0;i<4;++i)
{
printf("%d ",hanoiData.c[i]);
}
printf("\n");
return 0;
}
上面程式碼是為了實現非遞迴而寫的,一些處理方式可能不是是很好,主要是理解非遞迴實現。
四、後序遍歷
遞迴實現:
void PostTraverse(BINARYTREE root)
{
if (root == NULL) return;
PostTraverse(root->LChild);
PostTraverse(root->RChild);
Visit(root);
}
非遞迴實現:
void PostTraverse(BINARYTREE p)
{
while ( p != NULL || !Stack.IsEmpty() )// 儲存工作(手頭或待完成)
{
while (p != NULL) // 處理手頭工作,直到無現成手頭工作
{
Stack.Push(p, RCHILD_AND_ITSELF);
p = p->LChild;
}
if (!Stack.IsEmpty()) // 是否存在待完成工作
{
Stack.Pop(p, Tag);
if (Tag == RCHILD_AND_ITSELF) // 情況一: RChild & Itself
{
Stack.Push(p, ONLY_ITSELF) // 儲存待完成工作
p = p->RChild; // 新的手頭工作
}
else // tag == ONLY_ITSELF, 情況二: Only Itself
{
visit(p);
p = NULL; // 已無現成的手頭工作
}
}
}
}
熟能生巧!多加聯絡。