?? data structor.txt
字號:
為使程序在檢查皇后配置的合理性方面簡易方便,引入以下三個工作數組:
(1) 數組a[ ],a[k]表示第k行上還沒有皇后;
(2) 數組b[ ],b[k]表示第k列右高左低斜線上沒有皇后;
(3) 數組 c[ ],c[k]表示第k列左高右低斜線上沒有皇后;
棋盤中同一右高左低斜線上的方格,他們的行號與列號之和相同;同一左高右低斜線上的方格,他們的行號與列號之差均相同。
初 始時,所有行和斜線上均沒有皇后,從第1列的第1行配置第一個皇后開始,在第m列col[m]行放置了一個合理的皇后后,準備考察第m+1列時,在數組 a[ ]、b[ ]和c[ ]中為第m列,col[m]行的位置設定有皇后標志;當從第m列回溯到第m-1列,并準備調整第m-1列的皇后配置時,清除在數組a[ ]、b[ ]和c[ ]中設置的關于第m-1列,col[m-1]行有皇后的標志。一個皇后在m列,col[m]行方格內配置是合理的,由數組a[ ]、b[ ]和c[ ]對應位置的值都為1來確定。細節見以下程序:
【程序】
# include
# include
# 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(“列\t行”);
for (j=1;j<=n;j++)
printf(“%3d\t%d\n”,j,col[j]);
printf(“Enter a character (Q/q for exit)!\n”);
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);
}
試探法找解算法也常常被編寫成遞歸函數,下面兩程序中的函數queen_all()和函數queen_one()能分別用來解皇后問題的全部解和一個解。
【程序】
# include
# include
# 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(“列\t行”);
for (j=1;j<=n;j++)
printf(“%3d\t%d\n”,j,col[j]);
printf(“Enter a character (Q/q for exit)!\n”);
scanf(“%c”,&awn);
if (awn==’Q’||awn==’q’) exit(0);
}
queen_all(k+1,n);
a[i]=b[k+i]=c[n+k-i];
}
}
采 用遞歸方法找一個解與找全部解稍有不同,在找一個解的算法中,遞歸算法要對當前候選解最終是否能成為解要有回答。當它成為最終解時,遞歸函數就不再遞歸試 探,立即返回;若不能成為解,就得繼續試探。設函數queen_one()返回1表示找到解,返回0表示當前候選解不能成為解。細節見以下函數。
【程序】
# 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
{ 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;
}
六、貪婪法
貪婪法是一種不追求最優解,只希望得到較為滿意解的方法。貪婪法一般可以快速得到滿意的解,因為它省去了為找最優解要窮盡所有可能而必須耗費的大量時間。貪婪法常以當前情況為基礎作最優選擇,而不考慮各種可能的整體情況,所以貪婪法不要回溯。
例 如平時購物找錢時,為使找回的零錢的硬幣數最少,不考慮找零錢的所有各種發表方案,而是從最大面值的幣種開始,按遞減的順序考慮各幣種,先盡量用大面值的 幣種,當不足大面值幣種的金額時才去考慮下一種較小面值的幣種。這就是在使用貪婪法。這種方法在這里總是最優,是因為銀行對其發行的硬幣種類和硬幣面值的 巧妙安排。如只有面值分別為1、5和11單位的硬幣,而希望找回總額為15單位的硬幣。按貪婪算法,應找1個11單位面值的硬幣和4個1單位面值的硬幣, 共找回5個硬幣。但最優的解應是3個5單位面值的硬幣。
【問題】 裝箱問題
問題描述:裝箱問題可簡述如下:設有編號為0、 1、…、n-1的n種物品,體積分別為v0、v1、…、vn-1。將這n種物品裝到容量都為V的若干箱子里。約定這n種物品的體積均不超過V,即對于 0≤i<n,有0<vi≤V。不同的裝箱方案所需要的箱子數目可能不同。裝箱問題要求使裝盡這n種物品的箱子數要少。
若考察將n種物品的集合分劃 成n個或小于n個物品的所有子集,最優解就可以找到。但所有可能劃分的總數太大。對適當大的n,找出所有可能的劃分要花費的時間是無法承受的。為此,對裝 箱問題采用非常簡單的近似算法,即貪婪法。該算法依次將物品放到它第一個能放進去的箱子中,該算法雖不能保證找到最優解,但還是能找到非常好的解。不失一 般性,設n件物品的體積是按從大到小排好序的,即有v0≥v1≥…≥vn-1。如不滿足上述要求,只要先對這n件物品按它們的體積從大到小排序,然后按排 序結果對物品重新編號即可。裝箱算法簡單描述如下:
{ 輸入箱子的容積;
輸入物品種數n;
按體積從大到小順序,輸入各物品的體積;
預置已用箱子鏈為空;
預置已用箱子計數器box_count為0;
for (i=0;i
{ 從已用的第一只箱子開始順序尋找能放入物品i 的箱子j;
if (已用箱子都不能再放物品i)
{ 另用一個箱子,并將物品i放入該箱子;
box_count++;
}
else
將物品i放入箱子j;
}
}
上 述算法能求出需要的箱子數box_count,并能求出各箱子所裝物品。下面的例子說明該算法不一定能找到最優解,設有6種物品,它們的體積分別為: 60、45、35、20、20和20單位體積,箱子的容積為100個單位體積。按上述算法計算,需三只箱子,各箱子所裝物品分別為:第一只箱子裝物品1、 3;第二只箱子裝物品2、4、5;第三只箱子裝物品6。而最優解為兩只箱子,分別裝物品1、4、5和2、3、6。
若每只箱子所裝物品用鏈表來表示,鏈表首結點指針存于一個結構中,結構記錄尚剩余的空間量和該箱子所裝物品鏈表的首指針。另將全部箱子的信息也構成鏈表。以下是按以上算法編寫的程序。
【程序】
# include
# include
typedef struct ele
{ int vno;
struct ele *link;
} ELE;
typedef struct hnode
{ int remainder;
ELE *head;
Struct hnode *next;
} HNODE;
void main()
{ int n, i, box_count, box_volume, *a;
HNODE *box_h, *box_t, *j;
ELE *p, *q;
Printf(“輸入箱子容積\n”);
Scanf(“%d”,&box_volume);
Printf(“輸入物品種數\n”);
Scanf(“%d”,&n);
A=(int *)malloc(sizeof(int)*n);
Printf(“請按體積從大到小順序輸入各物品的體積:”);
For (i=0;i
Box_h=box_t=NULL;
Box_count=0;
For (i=0;i
{ p=(ELE *)malloc(sizeof(ELE));
p->vno=i;
for (j=box_h;j!=NULL;j=j->next)
if (j->remainder>=a) break;
if (j==NULL)
{ j=(HNODE *)malloc(sizeof(HNODE));
j->remainder=box_volume-a;
j->head=NULL;
if (box_h==NULL) box_h=box_t=j;
else box_t=boix_t->next=j;
j->next=NULL;
box_count++;
}
else j->remainder-=a;
for (q=j->next;q!=NULL&&q->link!=NULL;q=q->link);
if (q==NULL)
{ p->link=j->head;
j->head=p;
}
else
{ p->link=NULL;
q->link=p;
}
}
printf(“共使用了%d只箱子”,box_count);
printf(“各箱子裝物品情況如下:”);
for (j=box_h,i=1;j!=NULL;j=j->next,i++)
{ printf(“第%2d只箱子,還剩余容積%4d,所裝物品有;\n”,I,j->remainder);
for (p=j->head;p!=NULL;p=p->link)
printf(“%4d”,p->vno+1);
printf(“\n”);
}
}
【問題】 馬的遍歷
問題描述:在8×8方格的棋盤上,從任意指定的方格出發,為馬尋找一條走遍棋盤每一格并且只經過一次的一條路徑。
馬 在某個方格,可以在一步內到達的不同位置最多有8個,如圖所示。如用二維數組board[ ][ ]表示棋盤,其元素記錄馬經過該位置時的步驟號。另對馬的8種可能走法(稱為著法)設定一個順序,如當前位置在棋盤的(i,j)方格,下一個可能的位置依 次為(i+2,j+1)、(i+1,j+2)、(i-1,j+2)、(i-2,j+1)、(i-2,j-1)、(i-1,j-2)、(i+1,j-2)、 (i+2,j-1),實際可以走的位置盡限于還未走過的和不越出邊界的那些位置。為便于程序的同意處理,可以引入兩個數組,分別存儲各種可能走法對當前位 置的縱橫增量。
4 3
5 2
馬
6 1
7 0
對于本題,一般可以采用回溯法,這里采用 Warnsdoff策略求解,這也是一種貪婪法,其選擇下一出口的貪婪標準是在那些允許走的位置中,選擇出口最少的那個位置。如馬的當前位置(i,j)只 有三個出口,他們是位置(i+2,j+1)、(i-2,j+1)和(i-1,j-2),如分別走到這些位置,這三個位置又分別會有不同的出口,假定這三個 位置的出口個數分別為4、2、3,則程序就選擇讓馬走向(i-2,j+1)位置。
由于程序采用的是一種貪婪法,整個找解過程是一直向前,沒有回 溯,所以能非常快地找到解。但是,對于某些開始位置,實際上有解,而該算法不能找到解。對于找不到解的情況,程序只要改變8種可能出口的選擇順序,就能找 到解。改變出口選擇順序,就是改變有相同出口時的選擇標準。以下程序考慮到這種情況,引入變量start,用于控制8種可能著法的選擇順序。開始時為0, 當不能找到解時,就讓start增1,重新找解。細節以下程序。
【程序】
# include
int delta_i[ ]={2,1,-1,-2,-2,-1,1,2};
int delta_j[ ]={1,2,2,1,-1,-2,-2,-1};
int board[8][8];
int exitn(int i,int j,int s,int a[ ])
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -