分支限界法解決裝載問題之FIFO隊列方式的總結

來源:互聯網
上載者:User

1.先說一下順序隊列來建立資料結構

 

/************************************************************************順序隊列(迴圈隊列)實現FIFO分支限界法--裝載問題把資料結構換了一下,僅僅把之前的鏈隊列換成了迴圈隊列。其餘的函數基本沒有變,還有main()函數根本就沒變,只是EnQueue(), Add(), DeQueue()這些涉及到E和bestE的函數中的某些函數參數由QUEUE *類型變成了int類型。函數調用中的參數根本就沒變,也不用變。在原來的鏈隊列程式中,資料結構定義按照節點來的,每個節點中有*parent, weigth, LChild, *next。然後一個隊列是很多個節點,每次入隊或者出隊通過加入含有這些類型元素的節點或者刪除,E和bestE是獨立的,不依賴於鏈隊列LQ的具有以上所說類型的節點,每次用來存放某些節點的地址,只要找到了某個節點地址,就可以調用這個節點裡面的*parent, weigth, LChild等元素,這是鏈隊列的來實現的思想。這裡我採用迴圈隊列來實現,順序隊列也好,迴圈隊列也好,都是順序的儲存方式,即將要儲存的資料存到數組裡面(數組是順序的)。所以思想就跟之前的鏈隊列實現的思想有所不同,原來每個節點有一個parent, weight,LChild之類的,但是如今只有一個節點SQ。元素的尋找是通過下標值來尋找也就相當於類比鏈隊列裡面的節點地址,這個下標值可以類比成地址。so,weight, parent, LChild每個都是一個數組weight[], parent[], LChild[];比如鏈隊列中的E->weight表示的是E所指向的那個節點的weight,在迴圈隊列裡面就要用weight[E]來實現了,在鏈隊列中,E是QUEUE*型的,表示的是隊列節點的那種類型,然而在順序隊列裡面,就定義成了int型,表示的是下標值,從1開始(0表示加入空節點weight值為-1時)。再比如,原來的鏈隊列程式中bestx[j] = bestE->LChild ;bestE = bestE ->parent ;換成了下面的:bestx[j] = Q->LChild[bestE] ;bestE = Q->parent[bestE] ;so,可以仔細體味一下這兩種方式的不同。其實還有一種構建資料結構的方式,也是迴圈隊列或者順序隊列,總是是用數組來儲存元素的。但是那種方式和鏈隊列的思想基本上是一樣,按照開闢節點的方式,唯一不同的是鏈隊列的節點之間不是順序的,而是通過指標連起來的,而此種方式確實開闢一個順序的節點空間Q[100];見下面struct{int weight;int LChild;int parent;}Q[MAXSIZE];int front, int rear;這個和鏈隊列方式基本一樣weight, LChild, parent都是節點內部的元素。而front, rear都是獨立的元素,用來指向節點。仔細體味三種隊列構建方式的異同。*******************************************************************************/#include<stdio.h>#include<stdlib.h>#defineMAXSIZE100typedef struct {int weight[MAXSIZE];int LChild[MAXSIZE];int parent[MAXSIZE];int front, rear;}SEQUEUE;//只有個全域變數:*bestx,bestw。int *bestx ;int bestw = 0 ; // 目前的最優值void InitQueue(SEQUEUE *SQ){SQ->front=SQ->rear=0;for(int i=0;i<MAXSIZE;i++){SQ->parent[i]=-1;}}int Empty(SEQUEUE *SQ){if(SQ->rear==SQ->front)return 1;elsereturn 0;}int Add(SEQUEUE *SQ, int w, int E, int l){if((SQ->rear+1)%MAXSIZE==SQ->front){printf("隊列滿了!\n");return 0;}SQ->rear=(SQ->rear+1)%MAXSIZE;SQ->weight[SQ->rear]=w;SQ->LChild[SQ->rear]=l;SQ->parent[SQ->rear]=E;return 1;}int DeQueue(SEQUEUE *SQ, int *E){if(Empty(SQ)){printf("隊列空了!\n");return 0;}SQ->front=(SQ->front+1)%MAXSIZE;*E=SQ->front;return 1;}void EnQueue(SEQUEUE *SQ, int wt, int i, int n , int E , int *bestE , int ch){if(i==n){if(wt==bestw){*bestE = E;//bestE只有i==n時候,也就是葉子的時候,才用得到bestE,因為為了追溯路徑,靠bestE=bestE->parent;也就是說一旦到了葉子節點,說明搜尋已經結束            bestx[n] = ch;}        return;}Add(SQ, wt , E, ch); // 不是葉子}/*int GetHeadQueue(SEQUEUE *SQ, int *x){if(Empty(SQ)){printf("隊列空了!\n");return 0;}*x=SQ->data[(SQ->front+1)%MAXSIZE];return 1;}*/int MaxLoading(int w[], int c, int n)//求最大裝載函數{int err ; //傳回值int i = 1; // 當前擴充結點的層int cnt=0;int Ew = 0; // 當前擴充結點的權值int r = 0 ; //剩餘集裝箱重量int flag=0;int E=0;int bestE=0;SEQUEUE *Q=(SEQUEUE *)malloc(sizeof(SEQUEUE)); // 活結點隊列for(int j =2; j<=n; j++){          r += w[j];}bestw = 0; // 目前的最優值InitQueue(Q);err=Add(Q, -1, 0, 0);if(!err){return 0 ;}while (true) { int wt = Ew + w[i] ;if (wt <= c)   // 檢查左孩子結點{if(wt>bestw) bestw = wt ;// x[i] = 1EnQueue(Q, Ew + w[i], i , n , E , &bestE , 1);flag=1;/******這部分剪枝的內容也可以放在這個裡面,當然下面就只有if(!flag),而沒有else了***if(Ew+r>=bestw&&i<n) // 對右孩子進行剪枝{EnQueue(Q, Ew, i, n , E, &bestE, 0);}********/}/////if(!flag)//如果flag==0,說明左孩子沒有入隊,那麼一定要把右孩子入隊。也只有因為這樣,邏輯上才講得通。{EnQueue(Q, Ew, i, n , E, &bestE, 0);}else{if(Ew+r>bestw&&i<n) // 對右孩子進行剪枝{EnQueue(Q, Ew, i, n , E, &bestE, 0);}flag=0;//忘了給flag清零讓我多花了好長時間來檢查,還以為是演算法錯了,實際上我想的演算法是對的,只是忘了清零了//也就是說,如果這個flag不清零,那麼每次if(!flag){}else{}的程式都會執行else,也就是說每次程式都會當做已經將左孩子入隊了(flag=1),從而來判斷剪枝條件//如果條件不滿足,右孩子不入隊,這樣,出現了左右孩子均沒有入隊的情況。但實際上,邏輯上講不通,如果左孩子不入隊,那麼右孩子一定要入隊,因為左孩子入隊//代表的是選中了w[i],如果不滿足wt<=c,不選擇左分支,沒選左分支,說明沒選這個物品,那右孩子入隊的意義當然就是不選擇這個物品w[i],所以如果左孩子不入隊,//那麼右孩子一定要入隊。so,程式的邏輯就是:先檢查左孩子,如果wt<=c,左孩子入隊,flag置.然後通過if語句和flag  if(!flag){}else{}來判斷,如果flag==0,說明//左孩子沒有入隊(物品w[i]沒有被選中),既然沒被選中,右孩子一定要入隊,所以右孩子入隊。但是如果flag==1,說明之前的左孩子滿足條件,已經//入隊了,這樣的話,右孩子入隊與否就得看這個右子樹的當前值(Ew)+剩餘最大的品質(這顆右子樹的後面的全部左子樹的值之和,也就是r)是否>當前最優值bestw//(bestw一般來講就是當前的右子樹的同一級的左節點的值),如果不大於,說明本級的下面幾級的子樹即使全部取左子樹(每個物品都要)//這樣也不如當前的值bestw大,所以此右子樹就不必要了,所以對右子樹進行判斷剪枝(滿足if裡面的條件就不剪枝,右孩子入隊)。}DeQueue(Q, &E);if (E!=0&&Q->weight[E] == -1)  //我覺得這個地方E!=NULL沒啥用,直接if(E->weight==-1)就行{// 到達層的尾部if (Empty(Q)) {break;}if(i<n){Add(Q,  -1 , 0, 0) ; // 同層結點的尾部}DeQueue(Q, &E);            i++; // 進入下一層r -= w[i] ;}Ew = Q->weight[E];}for(int j = n-1; j>0; j--)//構造當前最優解{bestx[j] = Q->LChild[bestE] ;bestE = Q->parent[bestE] ;}    return 0 ;  } int main(){int n =0 ; int c = 0 ;int i = 0 ;int* w ;FILE*in , *out ;in = fopen("input4.txt" , "r") ;if(in==NULL){printf("沒有輸入輸出檔案\n") ;return 1 ;}fscanf(in , "%d" , &n) ;fscanf(in , "%d" , &c) ;w = (int*)malloc(sizeof(int)*(n+1)) ;bestx = (int*)malloc(sizeof(int)*(n+1)) ;for(i =1 ; i<=n ; i++){fscanf(in , "%d" , &w[i]) ;}MaxLoading(w , c , n) ;out = fopen("output.txt" , "w") ;for(i=1 ; i<=n ; i++){fprintf(out , "%d\t" , bestx[i]) ;}fprintf(out , "\n") ;fclose(in);fclose(out);return 0 ;}

二.再說一下鏈隊列來建立資料結構

/************************************************************二重指標方式解決FIFO隊列裝載問題v0.3版本,全部採用局部變數+二重指標+正規熟悉的隊列構建方式+正確的剪枝演算法輸入:在input.txt中輸入n,c,w[1], w[2], ......, w[n]。輸出:輸出到output.txt中bestx[1], bestx[2], ......, bestx[n]的值為表示w[i]被選中,為表示沒有被選中。資料結構:隊列。應用了自己定義的規範型的通用的隊列實現方式,   並且靈活做了個人化處理,比如*parent, LChild等*************************************************************/#include<stdio.h>#include<stdlib.h>typedef struct Qnode{    struct Qnode *parent; // 指向父結點的指標struct Qnode* next ;    int LChild;        // 左兒子標誌    int weight;       // 結點所相應的載重量}QTYPE;/*注意:再定義*E, *bestE的時候直接用QTYPE定義,因為E和bestE都是QTYPE型的,不用定義成LinkQUEUE型的。E和bestE是個游離的節點,與LinkQUEUE *Q的Q沒直接關係,不依賴與Q,是自由的,但是front 和rear是在LinkQUEUE裡面定義的,雖然是QTYPE類型的,但是依賴於Q。Q->front, Q->rear.*/typedef struct{//QTYPE*front,*rear;}LinkQUEUE;// //只有2個全域變數:*bestx,bestw。int *bestx ;int bestw = 0 ; // 目前的最優值int InitQueue(LinkQUEUE *LQ)//初始化隊列{QTYPE*p;p=(QTYPE *)malloc(sizeof(QTYPE));if(!p){printf(" 分配空間失敗!\n");return 0;}p->next=NULL;p->weight=-1;p->parent=NULL;LQ->front=LQ->rear=p;return 1;}int Add(LinkQUEUE *LQ,  int w, QTYPE *E, int l)//入隊的原子操作{QTYPE *q;q=(QTYPE *)malloc(sizeof(QTYPE));if(!q){printf("分配空間失敗!\n");return 0;}q->weight=w;//q->next=NULL;q->parent=E;//q->LChild=l;//LQ->rear->next=q;LQ->rear=q;return 1;}int IsEmpty(LinkQUEUE  *LQ)//判斷對空{return (LQ->front==LQ->rear?1:0);}/*出隊,出隊的節點指標賦給*E,通過二重指標方式來修改傳遞的參數*/int Delete(LinkQUEUE  *LQ, QTYPE **E){QTYPE *tmp=NULL;if(IsEmpty(LQ)){printf("隊空!\n");return 0;}tmp=LQ->front->next;LQ->front->next=tmp->next;if(LQ->front->next==NULL)LQ->rear=LQ->front;*E=tmp;//free(tmp);return 1;}/*入隊的封裝操作通過二重指標方式來修改傳遞的參數*/void EnQueue(LinkQUEUE *LQ, int wt,int i, int n , QTYPE  *E ,QTYPE  **bestE , int ch) {if(i==n){if(wt==bestw){*bestE = E;//bestE只有i==n時候,也就是葉子的時候,才用得到bestE,因為為//了追溯路徑,靠bestE=bestE->parent;//也就是說一旦到了葉子節點,說明搜尋已經結束            bestx[n] = ch;}        return;}Add(LQ, wt , E, ch); // 不是葉子}int MaxLoading(int w[], int c, int n)//求最大裝載函數{int err ; //傳回值int i = 1; // 當前擴充結點的層int cnt=0;int Ew = 0; // 當前擴充結點的權值int r = 0 ; //剩餘集裝箱重量int flag=0;LinkQUEUE*Q=(LinkQUEUE *)malloc(sizeof(LinkQUEUE)); // 活結點隊列QTYPE *E=NULL; QTYPE*bestE=NULL;for(int j =2; j<=n; j++){          r += w[j];}bestw = 0; // 目前的最優值InitQueue(Q);err=Add(Q, -1, NULL, 0);if(!err){return 0 ;}while (true) { int wt = Ew + w[i] ;if (wt <= c)   // 檢查左孩子結點{if(wt>bestw) bestw = wt ;// x[i] = 1EnQueue(Q, Ew + w[i], i , n , E , &bestE , 1);flag=1;/***這部分剪枝的內容也可以放在這個裡面,當然下面就只有if(!flag),而沒有else了if(Ew+r>=bestw&&i<n) // 對右孩子進行剪枝{EnQueue(Q, Ew, i, n , E, &bestE, 0);}*********/}/////if(!flag)//如果flag==0,說明左孩子沒有入隊,那麼一定要把右孩子入隊。也只有因//為這樣,邏輯上才講得通。{EnQueue(Q, Ew, i, n , E, &bestE, 0);}else{if(Ew+r>bestw&&i<n) // 對右孩子進行剪枝{EnQueue(Q, Ew, i, n , E, &bestE, 0);}flag=0;//忘了給flag清零讓我多花了好長時間來檢查,還以為是演算法錯了,實際上我//想的演算法是對的,只是忘了清零了     //也就是說,如果這個flag不清零,那麼每次if(!flag){}else{}的程式都會執行//else,也就是說每次程式都會當做已經將左孩子入隊了(flag=1),從而來//判斷剪枝條件//如果條件不滿足,右孩子不入隊,這樣,出現了左右孩子均沒有入隊的情//況。但實際上,邏輯上講不通,如果左孩子不入隊,那麼右孩子一定要入//隊,因為左孩子入隊代表的是選中了w[i],如果不滿足wt<=c,不選擇左分支,//沒選左分支,說明沒選這個物品,那右孩子入隊的意義當然就是不選擇這//個物品w[i],所以如果左孩子不入隊,那麼右孩子一定要入隊。so,程式//的邏輯就是:先檢查左孩子,如果wt<=c,左孩子入隊,flag置.然後通過if//語句和flag  if(!flag){}else{}來判斷,如果flag==0,說明左孩子沒有入隊(物//品w[i]沒有被選中),既然沒被選中,右孩子一定要入隊,所以右孩子入隊。//但是如果flag==1,說明之前的左孩子滿足條件,已經入隊了,這樣的話,//右孩子入隊與否就得看這個右子樹的當前值(Ew)+剩餘最大的品質(這顆右//子樹的後面的全部左子樹的值之和,也就是r)是否>當前最優值//bestw(bestw一般來講就是當前的右子樹的同一級的左節點的值),如果不//大於,說明本級的下面幾級的子樹即使全部取左子樹(每個物品都要)     //這樣也不如當前的值bestw大,所以此右子樹就不必要了,所以對右子樹進//行判斷剪枝(滿足if裡面的條件就不剪枝,右孩子入隊)。}Delete(Q, &E);if (E!=NULL&&E->weight == -1)  //我覺得這個地方E!=NULL沒啥用,直接//if(E->weight==-1)就行{// 到達層的尾部if (IsEmpty(Q)) {break;}if(i<n){Add(Q,  -1 , NULL, 0) ; // 同層結點的尾部}Delete(Q, &E);            i++; // 進入下一層r -= w[i] ;}Ew = E->weight ;}for(int j = n-1; j>0; j--)//構造當前最優解{bestx[j] = bestE->LChild ;bestE = bestE ->parent ;}    return 0 ;  } int main(){int n =0 ; int c = 0 ;int i = 0 ;int* w ;FILE*in , *out ;in = fopen("input.txt" , "r") ;if(in==NULL){printf("沒有輸入輸出檔案\n") ;return 1 ;}fscanf(in , "%d" , &n) ;fscanf(in , "%d" , &c) ;w = (int*)malloc(sizeof(int)*(n+1)) ;bestx = (int*)malloc(sizeof(int)*(n+1)) ;for(i =1 ; i<=n ; i++){fscanf(in , "%d" , &w[i]) ;}MaxLoading(w , c , n) ;out = fopen("output.txt" , "w") ;for(i=1 ; i<=n ; i++){fprintf(out , "%d\t" , bestx[i]) ;}fprintf(out , "\n") ;fclose(in);fclose(out);return 0 ;}

三.

運行結果:

在input.txt中輸入:n=4, c=70, w1=30, w2=25, w3=15, w4=10.

在output.txt中查看運行結果:

這個1 1 1 0是bestx[i]的結果,bestx[i]==1代表選擇這個分支,也就是第i個貨物裝船,為0表示不裝船。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.