17圖的搜索算法之回溯法


回 溯 法

       回溯算法實際是一個類似枚舉的搜索嘗試方法,它的主題思想是在搜索嘗試中找問題的解,當不滿足求解條件就”回溯”返回,嘗試別的路徑。回溯算法是嘗試搜索算法中最為基本的一種算法,其采用了一種“走不通就掉頭”的思想,作為其控制結構。

【例1】八皇后問題模型建立

     要在8*8的國際象棋棋盤中放八個皇后,使任意兩個皇后都不能互相吃掉。規則:皇后能吃掉同一行、同一列、同一對角線的任意棋子。如圖5-12為一種方案,求所有的解。

模型建立    

      不妨設八個皇后為xi,她們分別在第i行(i=1,2,3,4……,8),這樣問題的解空間,就是一個八個皇后所在列的序號,為n元一維向量(x1,x2,x3,x4,x5,x6,x7,x8),搜索空間是1≤xi≤8(i=1,2,3,4……,8),共88個狀態。約束條件是八個(1,x1),(2,x2) ,(3,x3),(4,x4) ,(5,x5), (6,x6) , (7,x7), (8,x8)不在同一行、同一列和同一對角線上。

   雖然問題共有88個狀態,但算法不會真正地搜索這么多的狀態,因為前面已經說明,回溯法采用的是“走不通就掉頭”的策略,而形如(1,1,x3,x4, x5,x6,x7,x8)的狀態共有86個,由於1,2號皇后

在同一列不滿足約束條件,回溯后這86個狀態是不會搜索的。

                                   

算法設計1:加約束條件的枚舉算法  

   最簡單的算法就是通過八重循環模擬搜索空間中的88個狀態,按深度優先思想,從第一個皇后從第一列開始搜索,每前進一步檢查是否滿足約束條件,不滿足時,用continue語句回溯,滿足滿足約束條件,開始下一層循環,直到找出問題的解。

約束條件不在同一列的表達式為xi xj;而在同一主對角線上時xi-i=xj-j, 在同一負對角線上時xi+i=xj+j,因此,不在同一對角線上的約束條件表示為abs(xi-xj) abs(i-j)(abs()取絕對值)。

算法1:

queen1(  )
{int a[9];
 for (a[1]=1;a[1]<=8;a[1]++)     
  for (a[2]=1;a[2]<=8;a[2]++)  
   {if ( check(a,2)=0 )  continue;
    for (a[3]=1;a[3]<=8;a[3]++)     
      {if(check(a,3)=0)   continue;
         for (a[4]=1;a[4]<=8;a[4]++)
          {if  (check(a,4)=0)   continue;
            for (a[5]=1;a[5]<=8;a[5]++)
             {if (check(a,5)=0)   continue;
               for (a[6]=1;a[6]<=8;a[6]++)  
                 {if  (check(a,6)=0)   continue;
for(a[7]=1;a[7]<=8;a[7]++)   
   {if (check(a,7)=0)   continue;
      for(a[8]=1;a[8]<=8;a[8]++)
         {if (check(a,8)=0)  
              continue;
          else  
              for(i=1;i<=8;i++)
                  print(a[i]);
          }
    } } } } } } }

check(int a[ ],int n)
{int i;
 for(i=1;i<=n-1;i++)
  if (abs(a[i]-a[n])=abs(i-n)) or (a[i]=a[n])
    return(0);
return(1);
}

算法分析1:

    若將算法中循環嵌套間的檢查是否滿足約束條件的:

   “if  (check(a[],i)=0)continue;

          i=2,3,4,5,6,7“

   語句都去掉,只保留最后一個檢查語句:

   “if  (check(a[],8)=0)continue;”

相應地check()函數修改成:

check*(a[],n)

{int i,j;

 for(i=2;i<=n;i++)
   for(j=1;j<=i-1;j++)
     if(abs(a[i]-a[j])=abs(i-j))or(a[i]=a[j])
      return(0);

 return(1);
}                                   

  則算法退化成完全的盲目搜索,復雜性就是88

 

 

 

  算法設計2:非遞歸回溯算法

     以上的枚舉算法可讀性很好,但它只能解決八皇后問題,而不能解決任意的n皇后問題。下面的非遞歸算法可以說是典型的回溯算法模型。

算法2:

 int a[20],n; 
queen2(  )
{ 
input(n);
          backdate(n);	
}
backdate (int n)
{ int k;
  a[1]=0;       k=1;
  while( k>0 )
   {a[k]=a[k]+1;
   while ((a[k]<=n) and (check(k)=0)) /搜索第k個皇后位置/
          a[k]=a[k]+1;
   if( a[k]<=n)
       if(k=n ) output(n);     / 找到一組解/
    else {k=k+1; 繼續為第k+1個皇后找到位置/
            a[k]=0;}/注意下一個皇后一定要從頭開始搜索/
   else  k=k-1;           /回溯/
    }
}
check(int k)
{ int i;
  for(i=1;i<=k-1;i++)
 if (abs(a[i]-a[k])=abs(i-k)) or  (a[i]=a[k])
   return(0);
   return(1);
}
output( )
{ int i;
  for(i=1;i<=n;i++)
     print(a[i]);
}

  

 算法設計3:遞歸算法

      這種方式也可以解決任意的n皇后問題。

       這里我們用第三章3.2.3 “利用數組記錄狀態信息”的技巧,用三個數組c,b,d分別記錄棋盤上的n個列、n個主對角線和n個負對角線的占用情況。

以四階棋盤為例,如圖5-13,共有2n-1=7個主對角線,對應地也有7個負對角線。

   用i,j分別表示皇后所在的行列,同一主對角線上的行列下標的差一樣,若用表達式i-j編號,則范圍為-n+1——n-1,所以我們用表達式i-j+n對主對角線編號,范圍就是1——2n-1。同樣地,負對角線上行列下標的和一樣,用表達式i+j編號,則范圍為2——2n。

 

算法3:

 

   int a[20],b[20],c[40],d[40];
     int n,t,i,j,k;      /t記錄解的個數/
     queen3(  )
     { int i,
       input(n);
       for(i=1;i<=n;i++)
          { b[i]=0;
            c[i]=0; c[n+i]=0;
            d[i]=0; d[n+i]=0;
           }
       try(1);
     }
try(int i)
{int j;
 for(j=1;j<=n;j++)      /第i個皇后有n種可能位置/
   if (b[j]=0) and (c[i+j]=0) and (d[i-j+n]=0)
    {a[i]=j;           /擺放皇后/
     b[j]=1;           /占領第j列/
     c[i+j]=1; d[i-j+n]=1;   /占領兩個對角線/
     if (i<n) 
       try(i+1); /n個皇后沒有擺完,遞歸擺放下一皇后/
    else 
       output( );        /完成任務,打印結果/
    b[j]=0; c[i+j]=0;   d[i-j+n]=0; /回溯/
   }     
}
output( )
{  t=t+1;
    print(t,'       ');
    for( k=1;k<=n;k++)
           print(a[k],'   ');
    print(“ 換行符”);
   }

遞歸算法的回溯是由函數調用結束自動完成的,也不需要指出回溯點,但也需要“清理現場”——將當前點占用的位置釋放,也就是算法try()中的后三個賦值語句。

 

 

1. 回溯法基本思想

    回溯法是在包含問題的所有解的解空間樹中。按照深度優先的策略,從根結點出發搜索解空間樹,算法搜索至解空間樹的任一結點時,總是先判斷該結點是否滿足問題的約束條件。如果滿足進入該子樹,繼續按深度優先的策略進行搜索。否則,不去搜索以該結點為根的子樹,而是逐層向其祖先結點回溯。

  回溯法就是對隱式圖的深度優先搜索算法。 

如圖5-14是四皇后問題的搜索過程

 

          圖5-14四皇后問題的解空間樹

2.算法設計過程

   1)確定問題的解空間

         問題的解空間應只至少包含問題的一個解。

   2)確定結點的擴展規則

   如每個皇后在一行中的不同位置移動,而象棋中的馬只能走“日”字等。

3) 搜索解空間

       回溯算法從開始結點出發,以深度優先的方式搜索整個解空間。這個開始結點就成為一個活結點,同時也成為當前的擴展結點。在當前的擴展結點處,搜索向縱深方向移至一個新結點。這個新結點就成為一個新的活結點,並成為當前擴展結點。如果在當前的擴展結點處不能再向縱深方向移動,則當前擴展結點就成為死結點。此時,應往回移動至最近的一個活結點處,並使這個活結點成為當前的擴展結點。回溯法即以這種工作方式遞歸地在解空間中搜索,直至找到所要求的解或解空間中已沒有活結點時為止。

 

3.算法框架

 1)問題框架

       設問題的解是一個n維向量(a1,a2,……,an),約束條件是ai(i=1,2,3……n)之間滿足某種條件,記為f(ai)

2)非遞歸回溯框架

int a[n],i;

初始化數組a[ ];

i=1;

While (i>0(有路可走)) and ([未達到目標]) /還未回溯到頭/

 {if (i>n)         /正在處理第i個元素/

     搜索到一個解,輸出;

  else

   {a[i]第一個可能的值;

    while (a[i]在不滿足約束條件  且  在在搜索空間內)  

       a[i]下一個可能的值;

    if (a[i]在搜索空間內)      

      {標識占用的資源; i=i+1;}    /擴展下一個結點/

    else  {清理所占的狀態空間;  i=i-1;}/回溯/

  }}

3)遞歸算法框架

    一般情況下用遞歸函數來實現回溯法比較簡單,其中i為搜索深度。

int a[n];

try(int i)
{if (i>n) 輸出結果;
 else

   for( j=下界 ; j<=上界; j++) /枚舉i所有可能的路徑/
       { if ( f(j) )        /滿足限界函數和約束條件/

            { a[i]=j;

              ……            /其它操作/

             try(i+ 1);}
             }

        回溯前的清理工作(如a[i]置空值等);

        }

 }

 

 

應用1 ——基本的回溯搜索

【例2】馬的遍歷問題

       在n*m的棋盤中,馬只能走日字。馬從位置(x,y)處出發,把棋盤的每一格都走一次,且只走一次。找出所有路徑。

1、問題分析

      馬是在棋盤的點上行走的,所以這里的棋盤是指行有N條邊、列有M條邊。而一個馬在不出邊界的情況下有八個方向可以行走,如當前坐標為(x,y)則行走后的坐標可以為:

(x+1,y+2) ,( x+1, y-2),( x+2, y+1),

( x+2, y-1),(x-1, y -2),(x -1, y+2),

( x -2, y -1),( x -2, y+1)

2、算法設計

      搜索空間是整個n*m個棋盤上的點。約束條件是不出邊界且每個點只經過一次。結點的擴展規則如問題分析中所述。

      搜索過程是從任一點(x,y)出發,按深度優先的原則,從八個方向中嘗試一個可以走的點,直到走過棋盤上所有n*m個點。用遞歸算法易實現此過程。

      注意問題要求找出全部可能的解,就要注意回溯過程的清理現場工作,也就是置當前位置為未經過。

3、數據結構設計

    1)用一個變量dep記錄遞歸深度,也就是走過的點數,當dep=n*m時,找到一組解。

    2)用n*m的二維數組記錄馬行走的過程,初始值為0表示未經過。搜索完畢后,起點存儲的是“1”,終點存儲的是 “n*m”。

4、算法

  int  n=5 , m=4, dep , i, x , y , count;
   int  fx[8]={1,2,2,1,-1,-2,-2,-1} , 
        fy[8]={2,1,-1,-2,-2,-1,1,2} , a[n][m];
main( )
{count=0;      dep=1;
 print('input x,y');      input(x,y);
 if (y>n  or  x>m  or  x<1  or  y<1) 
     { print('x,y error!');   return;}
 for(i=1;i<=;i++)  for(j=1;j<=;j++)  a[i][j]=0;
 a[x][y]=1;
 find(x,y,2);
 if  (count=0 )     print(“No answer!”);
   else              print(“count=!”,count);
}
find(int y,int x,int dep)
{int  i , xx , yy ;
for i=1 to 8 do      /加上方向增量,形成新的坐標/
 {xx=x+fx[i];     yy=y+fy[i]; 
  if (check(xx,yy)=1) /判斷新坐標是否出界,是否已走過?/
     {a[xx,yy]=dep;      /走向新的坐標/
      if (dep=n*m) 
        output( );
      else 
        find(xx,yy,dep+1);    /從新坐標出發,遞歸下一層/
      a[xx,yy]=0;            /回溯,恢復未走標志/
      }
   }
}
  output( ) 
 {     count=count+1;
       print(“換行符”);
     print('count=',count);
     for y=1 to n do 
          {print(“換行符”);
           for x=1 to m do 
               print(a[y,x]:3);
         }
  }

  

 

【例3】素數環問題

      把從1到20這20個數擺成一個環,要求相鄰的兩個數的和是一個素數。         

 1、算法設計

       嘗試搜索從1開始,每個空位有2——20共19種可能,約束條件就是填進去的數滿足:與前面的數不相同;與前面相鄰數據的和是一個素數。第20個數還要判斷和第1個數的和是否素數。

2、算法

main()
       { int a[20],k;
         for (k=1;k<=20;k++) 
              a[k]=0;
         a[1]=1;
         try(2);
       }
   try(int i)
   { int k
     for (k=2;k<=20;k++)
         if (check1(k,i)=1 and check3(k,i)=1 )
            { a[i]=k;
              if (i=20) 
                  output( );
              else 
                 {try(i+1);
                  a[i]=0;}
             }
   }
check1(int j,int i)
{ int k;
  for (k=1;k<=i-1;k++)
      if (a[k]=j )  return(0);
  return(1);
}
check2(int x)
{ int k,n;
  n= sqrt(x);
  for (k=2;k<=n;k++)
       if (x mod k=0 )  return(0);
  return(1);
}
check3(int j,int i)
{ if (i<20)   return(check2(j+a[i-1]));
  else
    return(check2(j+a[i-1]) and check2(j+a[1]));
}
output( )
{ int k;
  for (k=1;k<=20;k++)
       print(a[k]);
  print(“換行符”); 
}

3、算法說明

     這個算法中也要注意在回溯前要“清理現場”,也就是置a[i]為0。

 

 

【例4】找n個數中r個數的組合。

1、算法設計  先分析數據的特點,以n=5,r=3為例

   在數組a中: a[1]  a[2] a[3]

                5   4    3

                5   4    2

                5   4    1

                5   3    2 

                5   3    1

                5   2    1

                4   3    2

                4   3    1

                4   2    1

                3   2    1

分析數據的特點,搜索時依次對數組(一維向量)元素a[1]、a[2]、a[3]進行嘗試,

a[ri]      i1——i2

a[1]嘗試范圍5——3

a[2]嘗試范圍4——2

a[3]嘗試范圍3——1

且有這樣的規律:“后一個元素至少比前一個數小1”,ri+i2均為4。

歸納為一般情況:

a[1]嘗試范圍n——r,a[2]嘗試范圍n-1——r-1,……,a[r]嘗試范圍r——1.

由此,搜索過程中的約束條件為ri+a[ri]>=r+1,若ri+a[ri]<r就要回溯到元素a[ri-1]搜索,特別地a[r]=1時,回溯到元素a[r-1]搜索。

2、算法

main(  );
{ int n,r,a[20] ;
  print(“n,r=”);
  input(n,r);
  if (r>n) 
      print(“Input n,r error!”);
  else
     {a[0]=r;
      comb(n,r);}           /調用遞歸過程/
}
comb2(int n,int r,int a[])
{int i,ri;     ri=1;    a[1]=n;
 while(a[1]<>r-1)
  if (ri<>r)              /沒有搜索到底/
    if (ri+a[ri]>=r+1){a[ri+1]=a[ri]-1;   ri=ri+1;}
     else   {ri=ri-1;     a[ri]=a[ri]-1;}    /回溯/
   else
    {for (j=1;j<=r;j++)    print(a[j]);
     print(“換行符”); /輸出組合數/
    if (a[r]=1) {ri=ri-1;a[ri]=a[ri]-1;}    /回溯/
    else  a[ri]=a[ri]-1;      /搜索到下一個數/
    }
}

  

 

應用2——排列及排列樹的回溯搜索

  【例5】輸出自然數1到n所有不重復的排列,即n的全排列

1、算法設計

    n的全排列是一組n元一維向量:(x1,x2,x3,……,xn),搜索空間是:1≤xi≤n     i=1,2,3,……n,約束條件很簡單,xi互不相同。

    這里我們采用第三章“3.2.3  利用數組記錄狀態信息”的技巧,設置n個元素的數組d,其中的n個元素用來記錄數據1——n的使用情況,已使用置1,未使用置0。

2、算法

 main( )
    {  int  j,n,
       print(‘Input n=’ ‘); 
       input(n);
       for(j=1;j<=n;j++)
           d[j]=0;
       try(1);
    }
try(int k)
{ int  j;
  for(j=1;j<=n;j++)
     {if (d[j]=0)  {a[k]=j;  d[j]=1;}
      else       continue;
      if (k<n)
          try(k+1);
      else
         {p=p+1; output(k);}
      d[a[k]]=0;
   }
}
     output( )
    {   int j;
        print(p,”:”)
        for(j=1;j<=n;j++)
            print(a[j]);
        print(“換行符”);
    }

3、算法說明:變量p記錄排列的組數,k為當前處理的第k個元素

4、算法分析

     全排列問題的復雜度為O(nn),不是一個好的算法。因此不可能用它的結果去搜索排列樹。

 

 

【例6】全排列算法另一解法——搜索排列樹的算法框架

    1、算法設計

       根據全排列的概念,定義數組初始值為(1,2,3,4,……,n),這是全排列中的一種,然后通過數據間的交換,則可產生所有的不同排列。

2、算法

  int a[100],n,s=0;
    main( )
    {  int i,;
       input(n);
       for(i=1;i<=n;i++)
         a[i]=i;
       try(1);
       print(“換行符”,“s=”,s);
     }
try(int t)
{  int j;
   if (t>n)
      {output( );}
   else
     for(j=t;j<=n;j++)
       {swap(t,j);
        try(t+1);
        swap(t,j); /回溯時,恢復原來的排列/
        }
 }
output( )
{ int j;
  printf("\n");
  for( j=1;j<=n;j++)   printf(" mod d",a[j]);
  s=s+1;
}
swap(int t1,int t2)
{  int t;
   t=a[t1];
   a[t1]= a[t2];
   a[t2]=t;
}

3、算法說明

     1)有的讀者可能會想try( )函數中,不應該出現自身之間的交換,for循環是否應該改為for(j=t+1;j<=n;j++)?回答是否定的。當n=3時,算法的輸出是:123,132,213,231,321,312。123的輸出說明第一次到達葉結點是不經過數據交換的,而132的排列也是1不進行交換的結果。

        2)for循環體中的第二個swap( )調用,是用來恢復原順序的。為什么要有如此操作呢?還是通過實例進行說明,排列“213”是由“123”進行1,2交換等到的所以在回溯時要將“132” 恢復為“123”。

4、算法分析

    全排列算法的復雜度為O(n!), 其結果可以為搜索排列樹所用。

 

 

【例7】按排列樹回溯搜索解決八皇后問題

  1、算法設計

      利用例6“枚舉”所有1-n的排列,從中選出滿足約束條件的解來。這時的約束條件只有不在同一對角線,而不需要不同列的約束了。和例1的算法3一樣,我們用數組c,d記錄每個對角線的占用情況。   

2、算法

 int a[100],n,s=0,c[20],d[20];
   main( )
   {  int i; 
      input(n);
      for(i=1;i<=n;i++)
       a[i]=i;
      for (i=1;i<=n;i++)
         { c[i]=0; 
           c[n+i]=0;
           d[i]=0; 
           d[n+i]=0;}
      try(1);
      print("s=",s);
   }
try(int t)
{   int j;
    if (t>n)
       {output( );}
    else
        for(j=t;j<=n;j++)
           { swap(t,j);
             if (c[t+a[t]]=0 and d[t-a[t]+n]=0)
                { c[t+a[t]]=1;   d[t-a[t]+n]=1;
                  try(t+1);
                  c[t+a[t]]=0;   d[t-a[t]+n]=0;}
              swap(t,j);
            }
}
output( )
{ int j;
  print("換行符");
  for( j=1;j<=n;j++)   print(a[j]);
  s=s+1;
}
swap(int t1,int t2)
{  int t;
   t=a[t1];
   a[t1]= a[t2];
   a[t2]=t;
}

  

 

應用3——最優化問題的回溯搜索

【例8】一個有趣的高精度數據

      構造一個盡可能大的數,使其從高到低前一位能被1整除,前2位能被2整除,……,前n位能被n整除。

1、數學模型

    記高精度數據為a1 a2……an,題目很明確有兩個要求:

    1)a1整除1且

    (a1*10+a2)整除2且……

    (a1*10n-1+a210n-2+……+an) 整除n;

    2)求最大的這樣的數。

2、算法設計

    此數只能用從高位到低位逐位嘗試失敗回溯的算法策略求解,生成的高精度數據用數組的從高位到低位存儲,1號元素開始存儲最高位。此數的大小無法估計不妨為數組開辟100個空間。

    算法中數組A為當前求解的高精度數據的暫存處,數組B為當前最大的滿足條件的數。

    算法的首位A[1]從1開始枚舉。以后各位從0開始枚舉。所以求解出的滿足條件的數據之間只需要比較位數就能確定大小。n 為當前滿足條件的最大數據的位數,當i>=n就認為找到了更大的解,i>n不必解釋位數多數據一定大;i=n時,由於嘗試是由小到大進行的,位數相等時后來滿足條件的菀歡ū惹懊嫻拇蟆

3、算法

 main(  )
{ int  A[101],B[101];  int  i,j,k,n,r;
  A[1]=1;
  for(i=2;i<=100;i++)     /置初值:首位為1  其余為0/
      A[i]=0;
  n=1;   i=1; 
  while(A[1]<=9)
    {if (i>=n)    /發現有更大的滿足條件的高精度數據/
      {n=i;                  /轉存到數組B中/
       for (k=1;k<=n;k++)  
               B[k]=A[k];
        }
    i=i+1;r=0;
    for(j=1;j<=i;j++)     /檢查第i位是否滿足條件/
       {r=r*10+A[j];    r=r mod i;}
    if(r<>0)               /若不滿足條件/ 
      {A[i]=A[i]+i-r ;      /第i位可能的解/
       while (A[i]>9 and i>1)   /搜索完第i位的解,回
                                 溯到前一位/
          {A[i]=0;  i=i-1;  A[i]=A[i]+i;}     
       }
    }
 } 

4、算法說明

        1)從A[1]=1開始,每增加一位A[i](初值為0)先計算r=(A[1]*10i-1+A[2]*10i-2+……+A[i]),再測試r=r mod i是否為0。

    2)r=0表示增加第i位后,滿足條件,與原有滿足條件的數(存在數組B中)比較,若前者大,則更新后者(數組B),繼續增加下一位。

 

    3)r≠0表示增加i位后不滿足整除條件,接下來算法中並不是繼續嘗試A[i]= A[i]+1,而是繼續嘗試A[i]= A[i]+i-r,因為若A[i]= A[i]+i-r<=9時,(A[1]*10i-1+A[2]*10i-2+……+A[i]杛+i) mod i肯定為0,這樣可減少嘗試次數。如:17除5余2,15-2+5肯定能被5整除。

   

    4)同理,當A[i] -r +i>9時,要進位也不能算滿足條件。這時,只能將此位恢復初值0且回退到前一位(i=i-1)嘗試A[i]= A[i] +i……。這正是最后一個while循環所做的工作。

    5)當回溯到i=1時,A[1]加1開始嘗試首位為2的情況,最后直到將A[1]=9的情況嘗試完畢,算法結束。

 

 

【例9】流水作業車間調度

     n個作業要在由2台機器M1和M2組成的流水線上完成加工。每個作業加工的順序都是先在M1上加工,然后在M2上加工。M1和M2加工作業i所需的時間分別為ai和bi。流水作業調度問題要求確定這n個作業的最優加工順序,使得從第一個作業在機器M1上開始加工,到最后一個作業在機器M2上加工完成所需的時間最少。作業在機器M1、M2的加工順序相同。

1、算法設計

    1)問題的解空間是一棵排列樹,簡單的解決方法就是在搜索排列樹的同時,不斷更新最優解,最后找到問題的解。算法框架和例6完全相同,用數組x(初值為1,2,3,……,n)模擬不同的排列,在不同排列下計算各種排列下的加工耗時情況。

    2)機器M1進行順序加工,其加工f1時間是固定的,f1[i]= f1[i-1]+a[x[i]]。機器M2則有空閑(圖5-19(1))或積壓(圖5-19(2))的情況,總加工時間f2,當機器M2空閑時,f2[i]=f1+ b[x[i]];當機器M2有積壓情況出現時,f2[i]= f2[i-1]+ b[x[i]]。總加工時間就是f2[n]。

  3)一個最優調度應使機器M1沒有空閑時間,且機器M2的空閑時間最少。在一般情況下,當作業按在機器M1上由小到大排列后,機器M2的空閑時間較少,當然最少情況一定還與M2上的加工時間有關,所以還需要對解空間進行搜索。排序后可以盡快地找到接近最優的解,再加入下一步限界操作就能加速搜索速度。

 4)經過以上排序后,在自然數列的排列下,就是一個接近最優的解。因此,在以后的搜索過程中,一當某一排列的前面幾步的加工時間已經大於當前的最小值,就無需進行進一步的搜索計算,從而可以提高算法效率。

 

2、數據結構設計

    1)用二維數組job[100][2]存儲作業在M1、M2上的加工時間。

    2)由於f1在計算中,只需要當前值,所以用變量存儲即可;而f2在計算中,還依賴前一個作業的數據,所以有必要用數組存儲。

    3)考慮到回溯過程的需要,用變量f存儲當前加工所需要的全部時間。

 

3、算法

int job[100][2],x[100],n,f1=0,f=0,f2[100]=0;
main( )
{  int j;
   input(n);
   for(i=1;i<=2;i++)
       for(j=1;j<=n;j++)
           input(job[j][i]);
   try( );
}
     try(int i)
     {  int j;
        if (i=n+1)
           {for(j=1;j<=n;j++)
                bestx[j]=x[j];
            bestf=f;
            }
         else
  for(j=1;j<=n;j++)
   { f1= f1+ job[x[j]][1];
     if (f2[i-1]>f1)  f2[i]= f2[i-1]+job[x[j]][2];
     else   f2[i]= f1+job[x[j]][2];
         f=f+f2[i];
         if (f<bestf)
            { swap(x[i],x[j]);  try(i+1);
              swap(x[i],x[j]);}
         f1= f1-job[x[j]][1];
         f=f-f2[i];
     }
} 

    解空間為排列樹的最優化類問題,都可以依此算法解決。而對於解空間為子集樹的最優化類問題,類似本節例題1、2、3枚舉元素的選取或不選取兩種情況進行搜索就能找出最優解,同時也可以加入相應的限界策略。

 


免責聲明!

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



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