Feng.Li's Java See

          抓緊時間,大步向前。
          隨筆 - 95, 文章 - 4, 評論 - 58, 引用 - 0
          數(shù)據(jù)加載中……

          回溯算法

          常用算法設(shè)計方法——回溯法
          

          五、回溯法
             回溯法也稱為試探法,該方法首先暫時放棄關(guān)于問題規(guī)模大小的限制,并將問題的候選解按某種順序逐一枚舉和檢驗。當(dāng)發(fā)現(xiàn)當(dāng)前候選解不可能是解時,就選擇下一個候選解;倘若當(dāng)前候選解除了還不滿足問題規(guī)模要求外,滿足所有其他要求時,繼續(xù)擴大當(dāng)前候選解的規(guī)模,并繼續(xù)試探。如果當(dāng)前候選解滿足包括問題規(guī)模在內(nèi)的所有要求時,該候選解就是問題的一個解。在回溯法中,放棄當(dāng)前候選解,尋找下一個候選解的過程稱為回溯。擴大當(dāng)前候選解的規(guī)模,以繼續(xù)試探的過程稱為向前試探。
          1、回溯法的一般描述
          可用回溯法求解的問題P,通常要能表達為:對于已知的由n元組(x1,x2,…,xn)組成的一個狀態(tài)空間E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},給定關(guān)于n元組中的一個分量的一個約束集D,要求E中滿足D的全部約束條件的所有n元組。其中Si是分量xi的定義域,且 |Si| 有限,i=1,2,…,n。我們稱E中滿足D的全部約束條件的任一n元組為問題P的一個解。
          解問題P的最樸素的方法就是枚舉法,即對E中的所有n元組逐一地檢測其是否滿足D的全部約束,若滿足,則為問題P的一個解。但顯然,其計算量是相當(dāng)大的。
          我們發(fā)現(xiàn),對于許多問題,所給定的約束集D具有完備性,即i元組(x1,x2,…,xi)滿足D中僅涉及到x1,x2,…,xi的所有約束意味著j(j<i)元組(x1,x2,…,xj)一定也滿足D中僅涉及到x1,x2,…,xj的所有約束,i=1,2,…,n。換句話說,只要存在0≤j≤n-1,使得(x1,x2,…,xj)違反D中僅涉及到x1,x2,…,xj的約束之一,則以(x1,x2,…,xj)為前綴的任何n元組(x1,x2,…,xj,xj+1,…,xn)一定也違反D中僅涉及到x1,x2,…,xi的一個約束,n≥i>j。因此,對于約束集D具有完備性的問題P,一旦檢測斷定某個j元組(x1,x2,…,xj)違反D中僅涉及x1,x2,…,xj的一個約束,就可以肯定,以(x1,x2,…,xj)為前綴的任何n元組(x1,x2,…,xj,xj+1,…,xn)都不會是問題P的解,因而就不必去搜索它們、檢測它們。回溯法正是針對這類問題,利用這類問題的上述性質(zhì)而提出來的比枚舉法效率更高的算法。
          回溯法首先將問題P的n元組的狀態(tài)空間E表示成一棵高為n的帶權(quán)有序樹T,把在E中求問題P的所有解轉(zhuǎn)化為在T中搜索問題P的所有解。樹T類似于檢索樹,它可以這樣構(gòu)造:
             設(shè)Si中的元素可排成xi(1) ,xi(2) ,…,xi(mi-1) ,|Si| =mi,i=1,2,…,n。從根開始,讓T的第I層的每一個結(jié)點都有mi個兒子。這mi個兒子到它們的雙親的邊,按從左到右的次序,分別帶權(quán)xi+1(1) ,xi+1(2) ,…,xi+1(mi) ,i=0,1,2,…,n-1。照這種構(gòu)造方式,E中的一個n元組(x1,x2,…,xn)對應(yīng)于T中的一個葉子結(jié)點,T的根到這個葉子結(jié)點的路徑上依次的n條邊的權(quán)分別為x1,x2,…,xn,反之亦然。另外,對于任意的0≤i≤n-1,E中n元組(x1,x2,…,xn)的一個前綴I元組(x1,x2,…,xi)對應(yīng)于T中的一個非葉子結(jié)點,T的根到這個非葉子結(jié)點的路徑上依次的I條邊的權(quán)分別為x1,x2,…,xi,反之亦然。特別,E中的任意一個n元組的空前綴(),對應(yīng)于T的根。
             因而,在E中尋找問題P的一個解等價于在T中搜索一個葉子結(jié)點,要求從T的根到該葉子結(jié)點的路徑上依次的n條邊相應(yīng)帶的n個權(quán)x1,x2,…,xn滿足約束集D的全部約束。在T中搜索所要求的葉子結(jié)點,很自然的一種方式是從根出發(fā),按深度優(yōu)先的策略逐步深入,即依次搜索滿足約束條件的前綴1元組(x1i)、前綴2元組(x1,x2)、…,前綴I元組(x1,x2,…,xi),…,直到i=n為止。
             在回溯法中,上述引入的樹被稱為問題P的狀態(tài)空間樹;樹T上任意一個結(jié)點被稱為問題P的狀態(tài)結(jié)點;樹T上的任意一個葉子結(jié)點被稱為問題P的一個解狀態(tài)結(jié)點;樹T上滿足約束集D的全部約束的任意一個葉子結(jié)點被稱為問題P的一個回答狀態(tài)結(jié)點,它對應(yīng)于問題P的一個解。
          【問題】   組合問題
          問題描述:找出從自然數(shù)1、2、……、n中任取r個數(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<x2<x3
             顯然該約束集具有完備性。
          問題的狀態(tài)空間樹T:













          2、回溯法的方法
             對于具有完備約束集D的一般問題P及其相應(yīng)的狀態(tài)空間樹T,利用T的層次結(jié)構(gòu)和D的完備性,在T中搜索問題P的所有解的回溯法可以形象地描述為:
             從T的根出發(fā),按深度優(yōu)先的策略,系統(tǒng)地搜索以其為根的子樹中可能包含著回答結(jié)點的所有狀態(tài)結(jié)點,而跳過對肯定不含回答結(jié)點的所有子樹的搜索,以提高搜索效率。具體地說,當(dāng)搜索按深度優(yōu)先策略到達一個滿足D中所有有關(guān)約束的狀態(tài)結(jié)點時,即“激活”該狀態(tài)結(jié)點,以便繼續(xù)往深層搜索;否則跳過對以該狀態(tài)結(jié)點為根的子樹的搜索,而一邊逐層地向該狀態(tài)結(jié)點的祖先結(jié)點回溯,一邊“殺死”其兒子結(jié)點已被搜索遍的祖先結(jié)點,直到遇到其兒子結(jié)點未被搜索遍的祖先結(jié)點,即轉(zhuǎn)向其未被搜索的一個兒子結(jié)點繼續(xù)搜索。
          在搜索過程中,只要所激活的狀態(tài)結(jié)點又滿足終結(jié)條件,那么它就是回答結(jié)點,應(yīng)該把它輸出或保存。由于在回溯法求解問題時,一般要求出問題的所有解,因此在得到回答結(jié)點后,同時也要進行回溯,以便得到問題的其他解,直至回溯到T的根且根的所有兒子結(jié)點均已被搜索過為止。
             例如在組合問題中,從T的根出發(fā)深度優(yōu)先遍歷該樹。當(dāng)遍歷到結(jié)點(1,2)時,雖然它滿足約束條件,但還不是回答結(jié)點,則應(yīng)繼續(xù)深度遍歷;當(dāng)遍歷到葉子結(jié)點(1,2,5)時,由于它已是一個回答結(jié)點,則保存(或輸出)該結(jié)點,并回溯到其雙親結(jié)點,繼續(xù)深度遍歷;當(dāng)遍歷到結(jié)點(1,5)時,由于它已是葉子結(jié)點,但不滿足約束條件,故也需回溯。
          3、回溯法的一般流程和技術(shù)
             在用回溯法求解有關(guān)問題的過程中,一般是一邊建樹,一邊遍歷該樹。在回溯法中我們一般采用非遞歸方法。下面,我們給出回溯法的非遞歸算法的一般流程:

          在用回溯法求解問題,也即在遍歷狀態(tài)空間樹的過程中,如果采用非遞歸方法,則我們一般要用到棧的數(shù)據(jù)結(jié)構(gòu)。這時,不僅可以用棧來表示正在遍歷的樹的結(jié)點,而且可以很方便地表示建立孩子結(jié)點和回溯過程。
          例如在組合問題中,我們用一個一維數(shù)組Stack[ ]表示棧。開始棧空,則表示了樹的根結(jié)點。如果元素1進棧,則表示建立并遍歷(1)結(jié)點;這時如果元素2進棧,則表示建立并遍歷(1,2)結(jié)點;元素3再進棧,則表示建立并遍歷(1,2,3)結(jié)點。這時可以判斷它滿足所有約束條件,是問題的一個解,輸出(或保存)。這時只要棧頂元素(3)出棧,即表示從結(jié)點(1,2,3)回溯到結(jié)點(1,2)。
          【問題】   組合問題
          問題描述:找出從自然數(shù)1,2,…,n中任取r個數(shù)的所有組合。
          采用回溯法找問題的解,將找到的組合以從小到大順序存于a[0],a[1],…,a[r-1]中,組合的元素滿足以下性質(zhì):
          (1)   a[i+1]>a[i],后一個數(shù)字比前一個大;
          (2)   a[i]-i<=n-r+1。
          按回溯法的思想,找解過程可以敘述如下:
             首先放棄組合數(shù)個數(shù)為r的條件,候選組合從只有一個數(shù)字1開始。因該候選解滿足除問題規(guī)模之外的全部條件,擴大其規(guī)模,并使其滿足上述條件(1),候選組合改為1,2。繼續(xù)這一過程,得到候選組合1,2,3。該候選解滿足包括問題規(guī)模在內(nèi)的全部條件,因而是一個解。在該解的基礎(chǔ)上,選下一個候選解,因a[2]上的3調(diào)整為4,以及以后調(diào)整為5都滿足問題的全部要求,得到解1,2,4和1,2,5。由于對5不能再作調(diào)整,就要從a[2]回溯到a[1],這時,a[1]=2,可以調(diào)整為3,并向前試探,得到解1,3,4。重復(fù)上述向前試探和向后回溯,直至要從a[0]再回溯時,說明已經(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<r;j++)
                         printf(“%4d”,a[j]);
                      printf(“”);
                   }
                   a[i]++;
                   continue;
                }
                else
                {   if (i==0)
                      return;
                   a[--i]++;
                }
             }   while (1)
          }

          main()
          {   comb(5,3);
          }
          【問題】   填字游戲
          問題描述:在3×3個方格的方陣中要填入數(shù)字1到N(N≥10)內(nèi)的某9個數(shù)字,每個方格填一個整數(shù),似的所有相鄰兩個方格內(nèi)的兩個整數(shù)之和為質(zhì)數(shù)。試求出所有滿足這個要求的各種數(shù)字填法。
          可用試探發(fā)找到問題的解,即從第一個方格開始,為當(dāng)前方格尋找一個合理的整數(shù)填入,并在當(dāng)前位置正確填入后,為下一方格尋找可填入的合理整數(shù)。如不能為當(dāng)前方格找到一個合理的可填證書,就要回退到前一方格,調(diào)整前一方格的填入數(shù)。當(dāng)?shù)诰艂€方格也填入合理的整數(shù)后,就找到了一個解,將該解輸出,并調(diào)整第九個的填入的整數(shù),尋找下一個解。
          為找到一個滿足要求的9個數(shù)的填法,從還未填一個數(shù)開始,按某種順序(如從小到大的順序)每次在當(dāng)前位置填入一個整數(shù),然后檢查當(dāng)前填入的整數(shù)是否能滿足要求。在滿足要求的情況下,繼續(xù)用同樣的方法為下一方格填入整數(shù)。如果最近填入的整數(shù)不能滿足要求,就改變填入的整數(shù)。如對當(dāng)前方格試盡所有可能的整數(shù),都不能滿足要求,就得回退到前一方格,并調(diào)整前一方格填入的整數(shù)。如此重復(fù)執(zhí)行擴展、檢查或調(diào)整、檢查,直到找到一個滿足問題要求的解,將解輸出。
          回溯法找一個解的算法:
          {   int m=0,ok=1;
             int n=8;
             do{
                if (ok)   擴展;
                else      調(diào)整;
                ok=檢查前m個整數(shù)填放的合理性;
             }   while ((!ok||m!=n)&&(m!=0))
             if (m!=0)   輸出解;
             else      輸出無解報告;
          }
          如果程序要找全部解,則在將找到的解輸出后,應(yīng)繼續(xù)調(diào)整最后位置上填放的整數(shù),試圖去找下一個解。相應(yīng)的算法如下:
          回溯法找全部解的算法:
          {    int m=0,ok=1;
             int n=8;
             do{
                if (ok)   
          {   if (m==n)   
          {   輸出解;
          調(diào)整;
          }
          else   擴展;
                   }
                   else      調(diào)整;
                ok=檢查前m個整數(shù)填放的合理性;
             }   while (m!=0);
          }
          為了確保程序能夠終止,調(diào)整時必須保證曾被放棄過的填數(shù)序列不會再次實驗,即要求按某種有許模型生成填數(shù)序列。給解的候選者設(shè)定一個被檢驗的順序,按這個順序逐一形成候選者并檢驗。從小到大或從大到小,都是可以采用的方法。如擴展時,先在新位置填入整數(shù)1,調(diào)整時,找當(dāng)前候選解中下一個還未被使用過的整數(shù)。將上述擴展、調(diào)整、檢驗都編寫成程序,細節(jié)見以下找全部解的程序。
          【程序】
          # include <stdio.h>
          # 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(“”);
             }
             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皇后問題
          問題描述:求出在一個n×n的棋盤上,放置n個不能互相捕捉的國際象棋“皇后”的所有布局。
             這是來源于國際象棋的一個問題。皇后可以沿著縱橫和兩條斜線4個方向相互捕捉。如圖所示,一個皇后放在棋盤的第4行第3列位置上,則棋盤上凡打“×”的位置上的皇后就能與這個皇后相互捕捉。
             
          1   2   3   4   5   6   7   8
                ×         ×      
          ×      ×      ×         
             ×   ×   ×            
          ×   ×   Q   ×   ×   ×   ×   ×
             ×   ×   ×            
          ×      ×      ×         
                ×         ×      
                ×            ×   
          從圖中可以得到以下啟示:一個合適的解應(yīng)是在每列、每行上只有一個皇后,且一條斜線上也只有一個皇后。
             求解過程從空配置開始。在第1列至第m列為合理配置的基礎(chǔ)上,再配置第m+1列,直至第n列配置也是合理時,就找到了一個解。接著改變第n列配置,希望獲得下一個解。另外,在任一列上,可能有n種配置。開始時配置在第1行,以后改變時,順次選擇第2行、第3行、…、直到第n行。當(dāng)?shù)趎行配置也找不到一個合理的配置時,就要回溯,去改變前一列的配置。得到求解皇后問題的算法如下:
             {   輸入棋盤大小值n;
                m=0;
                good=1;
                do {
                   if (good)
                      if (m==n)
                      {   輸出解;
                         改變之,形成下一個候選解;
                      }
                      else   擴展當(dāng)前候選接至下一列;
                   else   改變之,形成下一個候選解;
                   good=檢查當(dāng)前候選解的合理性;
                } while (m!=0);
             }
             在編寫程序之前,先確定邊式棋盤的數(shù)據(jù)結(jié)構(gòu)。比較直觀的方法是采用一個二維數(shù)組,但仔細觀察就會發(fā)現(xiàn),這種表示方法給調(diào)整候選解及檢查其合理性帶來困難。更好的方法乃是盡可能直接表示那些常用的信息。對于本題來說,“常用信息”并不是皇后的具體位置,而是“一個皇后是否已經(jīng)在某行和某條斜線合理地安置好了”。因在某一列上恰好放一個皇后,引入一個一維數(shù)組(col[ ]),值col[i]表示在棋盤第i列、col[i]行有一個皇后。例如:col[3]=4,就表示在棋盤的第3列、第4行上有一個皇后。另外,為了使程序在找完了全部解后回溯到最初位置,設(shè)定col[0]的初值為0當(dāng)回溯到第0列時,說明程序已求得全部解,結(jié)束程序運行。
             為使程序在檢查皇后配置的合理性方面簡易方便,引入以下三個工作數(shù)組:
          (1)   數(shù)組a[ ],a[k]表示第k行上還沒有皇后;
          (2)   數(shù)組b[ ],b[k]表示第k列右高左低斜線上沒有皇后;
          (3)   數(shù)組 c[ ],c[k]表示第k列左高右低斜線上沒有皇后;
          棋盤中同一右高左低斜線上的方格,他們的行號與列號之和相同;同一左高右低斜線上的方格,他們的行號與列號之差均相同。
             初始時,所有行和斜線上均沒有皇后,從第1列的第1行配置第一個皇后開始,在第m列col[m]行放置了一個合理的皇后后,準(zhǔn)備考察第m+1列時,在數(shù)組a[ ]、b[ ]和c[ ]中為第m列,col[m]行的位置設(shè)定有皇后標(biāo)志;當(dāng)從第m列回溯到第m-1列,并準(zhǔn)備調(diào)整第m-1列的皇后配置時,清除在數(shù)組a[ ]、b[ ]和c[ ]中設(shè)置的關(guān)于第m-1列,col[m-1]行有皇后的標(biāo)志。一個皇后在m列,col[m]行方格內(nèi)配置是合理的,由數(shù)組a[ ]、b[ ]和c[ ]對應(yīng)位置的值都為1來確定。細節(jié)見以下程序:
          【程序】
          # include   <stdio.h>
          # include   <stdlib.h>
          # define   MAXN   20
          int n,m,good;
          int col[MAXN+1],a[MAXN+1],b[2*MAXN+1],c[2*MAXN+1];

          void main()
          {   int j;
             char awn;
             printf(“Enter n:    “);   scanf(“%d”,&n);
             for (j=0;j<=n;j++)   a[j]=1;
             for (j=0;j<=2*n;j++)   cb[j]=c[j]=1;
             m=1;   col[1]=1;      good=1;   col[0]=0;
             do {
                if (good)
                   if (m==n)
                   {   printf(“列 行”);
                      for (j=1;j<=n;j++)
                         printf(“%3d %d”,j,col[j]);
                      printf(“Enter  a  character (Q/q  for  exit)!”);
                      scanf(“%c”,&awn);
                      if (awn==’Q’||awn==’q’)   exit(0);
                      while (col[m]==n)
                      {   m--;
                         a[col[m]]=b[m+col[m]]=c[n+m-col[m]]=1;
                      }
                      col[m]++;
                   }
                   else
                   {   a[col[m]]=b[m+col[m]]=c[n+m-col[m]]=0;
                      col[++m]=1;
                   }
                else
                {   while (col[m]==n)
                   {   m--;
                      a[col[m]]=b[m+col[m]]=c[n+m-col[m]]=1;
                   }
                   col[m]++;
                }
                good=a[col[m]]&&b[m+col[m]]&&c[n+m-col[m]];
             } while (m!=0);
          }
             試探法找解算法也常常被編寫成遞歸函數(shù),下面兩程序中的函數(shù)queen_all()和函數(shù)queen_one()能分別用來解皇后問題的全部解和一個解。
          【程序】
          # include   <stdio.h>
          # include   <stdlib.h>
          # define   MAXN   20
          int n;
          int col[MAXN+1],a[MAXN+1],b[2*MAXN+1],c[2*MAXN+1];
          void main()
          {   int j;
             printf(“Enter n:    “);   scanf(“%d”,&n);
             for (j=0;j<=n;j++)   a[j]=1;
             for (j=0;j<=2*n;j++)   cb[j]=c[j]=1;
             queen_all(1,n);
          }

          void queen_all(int k,int n)
          {   int i,j;
             char awn;
             for (i=1;i<=n;i++)
                if (a[i]&&b[k+i]&&c[n+k-i])
                {   col[k]=i;
                   a[i]=b[k+i]=c[n+k-i]=0;
                   if (k==n)
                   {   printf(“列 行”);
                      for (j=1;j<=n;j++)
                         printf(“%3d %d”,j,col[j]);
                      printf(“Enter  a  character (Q/q  for  exit)!”);
                      scanf(“%c”,&awn);
                      if (awn==’Q’||awn==’q’)   exit(0);
                   }
                   queen_all(k+1,n);
                   a[i]=b[k+i]=c[n+k-i];
                }
          }
             采用遞歸方法找一個解與找全部解稍有不同,在找一個解的算法中,遞歸算法要對當(dāng)前候選解最終是否能成為解要有回答。當(dāng)它成為最終解時,遞歸函數(shù)就不再遞歸試探,立即返回;若不能成為解,就得繼續(xù)試探。設(shè)函數(shù)queen_one()返回1表示找到解,返回0表示當(dāng)前候選解不能成為解。細節(jié)見以下函數(shù)。
          【程序】
          # define   MAXN   20
             int n;
             int col[MAXN+1],a[MAXN+1],b[2*MAXN+1],c[2*MAXN+1];
             int queen_one(int k,int n)
             {   int i,found;
                i=found=0;
                While (!found&&i<n)
                {   i++;
                   if (a[i]&&b[k+i]&&c[n+k-i])
                   {   col[k]=i;
                      a[i]=b[k+i]=c[n+k-i]=0;
                      if (k==n)   return 1;
                      else
                         found=queen_one(k+1,n);
                      a[i]=b[k+i]=c[n+k-i]=1;
                   }
                }
                return found;
             }

          posted on 2007-06-14 09:41 小鋒 閱讀(1164) 評論(1)  編輯  收藏 所屬分類: algorithm

          評論

          # re: 回溯算法  回復(fù)  更多評論   

          1,2,3,4,5,6,7,8,9九個數(shù)字排成3*3方陣,若每行從左到右每列從上到下都是遞增的,則這樣的不同方陣有多少?
          2008-09-14 10:57 | 小王
          主站蜘蛛池模板: 郯城县| 锡林浩特市| 库伦旗| 河曲县| 万载县| 高雄县| 阜新市| 农安县| 巴林左旗| 宝坻区| 平山县| 安岳县| 酉阳| 聊城市| 嘉兴市| 阿勒泰市| 九龙县| 西丰县| 乐陵市| 习水县| 滦南县| 苏州市| 北海市| 新源县| 铁岭县| 饶河县| 博兴县| 镇坪县| 永靖县| 都兰县| 视频| 平湖市| 延吉市| 肇州县| 大同县| 瑞丽市| 东方市| 崇明县| 玉屏| 河南省| 彰化县|