一、問題描述
①在箱子裝載問題中,有若干個容量為c的箱子和n個待裝載入箱子中的物品。物品i需占是s[i]個單元(0<s[i]<=c)。所謂成功裝載(feasible packing),是指能把所有物品都裝入箱子而不溢出,而最優裝載(optimal packing)是指使用了最少箱子的成功裝載。對於箱子裝載問題,有4種流行的求解算法。
②基本要求:
->n依次取100,200,500,1000,比較以上四種方法(在時間上和所用箱子的數量上)的性能。
->FF,FFD方法使用競賽樹結構,BF,BFD使用二叉搜索樹結構。
二、需求描述
1.4種流行的求解算法:
<1>最先匹配法(FF):物品按1,2...,n的順序裝入箱子。假設箱子從左至右排列,每一物品i放入可盛載它的最左箱子。
<2>最先匹配遞減法(FFD):方法與FF類似,區別在於各物品首先按容量遞減的次序排列,即對於1<=i<n,有s[i]>=s[i+1].
<3>最優匹配法(BF):設c[j]為箱子j的可用容量,初始時,所有箱子的可負載容量為c。物品i放入具有最小c且容量大於s[i]的箱子中。
<4>最優匹配遞減法(BFD):方法與BF相似,區別在於各物品首先按容量遞減的次序排列,即對於1<=i<n,有s[i]>=s[i+1].
2.輸入要求:
①第一種輸入方式:n在程序中固定讀入100,200,500,1000個數,這些數預先在文本中手動輸入為固定的無序正整數。
②第二種輸入方式:n為程序運行后,人為輸入的數n,並且生成n個隨機數,用作程序所需要測試的物品大小數。
3.輸出要求:
輸出的要求為:直觀的分析排列出在不同的箱子數量、不同的物品數量以及不同的物品大小的情況下,上述四種方法在時間上和所用的箱子的數量上的不同。
比較四種方法的性能。
4.界面設計:
使用的是程序運行控制台輸出,沒有使用圖形化界面。
界面設計如圖:
①第一種輸入方式:

②第二種輸入方式

5.測試設計:
使用第一種方式輸入數據:首先分別將100,200,500,1000個數據輸入到一個文本文件中,然后在程序中使用讀取文件的方法將文本文件的數據當做程序的測試數據,進行測試實驗。
讀取文件的方法為:

使用的測試數據為:
①100個數據

②200個數據
③500個數據
④1000個數據
數據就是大同小異啦,自己建立txt文件輸入數據就好
三、設計
3.1結構設計
①對於最先匹配法(FF)和最先匹配遞減法(FFD)方法使用了競賽樹結構。
----->對於競賽樹的結構設計
*T[i]代表勝者
*P[I]代表參賽者
*當外部節點為n,則內部節點為n-1
*最底層最左端的內部節點編號為s
*最底層外部節點個數lowext=2(n-s)
*最底層內部節點個數為n-s
*令offset=2*s-1,對任何一個外部節點P[I],可根據公式得出其父節點
(i+offset)/2 i<=lowext
p=
(i-lowext+n-1)/2 i>lowext
----->使用競賽樹對於上述方法的具體實現
*將競賽樹的外部節點當做容量為c的箱子,構建贏者樹,先從左子樹開始查找判斷是否可以容納物品,逐步向下尋找,因為內部節點返回的是外部節點的勝者(這里規定為箱子容量更大的一個作為勝者)即箱子號,便可以找到可以使用的箱子(即對應外部節點),在使用箱子后,箱子的容量減去物品大小,並重構贏者樹,形成新的勝者。
*初始化時,對所有n個箱子,bin [i] .unused Capacity=bin Capacity。
bin [i] 作為選用,對最大贏者樹進行初始化。
*FF和FFD利用了數據類型bintype,該類型只有一個數據成員,unused Capacity 並重載了操作符<=,使表達式x<=y的值為真,當且僅當x.unused Capacity>=y.unused Capacity。
*FF和FFD假定,除非右邊選手大於左邊選手,否則左邊選手為勝者。
*競賽樹是用數組表示的完全二叉樹,它能按照數組的下標乘2或加1方式從上向下移動。
②對於最優匹配法(BF)和最優匹配遞減法(BFD)使用了二叉搜索樹結構。
----->對於二叉搜索樹的結構設計
*對於二叉搜索樹由鏈表描述,因為二叉搜索樹的元素和形狀隨操作而改變,從鏈式描述二叉樹類派生
*使用帶有重復關鍵字的二叉搜索樹能在O(nlogn)時間內實現最優匹配法。
*在根節點的左子樹中,元素的關鍵字(如果有的話)都小於根節點的關鍵字。
*在根節點的右子樹中,元素的關鍵字(如果有的話)都大於根節點的關鍵字。
*根節點的左、右子樹也都是二叉搜索樹。
----->使用二叉搜索樹對於上述方法的具體實現
*在實現最優匹配法時,搜索樹的每個元素代表一個正在使用且剩余容量不為0的箱子
*因為箱子不同,但剩余容量可能相同,可以用一棵帶有重復關鍵字的二叉搜索樹來描述箱子,每個箱子的剩余容量作為節點的關鍵字。
*節點的內部值為箱子的剩余容量,節點外部是箱子的名稱。
*從根節點開始(左小右大),右子樹均可以,則先進入左子樹進行查找,若滿足可裝載,則成為候選者,再進入左子樹,若不滿足則進入其右子樹一直往下找,直到沒有子樹,則沒有更好的候選者,所以箱子i即是要找的箱子。
*當物品i找到最匹配箱子后,可將它從搜索樹中刪除,將其剩余容量減去s[i]再將它重新插入樹中(除非它的剩余容量為0),若沒有找到最匹配的箱子,則啟用一個新箱子。
*對於二叉搜索樹的元素類型是一個偶對pair<const K,E>,其中K是關鍵字類型,E是相應的元素的數據類型。
3.2類設計(共設計了5個類來完成整個程序)
①競賽書實例
{ 實例:
完全二叉樹,每個節點指向比賽勝者,外部節點表示參賽者
操作:
Initialize(a);為數組a的參賽者初始化勝者樹
Winner();返回勝者
Replay(i);在參賽者i改變之后重賽
}
抽象類Winner Tree
1 template<class T> 2 class winnerTree 3 { 4 public: 5 virtual ~winnerTree() {} 6 virtual void initialize(T *thePlayer, int theNumberOfPlayers) = 0; 7 //用參賽者數組建立贏者樹 8 virtual int winner() const = 0; 9 //返回贏者的索引 10 virtual void rePlay(int thePLayer) = 0; 11 //根據參賽真的改變重新構造贏者樹 12 };
②最大贏者樹類繼承競賽樹類實現其中的抽象方法,並定義了使用的變量
1 template<class T> 2 class completeWinnerTree : public winnerTree<T> 3 { 4 public: 5 completeWinnerTree(T *thePlayer, int theNumberOfPlayers) 6 { 7 tree = NULL; 8 initialize(thePlayer, theNumberOfPlayers); 9 } 10 ~completeWinnerTree() {delete [] tree;} 11 void initialize(T*, int); 12 int winner() const 13 {return tree[1];} 14 int winner(int i) const 15 {return (i < numberOfPlayers) ? tree[i] : 0;}// 返回節點i的贏者 16 void rePlay(int); 17 void output() const; 18 private: 19 int lowExt; // 最底層外部結點個數 20 int offset; // 2^log(n-1) - 1 21 int *tree; //贏者樹的數組 22 int numberOfPlayers; 23 T *player; //參賽者的數組 24 void play(int, int, int); 25 };
③定義二叉樹類
1 template<class T> 2 class binaryTree 3 { 4 public: 5 virtual ~binaryTree() {} 6 virtual bool empty() const = 0; 7 virtual int size() const = 0; 8 virtual void preOrder(void (*) (T *)) = 0; 9 // 前序遍歷輸出二叉樹 10 virtual void inOrder(void (*) (T *)) = 0; 11 virtual void postOrder(void (*) (T *)) = 0; 12 virtual void levelOrder(void (*) (T *)) = 0; 13 protected: 14 int treeSize; 15 };
④引用了字典類,用於實現元素類型為偶對pair<const K,E>
1 template<class K, class E> 2 class dictionary 3 { 4 public: 5 virtual ~dictionary() {} 6 virtual bool empty() const = 0; 7 //當字典為空時,返回ture 8 virtual int size() const = 0; 9 //返回字典中數對的數量 10 virtual pair<const K, E>* find(const K&) const = 0; 11 //返回字典中匹配的數對 12 virtual void erase(const K&) = 0; 13 //刪除對應數對 14 virtual void insert(const pair<const K, E>&) = 0; 15 //在字典中插入新數對 16 };
⑤二叉搜索樹繼承字典類,實現字典類的多有抽象方法,並由鏈式二叉樹派生
1 template<class K, class E> 2 class binarySearchTree : public bsTree<K,E> 3 { 4 public: 5 // 實現字典類的方法 6 bool empty() const {return treeSize == 0;} 7 int size() const {return treeSize;} 8 pair<const K, E>* find(const K& theKey) const; 9 void insert(const pair<const K, E>& thePair); 10 void erase(const K& theKey); 11 pair<const K, E>* findGE(const K& theKey) const; 12 void ascend(){} 13 binarySearchTree() {root = NULL; treeSize = 0;} 14 15 protected: 16 binaryTreeNode<pair<const K, E>> *root; // 指向根的指針 17 int treeSize; // 樹的節點個數 18 };
⑥在main函數中,加入上述頭文件
使用switch case語句構成方法的選擇。
3.3函數設計及分析
對於方法的設計都存放在player.h頭文件中。
①對於最先匹配法(FF)方法的設計
首先聲明一個箱子類型,將每個箱子的大小初始化為從鍵盤中接收到的數據,然后用初始化后的箱子構建競賽樹。
然后將物品依次裝入箱子中,先從根的左孩子開始搜索,判斷左孩子中的剩余容量是否裝的下物品,即判斷箱子剩余容量的大小與物品的大小。
設置一個整數變量binTouse作為要使用的箱子,binTouse的值是內部節點孩子的比賽中的獲勝者,將物品裝入該箱子中,然后更新箱子的容量,用箱子容量減去物品大小得到新的箱子容量,之后重新構造贏者樹,進行下一個物品的裝入。
之后用新的箱子剩余容量與之后所有物品的大小進行比較,用於統計箱子的使用數量,當之后的物品大小都大於箱子的新剩余容量,確認這個箱子無法在裝入物品,箱子使用數量加1;若至少有一個物品大小小於箱子的新剩余容量,確認這個箱子在之后還能被再次使用,箱子使用數量不改變。
②對於最先匹配遞減法(FFD)方法的設計
首先對於接收進來的箱子大小進行遞減排序,然后方法主體與最先匹配法(FF)方法相同,使用①所用的步驟進行箱子裝箱的實現。
③對於最優匹配法(BF)方法的設計
首先聲明一個二叉搜索樹類的箱子容量樹,每個箱子聲明為一個數對類型,first是箱子容量,second是箱子的序號。然后進行將物品逐個裝箱,將物品i裝箱,並且尋找最匹配的箱子,使用在二叉搜索樹類中定義的方法findGE(),返回目前找到的元素,其關鍵字是不小於thekey的最小關鍵字。
當沒有足夠大的箱子時啟用一個新箱子,若箱子容量可以裝下物品,則在裝入物品后,將該箱子節點先從箱子容量樹中刪除,並用箱子的剩余容量減去物品的大小,然后將改變后的箱子作為新節點重新插入箱子容量樹中。
之后用新的箱子剩余容量與之后所有物品的大小進行比較,用於統計箱子的使用數量,當之后的物品大小都大於箱子的新剩余容量,確認這個箱子無法在裝入物品,箱子使用數量加1;若至少有一個物品大小小於箱子的新剩余容量,確認這個箱子在之后還能被再次使用,箱子使用數量不改變。
④對於最優匹配遞減法(BFD)方法的設計
首先對於接收進來的箱子大小進行遞減排序,然后方法主體與最有匹配法(BF)方法相同,使用③所用的步驟進行箱子裝箱的實現。
四、測試結果
①第一種輸入方式,即n在程序中固定讀入100,200,500,1000個數,這些數預先在文本中手動輸入為固定的無序正整數。
----->首先根據菜單提示進行操作

----->分別使用100、200、500、1000個物品的數據進行測試輸出結果

②使用第二種輸入方法,即人為輸入的數n,並且生成n個隨機數,用作程序所需要測試的物品大小數。
----->首先先輸入物品的數目

----->根據菜單提示進行操作

----->輸出結果

五、分析與探討
通過實驗結果,可以看出使用不同的方法會對同一個問題有不一樣的性能,在時間使用上,和箱子數量的使用上都有差別。
在使用箱子數量上,可以畫出不同方法的比較圖

對於不同的問題,每種方法的適用范圍也不同,並不能武斷的判定哪種方法較好,哪種方法不好。
對於程序的改進,使用了兩種輸入方式,通過菜單欄的選擇來選擇不同的測試方式和實現的功能,可以人為的輸入需要測試的數據數量,也可以使用程序中固定的數據進行進一步的測試。
該課程設計內容主要是利用數據結構中的競賽樹和二叉搜索樹來實現方法,也從此次實驗中,我學到了更多對數據結構應用的知識,實用數據結構可以使程序的性能得到提升,占用的資源也會合理。
整理大二時寫的數據結構課設代碼,之中還有很多問題,待修改后再放上來。
整理於2017年4月26日
