演算法導論 之 動態規劃
- 作者:鄒祁峰
- 郵箱:[email protected]
-
日期:2014.03.07 18:00
- 轉載請註明來自"祁峰"的CSDN部落格
1 問題描述
現有兩條裝配線,Sij表示第i條上完成第j道工序的裝配站。汽車完成組裝需要依次完成1~n工序。請找出完成裝配並離開裝配線的最快路線。
符號說明:
①、ei:汽車進入裝配線i的時間,i=1,2
②、xi:汽車離開裝配線i的時間
③、aij:在裝配站Sij完成裝配需要的時間
④、tij:在裝配站Sij完成後離開第i條裝配線,進入另一條裝配線需要的轉移時間
注意,如果完成工序後,下一個工序還在同一條裝配線上,則不需要轉移時間。
圖1 裝配線排程
2 問題分析
我們用Fij表示在第i條裝配線上完成第j道工序的最快時間,用F表示完成汽車裝配並離開裝配線的時間。如果知道F1n和F2n,則有圖2 最小裝配時間 而要求出F1n和F2n,又需要知道F1n-1和F2n-1,從而有:
圖3 展開公式 依次類推,可得如下遞迴公式:
圖4 遞迴公式
3 問題求解
3.1 遞迴求解
遞迴求解的過程,可是使用如下的樹形結構來表示:
圖5 樹形表示
每個節點裡的符號,表示相應的問題,其孩子節點表示在求解該問題時需要求解的子問題。由此我們很容易得出,對於問題Fij,採用遞迴演算法計算時,需要求解的次數為2^(n-j)。3.2 動態規劃
如果反過來,採用自下而上的方式來求解,把求解結果儲存起來,後續的計算都依賴之前計算儲存的結果,則可有效的減少重複計算,從而極大的提高求解效率。
我們再回頭看看問題描述,並自下而上的方式來分析其處理過程:
第1行1列的最短耗時:ms[1][1] = e[1] + a[1][1]
第2行1列的最短耗時:ms[2][1] = e[2] + a[2][1]
第1行2列的最短耗時:ms[1][2] = min{ms[1][1], ms[2][1]+t[2][1]} + a[1][2],並記錄min{ms[1][1], ms[2][1]+t[2][1]}中更小值的行號
第2行2列的最短耗時:ms[2][2] = min{ms[1][1]+t[1][1], ms[2][1]} + a[2][1],並記錄min{ms[1][1]+t[1][1], ms[2][1]}中更小值的行號
第1行3列的最短耗時:ms[1][3] = min{ms[1][2], ms[2][2]+t[2][2]} + a[1][3],並記錄min{ms[1][2], ms[2][2]+t[2][2]}中更小值的行號
第2行3列的最短耗時:ms[2][3] = min{ms[1][2]+t[1][2], ms[2][2]} + a[2][3],並記錄min{ms[1][2]+t[1][2], ms[2][2]}中更小值的行號
....
第1行n列的最短耗時:ms[1][n] = min{ms[1][n-1], ms[2][n-1]+t[2][n-1]} + a[1][n],並記錄min{ms[1][n-1], ms[2][n-1]+t[2][n-1]}中更小值的行號
第2行n列的最短耗時:ms[2][n] = min{ms[1][n-1]+t[1][n-1], ms[2][n-1]} + a[2][n],並記錄min{ms[1][n-1]+t[1][n-1], ms[2][n-1]}中更小值的行號
第1行n列到終點最短耗時:ms[1][n+1] = ms[1][n] + x[1]
第2行n列到終點最短耗時:ms[2][n+1] = ms[2][n] + x[2]
此時如果想知道到達終點的最佳路徑,只需比較ms[1][n+1]和ms[2][n+1]中哪個更小,並依據記錄的且一列行號反推出最短路徑。因整個計算過程中都將{最短耗時,前一列行號}對應的儲存了起來,後續輸入任何一個座標,便能依據{最短耗時,前一列行號}找到前一列行號,根據前一行行號又能獲取到對應的儲存結果{最短耗時,前一列行號},通過儲存結果又可以獲取到更前一列行號,依次類推,便能反推出從起點到達查詢點的最佳路徑,而此過程不用再做任何的耗時計算。
當裝配線條數為rows(rows > 2), 裝配站個數為cols(cols > 1)時,其處理過程類似:
第r行c列的最短耗時:ms[r][c] = min{ms{1][c-1] + t[1][c-1], ms{2][c-1] + t[2][c-1], ..., ms{r][c-1] + t[r][c-1], ..., ms[rows][cols] + t[rows][c-1]} + a[r][c].其中的同行的轉移時間t[r][c-1]=0;
圖6 裝配示意圖
4 程式碼實現
4.1 結構定義
①、巨集定義值都可根據實際情況進行配置#define DYNC_LINE_NUM (2) /* 裝配線條數 */
#define DYNC_NODE_NUM (6) /* 各裝配線裝的裝配節點(裝配站)數 */
#define DYNC_IN_TM_MOD (9) /* 進組裝線耗時取模 */
#define DYNC_OUT_TM_MOD (9) /* 出組裝線耗時取模 */
#define DYNC_NODE_TM_MOD (17) /* 各結點裝線耗時取模 */
#define DYNC_TRANS_TM_MOD (9) /* 切換組裝線耗時取模 */
程式碼1 巨集定義
②、結構定義
/* 耗時型別 */
typedef enum
{
DYNC_IN_TM, /* 進組裝線耗時 */
DYNC_OUT_TM, /* 出組裝線耗時 */
DYNC_NODE_TM, /* 各結點裝線耗時 */
DYNC_TRANS_TM, /* 切換組裝線耗時 */
DYNC_TM_TYPE_TOTAL /* 耗時型別 */
}DYNC_TM_TYPE_e;
/* 最優路由資訊 */
typedef struct
{
int spend; /* 當前最短花費時間 */
int lrow; /* 上一節點所在行號 */
}dync_optmz_t;
/* 耗時資訊表 */
typedef struct
{
int *in; /* 進裝配線耗時資訊 */
int *out; /* 出裝配線耗時資訊 */
int *node; /* 各裝配線結點耗時資訊 */
int *trans; /* 切換裝配線耗時資訊 */
}dync_time_t;
/* 動態規劃結構體 */
typedef struct
{
int rows; /* 裝配線條數 */
int cols; /* 單條裝配線的結點個數 */
dync_time_t time; /* 裝配線各時間消耗資訊 */
dync_optmz_t *optmz; /* 最佳路由結果集 */
}dynamic_t;
程式碼2 結構定義
4.2 程式碼實現
/******************************************************************************
**函式名稱: dync_init
**功 能: 動態規劃初始化
**輸入引數:
** rows: 裝配線條數
** cols: 裝配站個數
**輸出引數:
** _dync: 動態規劃物件
**返 回: VOID
**實現描述:
** 1. 為物件分配空間
** 2. 設定各操作耗時情況表
**注意事項:
**作 者: # Qifeng.zou # 2014.03.06 #
******************************************************************************/
int dync_init(dynamic_t **_dync, int rows, int cols)
{
dynamic_t *dync = NULL;
do
{
/* 1. 為物件分配空間 */
dync = (dynamic_t *)calloc(1, sizeof(dynamic_t));
if(NULL == dync)
{
break;
}
dync->time.in = (int *)calloc(rows, sizeof(int)); /* 入裝配線耗時: 個數與裝配線條數一致 */
if(NULL == dync->time.in)
{
break;
}
dync->time.out = (int *)calloc(rows, sizeof(int)); /* 出裝配線耗時:個數與裝配線條數一致 */
if(NULL == dync->time.out)
{
break;
}
dync->time.node = (int *)calloc(rows*cols, sizeof(int)); /* 裝配站耗時:裝配線條數*裝配站個數 */
if(NULL == dync->time.node)
{
break;
}
dync->time.trans = (int *)calloc(rows*cols*rows, sizeof(int)); /* 轉移耗時:裝配線條數*裝配站個數*裝配線條數 */
if(NULL == dync->time.trans)
{
break;
}
/* 長度+1是為了儲存最後一個結點至出組裝線的總時間 */
dync->optmz = (dync_optmz_t *)calloc(rows*(cols + 1), sizeof(dync_optmz_t));
if(NULL == dync->optmz)
{
break;
}
/* 2. 設定各操作耗時情況表 */
dync->rows = rows;
dync->cols = cols;
set_time(dync->time.in, rows, cols, DYNC_IN_TM, DYNC_IN_TM_MOD);
set_time(dync->time.out, rows, cols, DYNC_OUT_TM, DYNC_OUT_TM_MOD);
set_time(dync->time.node, rows, cols, DYNC_NODE_TM, DYNC_NODE_TM_MOD);
set_time(dync->time.trans, rows, cols, DYNC_TRANS_TM, DYNC_TRANS_TM_MOD);
*_dync = dync;
return 0;
}while(0);
/* 異常處理: 釋放所有記憶體 */
if(NULL != dync)
{
if(NULL != dync->time.in) { free(dync->time.in); }
if(NULL != dync->time.out) { free(dync->time.out); }
if(NULL != dync->time.node) { free(dync->time.node); }
if(NULL != dync->time.trans) { free(dync->time.trans); }
if(NULL != dync->optmz) { free(dync->optmz); }
free(dync), dync=NULL;
}
return -1;
}
程式碼3 動態規劃初始化
為了方便快速設定耗時資訊,在此使用函式自動處理:
/******************************************************************************
**函式名稱: set_time
**功 能: 設定耗時資訊
**輸入引數:
** rows: 裝配線條數
** cols: 裝配站個數
** type: 耗時型別
** mod : 耗時取模
**輸出引數:
** node: 各裝配站耗時
**返 回: VOID
**實現描述:
**注意事項:
**作 者: # Qifeng.zou # 2014.03.06 #
******************************************************************************/
void set_time(int *tm, int rows, int cols, int type, int mod)
{
int row = 0, row2 = 0, col = 0, base = 0;
switch(type)
{
case DYNC_IN_TM:
case DYNC_OUT_TM:
{
printf("\nIN/OUT TIME:\n");
for(row=0; row<rows; row++)
{
tm[row] = 0;
while(0 == tm[row])
{
tm[row] = random()%mod;
}
printf("[%03d] ", tm[row]);
}
printf("\n\n");
break;
}
case DYNC_NODE_TM:
{
printf("\nNODE TIME:\n");
for(row=0; row<rows; row++)
{
printf("[ROW:%02d]\n", row);
for(col=0; col<cols; col++)
{
while(0 == tm[row*cols + col])
{
tm[row*cols + col] = random()%mod;
}
printf("[%03d] ", tm[row*cols + col]);
}
printf("\n");
}
printf("\n");
break;
}
case DYNC_TRANS_TM:
{
printf("\nTRANS TIME:\n");
for(row=0; row<rows; row++)
{
printf("[ROW:%02d]\n", row);
for(col=0; col<cols; col++)
{
base = row*cols*rows + col*rows;
printf("COL:[%03d]-", col);
for(row2=0; row2<rows; row2++)
{
tm[base + row2] = 0;
while((0 == tm[base + row2]) && (row != row2))
{
tm[base + row2] = random()%mod;
}
printf("[%03d] ", tm[base + row2]);
}
printf("\n");
}
printf("\n");
}
break;
}
}
}
程式碼4 耗時設定
/******************************************************************************
**函式名稱: dync_proc
**功 能: 動態規劃: 計算最短路徑並存儲
**輸入引數:
** dync: 動態規劃物件
**輸出引數:
**返 回: VOID
**實現描述:
**注意事項:
**作 者: # Qifeng.zou # 2014.03.06 #
******************************************************************************/
void dync_proc(dynamic_t *dync)
{
int *spend = NULL;
dync_optmz_t *best = dync->optmz, *curr = NULL, **line = NULL, *prev = NULL;
int curr_row = 0, row = 0, col = 0, min_spend = 0, min_row = 0;
spend = (int *)calloc(dync->rows, sizeof(int));
if(NULL == spend)
{
return;
}
line = (dync_optmz_t **)calloc(dync->rows, sizeof(dync_optmz_t*));
if(NULL == line)
{
free(spend);
return;
}
/* 逐列遍歷 */
for(col=0; col<dync->cols; col++)
{
memset(spend, 0, dync->rows * sizeof(int));
if(col > 0)
{
/* 指向各行前一列 */
for(row=0; row<dync->rows; row++)
{
line[row] = &best[row*(dync->cols+1) + col - 1];
}
}
/* 逐行當前列遍歷 */
for(curr_row=0; curr_row<dync->rows; curr_row++)
{
curr = &best[curr_row*dync->cols + col];
if(0 == col)
{
curr->spend = (dync->time.node[curr_row*dync->cols + col] + dync->time.in[curr_row]);
curr->lrow = -1;
continue;
}
/* 統計逐行前列至當前站點耗時 */
for(row=0; row<dync->rows; row++)
{
spend[row] = (line[row]->spend
+ dync->time.node[curr_row * dync->cols + col]
+ dync->time.trans[row * dync->cols * dync->rows + (col - 1) * dync->rows + curr_row]);
}
/* 查詢最小耗時路徑 */
min_spend = spend[0];
min_row = 0;
for(row=1; row<dync->rows; row++)
{
if(min_spend > spend[row])
{
min_spend = spend[row];
min_row= row;
}
}
curr->spend = min_spend;
curr->lrow = min_row;
}
}
/* 計算裝配線最後一節點至出站耗時 */
for(row=0; row<dync->rows; row++)
{
prev = &best[row*(dync->cols+1) + dync->cols - 1];
curr = &best[row*(dync->cols+1) + dync->cols];
curr->spend = (prev->spend + dync->time.out[row]);
curr->lrow = row;
}
/* 記憶體釋放 */
free(spend), spend=NULL;
free(line), line=NULL;
return;
}
程式碼5 動態規劃計算
/******************************************************************************
**函式名稱: dync_show
**功 能: 搜尋並顯示最佳路徑
**輸入引數:
** dync: 物件
** row: 行號
** col: 列號
**輸出引數:
**返 回: VOID
**實現描述:
**注意事項:
**作 者: # Qifeng.zou # 2014.03.06 #
******************************************************************************/
void dync_show(const dynamic_t *dync, int row, int col)
{
const dync_optmz_t *optmz = NULL, *prev = NULL;
if(row >= dync->rows)
{
row = dync->rows - 1;
}
if(col > dync->cols)
{
col = dync->cols;
}
fprintf(stdout, "\n");
optmz = &dync->optmz[row*(dync->cols+1) + col];
fprintf(stdout, "[%d][%d] Total: %d\n", row, col, optmz->spend);
while(col >= 0)
{
if(-1 == optmz->lrow)
{
fprintf(stdout, "[%02d][%02d]: CURR:%03d [PREV:L%02d-IN:%03d] | TOTAL:%03d\n",
row, col,
dync->time.node[row * dync->cols + col],
optmz->lrow,
dync->time.in[row],
optmz->spend);
break;
}
else if(dync->cols == col)
{
prev = &dync->optmz[optmz->lrow*(dync->cols+1) + (col-1)];
fprintf(stdout, "[%02d][%02d]: OUT:%03d [PREV:L%02d-SPEND:%03d] | TOTAL:%03d\n",
row, col,
dync->time.out[row],
optmz->lrow, prev->spend,
optmz->spend);
}
else
{
prev = &dync->optmz[optmz->lrow*(dync->cols+1) + (col-1)];
fprintf(stdout, "[%02d][%02d]: CURR:%03d [PREV:L%02d-SPEND:%03d-TRANS:%03d] | TOTAL:%03d\n",
row, col,
dync->time.node[row * dync->cols + col],
optmz->lrow, prev->spend,
dync->time.trans[optmz->lrow * dync->cols * dync->rows
+ (col - 1) * dync->rows + row],
optmz->spend);
}
col--;
row = optmz->lrow;
optmz = &dync->optmz[optmz->lrow*(dync->cols+1) + col];
}
return;
}
程式碼6 顯示路徑
函式呼叫示例:
int main(int argc, const char *argv[])
{
dynamic_t *dync = NULL;
int ret = 0, row = 0, col = 0;
if(3 != argc)
{
fprintf(stderr, "Please input arguments!\ndynamic 2 3\n");
return -1;
}
row = atoi(argv[1]);
col = atoi(argv[2]);
ret = dync_init(&dync, DYNC_LINE_NUM, DYNC_NODE_NUM);
if(0 != ret)
{
fprintf(stderr, "Initialize dync_proc failed!\n");
return -1;
}
dync_proc(dync);
dync_show(dync, row, col);
return 0;
}
程式碼7 呼叫示例
4.3 結果展示
假設有5條裝配線,每條裝配線上有9個裝配站,則有如下資料:①、入裝配線的耗時分別為:
圖7 入裝配線耗時
②、出裝配線的耗時分別為:
圖8 出裝配線耗時
③、各裝配線的裝配站耗時分別為:
圖9 各裝配站耗時
④、各裝配站切換裝配線耗時分別為:
圖10 切換裝配線耗時
⑤、查詢最佳路徑
輸入(4,8)的最佳路徑是:起點->L01->L04->L01->L03->L03->L03->L03->L02->(4, 8)
圖11 最佳路徑
圖12 路徑繪製