?? data structor.txt
字號(hào):
設(shè)Si 中的元素可排成xi(1) ,xi(2) ,…,xi(mi-1) ,|Si| =mi,i=1,2,…,n。從根開始,讓T的第I層的每一個(gè)結(jié)點(diǎn)都有mi個(gè)兒子。這mi個(gè)兒子到它們的雙親的邊,按從左到右的次序,分別帶權(quán)xi+1 (1) ,xi+1(2) ,…,xi+1(mi) ,i=0,1,2,…,n-1。照這種構(gòu)造方式,E中的一個(gè)n元組(x1,x2,…,xn)對(duì)應(yīng)于T中的一個(gè)葉子結(jié)點(diǎn),T的根到這個(gè)葉子結(jié)點(diǎn)的路徑上依次 的n條邊的權(quán)分別為x1,x2,…,xn,反之亦然。另外,對(duì)于任意的0≤i≤n-1,E中n元組(x1,x2,…,xn)的一個(gè)前綴I元組(x1, x2,…,xi)對(duì)應(yīng)于T中的一個(gè)非葉子結(jié)點(diǎn),T的根到這個(gè)非葉子結(jié)點(diǎn)的路徑上依次的I條邊的權(quán)分別為x1,x2,…,xi,反之亦然。特別,E中的任意 一個(gè)n元組的空前綴(),對(duì)應(yīng)于T的根。
因而,在E中尋找問題P的一個(gè)解等價(jià)于在T中搜索一個(gè)葉子結(jié)點(diǎn),要求從T的根到該葉子結(jié)點(diǎn)的路徑上依次的 n條邊相應(yīng)帶的n個(gè)權(quán)x1,x2,…,xn滿足約束集D的全部約束。在T中搜索所要求的葉子結(jié)點(diǎn),很自然的一種方式是從根出發(fā),按深度優(yōu)先的策略逐步深 入,即依次搜索滿足約束條件的前綴1元組(x1i)、前綴2元組(x1,x2)、…,前綴I元組(x1,x2,…,xi),…,直到i=n為止。
在回溯法中,上述引入的樹被稱為問題P的狀態(tài)空間樹;樹T上任意一個(gè)結(jié)點(diǎn)被稱為問題P的狀態(tài)結(jié)點(diǎn);樹T上的任意一個(gè)葉子結(jié)點(diǎn)被稱為問題P的一個(gè)解狀態(tài)結(jié)點(diǎn);樹T上滿足約束集D的全部約束的任意一個(gè)葉子結(jié)點(diǎn)被稱為問題P的一個(gè)回答狀態(tài)結(jié)點(diǎn),它對(duì)應(yīng)于問題P的一個(gè)解。
【問題】 組合問題
問題描述:找出從自然數(shù)1、2、……、n中任取r個(gè)數(shù)的所有組合。
例如n=5,r=3的所有組合為:
(1)1、2、3 (2)1、2、4 (3)1、2、5
(4)1、3、4 (5)1、3、5 (6)1、4、5
(7)2、3、4 (8)2、3、5 (9)2、4、5
(10)3、4、5
則該問題的狀態(tài)空間為:
E={(x1,x2,x3)∣xi∈S ,i=1,2,3 } 其中:S={1,2,3,4,5}
約束集為: x1
顯然該約束集具有完備性。
2、回溯法的方法
對(duì)于具有完備約束集D的一般問題P及其相應(yīng)的狀態(tài)空間樹T,利用T的層次結(jié)構(gòu)和D的完備性,在T中搜索問題P的所有解的回溯法可以形象地描述為:
從T 的根出發(fā),按深度優(yōu)先的策略,系統(tǒng)地搜索以其為根的子樹中可能包含著回答結(jié)點(diǎn)的所有狀態(tài)結(jié)點(diǎn),而跳過對(duì)肯定不含回答結(jié)點(diǎn)的所有子樹的搜索,以提高搜索效 率。具體地說,當(dāng)搜索按深度優(yōu)先策略到達(dá)一個(gè)滿足D中所有有關(guān)約束的狀態(tài)結(jié)點(diǎn)時(shí),即“激活”該狀態(tài)結(jié)點(diǎn),以便繼續(xù)往深層搜索;否則跳過對(duì)以該狀態(tài)結(jié)點(diǎn)為根 的子樹的搜索,而一邊逐層地向該狀態(tài)結(jié)點(diǎn)的祖先結(jié)點(diǎn)回溯,一邊“殺死”其兒子結(jié)點(diǎn)已被搜索遍的祖先結(jié)點(diǎn),直到遇到其兒子結(jié)點(diǎn)未被搜索遍的祖先結(jié)點(diǎn),即轉(zhuǎn)向 其未被搜索的一個(gè)兒子結(jié)點(diǎn)繼續(xù)搜索。
在搜索過程中,只要所激活的狀態(tài)結(jié)點(diǎn)又滿足終結(jié)條件,那么它就是回答結(jié)點(diǎn),應(yīng)該把它輸出或保存。由于在回溯法求解問題時(shí),一般要求出問題的所有解,因此在得到回答結(jié)點(diǎn)后,同時(shí)也要進(jìn)行回溯,以便得到問題的其他解,直至回溯到T的根且根的所有兒子結(jié)點(diǎn)均已被搜索過為止。
例 如在組合問題中,從T的根出發(fā)深度優(yōu)先遍歷該樹。當(dāng)遍歷到結(jié)點(diǎn)(1,2)時(shí),雖然它滿足約束條件,但還不是回答結(jié)點(diǎn),則應(yīng)繼續(xù)深度遍歷;當(dāng)遍歷到葉子結(jié)點(diǎn) (1,2,5)時(shí),由于它已是一個(gè)回答結(jié)點(diǎn),則保存(或輸出)該結(jié)點(diǎn),并回溯到其雙親結(jié)點(diǎn),繼續(xù)深度遍歷;當(dāng)遍歷到結(jié)點(diǎn)(1,5)時(shí),由于它已是葉子結(jié) 點(diǎn),但不滿足約束條件,故也需回溯。
3、回溯法的一般流程和技術(shù)
在用回溯法求解有關(guān)問題的過程中,一般是一邊建樹,一邊遍歷該樹。在回溯法中我們一般采用非遞歸方法。下面,我們給出回溯法的非遞歸算法的一般流程:
在用回溯法求解問題,也即在遍歷狀態(tài)空間樹的過程中,如果采用非遞歸方法,則我們一般要用到棧的數(shù)據(jù)結(jié)構(gòu)。這時(shí),不僅可以用棧來表示正在遍歷的樹的結(jié)點(diǎn),而且可以很方便地表示建立孩子結(jié)點(diǎn)和回溯過程。
例 如在組合問題中,我們用一個(gè)一維數(shù)組Stack[ ]表示棧。開始棧空,則表示了樹的根結(jié)點(diǎn)。如果元素1進(jìn)棧,則表示建立并遍歷(1)結(jié)點(diǎn);這時(shí)如果元素2進(jìn)棧,則表示建立并遍歷(1,2)結(jié)點(diǎn);元素3再 進(jìn)棧,則表示建立并遍歷(1,2,3)結(jié)點(diǎn)。這時(shí)可以判斷它滿足所有約束條件,是問題的一個(gè)解,輸出(或保存)。這時(shí)只要棧頂元素(3)出棧,即表示從結(jié) 點(diǎn)(1,2,3)回溯到結(jié)點(diǎn)(1,2)。
【問題】 組合問題
問題描述:找出從自然數(shù)1,2,…,n中任取r個(gè)數(shù)的所有組合。
采用回溯法找問題的解,將找到的組合以從小到大順序存于a[0],a[1],…,a[r-1]中,組合的元素滿足以下性質(zhì):
(1) a[i+1]>a[i],后一個(gè)數(shù)字比前一個(gè)大;
(2) a[i]-i<=n-r+1。
按回溯法的思想,找解過程可以敘述如下:
首 先放棄組合數(shù)個(gè)數(shù)為r的條件,候選組合從只有一個(gè)數(shù)字1開始。因該候選解滿足除問題規(guī)模之外的全部條件,擴(kuò)大其規(guī)模,并使其滿足上述條件(1),候選組合 改為1,2。繼續(xù)這一過程,得到候選組合1,2,3。該候選解滿足包括問題規(guī)模在內(nèi)的全部條件,因而是一個(gè)解。在該解的基礎(chǔ)上,選下一個(gè)候選解,因a [2]上的3調(diào)整為4,以及以后調(diào)整為5都滿足問題的全部要求,得到解1,2,4和1,2,5。由于對(duì)5不能再作調(diào)整,就要從a[2]回溯到a[1],這 時(shí),a[1]=2,可以調(diào)整為3,并向前試探,得到解1,3,4。重復(fù)上述向前試探和向后回溯,直至要從a[0]再回溯時(shí),說明已經(jīng)找完問題的全部解。按 上述思想寫成程序如下:
【程序】
# define MAXN 100
int a[MAXN];
void comb(int m,int r)
{ int i,j;
i=0;
a[i]=1;
do {
if (a[i]-i<=m-r+1
{ if (i==r-1)
{ for (j=0;j
printf(“%4d”,a[j]);
printf(“\n”);
}
a[i]++;
continue;
}
else
{ if (i==0)
return;
a[–i]++;
}
} while (1)
}
main()
{ comb(5,3);
}
【問題】 填字游戲
問題描述:在3×3個(gè)方格的方陣中要填入數(shù)字1到N(N≥10)內(nèi)的某9個(gè)數(shù)字,每個(gè)方格填一個(gè)整數(shù),似的所有相鄰兩個(gè)方格內(nèi)的兩個(gè)整數(shù)之和為質(zhì)數(shù)。試求出所有滿足這個(gè)要求的各種數(shù)字填法。
可 用試探發(fā)找到問題的解,即從第一個(gè)方格開始,為當(dāng)前方格尋找一個(gè)合理的整數(shù)填入,并在當(dāng)前位置正確填入后,為下一方格尋找可填入的合理整數(shù)。如不能為當(dāng)前 方格找到一個(gè)合理的可填證書,就要回退到前一方格,調(diào)整前一方格的填入數(shù)。當(dāng)?shù)诰艂€(gè)方格也填入合理的整數(shù)后,就找到了一個(gè)解,將該解輸出,并調(diào)整第九個(gè)的 填入的整數(shù),尋找下一個(gè)解。
為找到一個(gè)滿足要求的9個(gè)數(shù)的填法,從還未填一個(gè)數(shù)開始,按某種順序(如從小到大的順序)每次在當(dāng)前位置填入一個(gè)整 數(shù),然后檢查當(dāng)前填入的整數(shù)是否能滿足要求。在滿足要求的情況下,繼續(xù)用同樣的方法為下一方格填入整數(shù)。如果最近填入的整數(shù)不能滿足要求,就改變填入的整 數(shù)。如對(duì)當(dāng)前方格試盡所有可能的整數(shù),都不能滿足要求,就得回退到前一方格,并調(diào)整前一方格填入的整數(shù)。如此重復(fù)執(zhí)行擴(kuò)展、檢查或調(diào)整、檢查,直到找到一 個(gè)滿足問題要求的解,將解輸出。
回溯法找一個(gè)解的算法:
{ int m=0,ok=1;
int n=8;
do{
if (ok) 擴(kuò)展;
else 調(diào)整;
ok=檢查前m個(gè)整數(shù)填放的合理性;
} while ((!ok||m!=n)&&(m!=0))
if (m!=0) 輸出解;
else 輸出無解報(bào)告;
}
如果程序要找全部解,則在將找到的解輸出后,應(yīng)繼續(xù)調(diào)整最后位置上填放的整數(shù),試圖去找下一個(gè)解。相應(yīng)的算法如下:
回溯法找全部解的算法:
{ int m=0,ok=1;
int n=8;
do{
if (ok)
{ if (m==n)
{ 輸出解;
調(diào)整;
}
else 擴(kuò)展;
}
else 調(diào)整;
ok=檢查前m個(gè)整數(shù)填放的合理性;
} while (m!=0);
}
為 了確保程序能夠終止,調(diào)整時(shí)必須保證曾被放棄過的填數(shù)序列不會(huì)再次實(shí)驗(yàn),即要求按某種有許模型生成填數(shù)序列。給解的候選者設(shè)定一個(gè)被檢驗(yàn)的順序,按這個(gè)順 序逐一形成候選者并檢驗(yàn)。從小到大或從大到小,都是可以采用的方法。如擴(kuò)展時(shí),先在新位置填入整數(shù)1,調(diào)整時(shí),找當(dāng)前候選解中下一個(gè)還未被使用過的整數(shù)。 將上述擴(kuò)展、調(diào)整、檢驗(yàn)都編寫成程序,細(xì)節(jié)見以下找全部解的程序。
【程序】
# include
# define N 12
void write(int a[ ])
{ int i,j;
for (i=0;i<3;i++)
{ for (j=0;j<3;j++)
printf(“%3d”,a[3*i+j]);
printf(“\n”);
}
scanf(“%*c”);
}
int b[N+1];
int a[10];
int isprime(int m)
{ int i;
int primes[ ]={2,3,5,7,11,17,19,23,29,-1};
if (m==1||m%2=0) return 0;
for (i=0;primes[i]>0;i++)
if (m==primes[i]) return 1;
for (i=3;i*i<=m;)
{ if (m%i==0) return 0;
i+=2;
}
return 1;
}
int checkmatrix[ ][3]={ {-1},{0,-1},{1,-1},{0,-1},{1,3,-1},
{2,4,-1},{3,-1},{4,6,-1},{5,7,-1}};
int selectnum(int start)
{ int j;
for (j=start;j<=N;j++)
if (b[j]) return j
return 0;
}
int check(int pos)
{ int i,j;
if (pos<0) return 0;
for (i=0;(j=checkmatrix[pos][i])>=0;i++)
if (!isprime(a[pos]+a[j])
return 0;
return 1;
}
int extend(int pos)
{ a[++pos]=selectnum(1);
b[a][pos]]=0;
return pos;
}
int change(int pos)
{ int j;
while (pos>=0&&(j=selectnum(a[pos]+1))==0)
b[a[pos–]]=1;
if (pos<0) return –1
b[a[pos]]=1;
a[pos]=j;
b[j]=0;
return pos;
}
void find()
{ int ok=0,pos=0;
a[pos]=1;
b[a[pos]]=0;
do {
if (ok)
if (pos==8)
{ write(a);
pos=change(pos);
}
else pos=extend(pos);
else pos=change(pos);
ok=check(pos);
} while (pos>=0)
}
void main()
{ int i;
for (i=1;i<=N;i++)
b[i]=1;
find();
}
【問題】 n皇后問題
問題描述:求出在一個(gè)n×n的棋盤上,放置n個(gè)不能互相捕捉的國際象棋“皇后”的所有布局。
這是來源于國際象棋的一個(gè)問題。皇后可以沿著縱橫和兩條斜線4個(gè)方向相互捕捉。如圖所示,一個(gè)皇后放在棋盤的第4行第3列位置上,則棋盤上凡打“×”的位置上的皇后就能與這個(gè)皇后相互捕捉。
1 2 3 4 5 6 7 8
× ×
× × ×
× × ×
× × Q × × × × ×
× × ×
× × ×
× ×
× ×
從圖中可以得到以下啟示:一個(gè)合適的解應(yīng)是在每列、每行上只有一個(gè)皇后,且一條斜線上也只有一個(gè)皇后。
求 解過程從空配置開始。在第1列至第m列為合理配置的基礎(chǔ)上,再配置第m+1列,直至第n列配置也是合理時(shí),就找到了一個(gè)解。接著改變第n列配置,希望獲得 下一個(gè)解。另外,在任一列上,可能有n種配置。開始時(shí)配置在第1行,以后改變時(shí),順次選擇第2行、第3行、…、直到第n行。當(dāng)?shù)趎行配置也找不到一個(gè)合理 的配置時(shí),就要回溯,去改變前一列的配置。得到求解皇后問題的算法如下:
{ 輸入棋盤大小值n;
m=0;
good=1;
do {
if (good)
if (m==n)
{ 輸出解;
改變之,形成下一個(gè)候選解;
}
else 擴(kuò)展當(dāng)前候選接至下一列;
else 改變之,形成下一個(gè)候選解;
good=檢查當(dāng)前候選解的合理性;
} while (m!=0);
}
在 編寫程序之前,先確定邊式棋盤的數(shù)據(jù)結(jié)構(gòu)。比較直觀的方法是采用一個(gè)二維數(shù)組,但仔細(xì)觀察就會(huì)發(fā)現(xiàn),這種表示方法給調(diào)整候選解及檢查其合理性帶來困難。更 好的方法乃是盡可能直接表示那些常用的信息。對(duì)于本題來說,“常用信息”并不是皇后的具體位置,而是“一個(gè)皇后是否已經(jīng)在某行和某條斜線合理地安置好 了”。因在某一列上恰好放一個(gè)皇后,引入一個(gè)一維數(shù)組(col[ ]),值col[i]表示在棋盤第i列、col[i]行有一個(gè)皇后。例如:col[3]=4,就表示在棋盤的第3列、第4行上有一個(gè)皇后。另外,為了使程 序在找完了全部解后回溯到最初位置,設(shè)定col[0]的初值為0當(dāng)回溯到第0列時(shí),說明程序已求得全部解,結(jié)束程序運(yùn)行。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -