文章首發網址:星星點燈的技術棧(https://www.xxdiandeng.cn),歡迎關注點燈君的原創博客網站
《數據結構》課程設計報告
- 題目與要求
- 功能設計
- 功能代碼
- 初始化動態順序表
- 創建(初始化)倉庫
- 顯示倉庫
- 入庫
- 出庫
- 查詢電腦數據
- 盤點倉庫
- 重新對倉庫排序
- 導出倉庫數據到文件
- 調試與測試
- 總結
- 參考文獻
- 附錄
課程設計選題:倉庫管理系統
題目與要求
問題描述
某電子公司倉庫中有若干批次的同一種電腦,按價格、數量來存儲。
要求實現功能:
初始化 n 批不同價格電腦入庫;出庫:銷售m臺價格為p的電腦;入庫:新到m臺價格為p的電腦;盤點:電腦的總臺數,總金額,最高價,最低價,平均價格。
注:每個數據元素含有價格與數量;同一價格的電腦存儲為一個數據元素。
系統涉及知識點
倉庫管理系統可以使用順序表、有序表、單鏈表、有序循環鏈表等實現,本設計采用有序表實現。
所謂有序表,是指這樣的線性表,其中所有的元素以遞增或遞減方式有序排列。
首先要指出的是,有序表是基于順序表而延伸出來的一種數據結構,其共同點是用一組地址連續的存儲單元依次存儲線性表的數據元素。
其次,是有序表的獨特之處,它其中的數據元素按照一定的順序有序排列。
倉庫管理系統適合采用有序表,原因是可以按照商品的價格或數量進行有序排列,方便用戶比對價格、數量。
功能要求
初始化倉庫:初始化n批不同型號的電腦入庫;
入庫:新到m臺價格為p的電腦;
出庫:銷售m臺價格為p的電腦;
盤點倉庫:列出倉庫的關鍵數據:電腦的總臺數、總金額、最高價、平均價格等;
按照有序表的特點以及所使用的編程語言(C++)的特性,本程序還提供了以下功能:
查詢某一型號的電腦的價格、數量;
重新對倉庫數據按照一定規則排序;
導出倉庫數據到外部文件;
從外部文件導入數據,以初始化倉庫。
功能設計
數據結構定義
一、基本數據元素:電腦
typedef struct computer
{char type
[50]; double price
; int number
;
} Computer
, ElemType
;
基本數據元素電腦(Computer/ElemType)采用結構體表示,用于存儲某一類電腦的信息:型號(type[50])、價格(price)、庫存數量(number)。
二、數據結構:有序表
typedef struct {ElemType
*elem
; int length
; int listsize
; bool isInit
{false}; int sortWay
{1};
} SqList
;
數據結構有序表(SqList)由以下幾個部分組成:
數組指針elem指示有序表的基地址;length指示有序表的當前有效數據個數(長度);listsize指示有序表當前分配的存儲空間大小,一旦因插入數據元素(Computer)而空間不足時,可進行再分配;isInit指示有序表是否已經初始化(即是否有一個確定的基地址);sortWay指示有序表的排序方式,按照值的不同,對應的有序表排序方式也不同。本程序具體設計了以下四種排序方式:1-按照價格升序、2-按照價格降序、3-按照數量升序、4-按照數量降序。
整體的數據結構如下圖所示:
模塊圖
入庫
入庫有兩種方式,一是在倉庫中已有和待入庫電腦型號相同的數據,此時,檢查給出的價格是否與倉庫中一致,若一致,同意用戶的入庫操作。然后只需更改倉庫中此種電腦型號的數量。
示意圖如下:
第二種方式,入庫一種新型號的電腦,則應該按照有序表的排序方式(sortWay)在正確位置插入元素。
示意圖如下:
出庫
可以出庫的前提是,在倉庫中有待出庫型號的電腦且倉庫中的庫存數量大于等于待出庫數量。
第一類情況,待出庫電腦的數量在庫存中充足(即庫存數量大于待出庫數量),此時只需更改相應的數據元素。
示意圖如下:
第二類情況,此種型號的電腦恰好全部出庫完,則需要刪除相應的數據元素。
示意圖如下:
功能代碼
初始化動態順序表
有序表的數據結構是基于順序表實現的,所以在進行一切操作前,應當初始化一個空的動態順序表。
代碼如下:
Status
InitSqList(SqList
&L
, int n
) {auto listsize
{((n
/ 10) + 1) * 10}; L
.elem
= new ElemType
[listsize
]; if (!L
.elem
) return OVERFLOW
;L
.length
= 0; L
.listsize
= listsize
;return OK
;
}
有序表初始分配的內存空間(listsize)并沒有簡單采用一個常數大小(如10個ElemType字節),這樣不用限制用戶輸入的電腦類型最大個數,對用戶友好。具體是通過給定的n,計算表達式((n/10)+1)*10,使得初始分配的內存為10的整數倍大小的ElemType字節,比如n為12,表達式((n/10)+1)*10則為20。
需要注意的是,初始化動態順序表時,還暫無數據元素,L.length應為0。
創建(初始化)倉庫
創建倉庫的前提是,已經初始化過一個空的動態順序表,這個檢測可以在主函數中完成。
創建有序表式的倉庫步驟:
輸入n個數據元素,存儲到順序表中;對已創建的順序表按照有序表的排序方式(L.sortWay)進行快速排序。更新有序表的相應參數(length、isInit、sortWay)
代碼如下:
void CreateWarehouse(SqList
&L
, int n
, int sort_way
) {cout
<< "請輸入這" << n
<< "種電腦各自的型號、單價、總數:(以空格分隔)" << endl
;for (int i
= 0; i
< n
; i
++)cin
>> L
.elem
[i
].type
>> L
.elem
[i
].price
>> L
.elem
[i
].number
;switch (sort_way
) {case 1:sort(L
.elem
, L
.elem
+ n
, cmp1
);break;case 2:sort(L
.elem
, L
.elem
+ n
, cmp2
);break;case 3:sort(L
.elem
, L
.elem
+ n
, cmp3
);break;case 4:sort(L
.elem
, L
.elem
+ n
, cmp4
);break;default:break;}L
.sortWay
= sort_way
;L
.length
= n
;L
.isInit
= true;
}
如果使用文件流創建倉庫,那么代碼略有不同,這部分屬于C++的知識。
不同代碼(輸入n數據元素)如下:
string s
;
for (int i
= 0; i
< n
; i
++) {ImportFile
>> L
.elem
[i
].type
>> L
.elem
[i
].price
>> L
.elem
[i
].number
;getline(ImportFile
, s
);
}
顯示倉庫
顧名思義,就是將倉庫的數據打印出來。
代碼如下:
void PrintWarehouse(SqList L
) {if (!L
.length
)cout
<< "當前倉庫沒有數據!" << endl
;else {cout
<< "當前倉庫數據如下:" << endl
;string sort_way
;switch (L
.sortWay
) {case 1:sort_way
= "(按照價格升序)";break;case 2:sort_way
= "(按照價格降序)";break;case 3:sort_way
= "(按照數量升序)";break;case 4:sort_way
= "(按照數量降序)";break;default:break;}cout
<< sort_way
<< endl
;cout
<< "-------------------------------------------------------" << endl
;cout
<< setLeft
<< setw(5) << "序號"<< setw(20) << "型號"<< setw(15) << "單價"<< setw(15) << "數量"<< endl
;cout
<< "-------------------------------------------------------" << endl
;for (int i
= 0; i
< L
.length
; i
++)cout
<< setLeft
<< setw(5) << i
+ 1<< setw(20) << L
.elem
[i
].type
<< setw(15) << L
.elem
[i
].price
<< setw(15) << L
.elem
[i
].number
<< endl
;cout
<< "-------------------------------------------------------" << endl
;}
}
入庫
入庫要考慮兩種情況,第一種情況,倉庫中已經有和待入庫電腦相同型號的數據元素,那么需要先比較價格是否一致(不一致拒絕用戶的入庫操作),然后更改倉庫中此種電腦數據元素的數量值。
第二種情況入庫一種新型號的電腦,那么則要插入一個新的數據元素(Computer)到倉庫中,并且在插入之前還要檢查有序表的內存空間(listsize)是否充足,如果不足,則需要再分配有序表的內存大小,即為順序表增加一個大小為存儲LISTINCREMENT個數據元素的空間。然后,根據有序表的排序方式(L.sortWay),找到可以插入元素的地址。插入時,先將該地址及以后的地址全部后移一位,接著將當前地址寫入要入庫的數據元素。
代碼如下:
Status
Enter(SqList
&L
, const Computer
&c
) {for (int i
= 0; i
< L
.length
; i
++) {if (strcmp(c
.type
, L
.elem
[i
].type
) == 0) {if (c
.price
== L
.elem
[i
].price
) { L
.elem
[i
].number
+= c
.number
;return OK
;} else {cout
<< "提示:" << endl
;showInfo(L
.elem
[i
]);return ERROR
;}}}if (L
.length
>= L
.listsize
) { auto *newbase
= (ElemType
*) realloc(L
.elem
, (L
.listsize
+ LISTINCREMENT
) * sizeof(ElemType
));if (!newbase
)return OVERFLOW
;L
.elem
= newbase
; L
.listsize
+= LISTINCREMENT
;}int item
{L
.length
}; for (int i
= 0; i
< L
.length
; i
++) {switch (L
.sortWay
) {case 1: {if (c
.price
< L
.elem
[i
].price
) {item
= i
;goto change_sq
;}}break;case 2: {if (c
.price
> L
.elem
[i
].price
) {item
= i
;goto change_sq
;}}break;case 3: {if (c
.number
< L
.elem
[i
].number
) {item
= i
;goto change_sq
;}}break;case 4: {if (c
.number
> L
.elem
[i
].number
) {item
= i
;goto change_sq
;}}break;}}change_sq
:ElemType
*q
= &L
.elem
[item
];for (ElemType
*p
= L
.elem
+ L
.length
- 1; p
>= q
; p
--)*(p
+ 1) = *p
;*q
= c
;L
.length
++;return OK
;
}
要注意的是如果是入庫一種新型號的電腦,完成操作后,應當將有序表的長度加1(L.length++)。
出庫
出庫要考慮以下幾種情況:
倉庫中是否有待出庫電腦的型號的相應數據元素,如果沒有找到相應的電腦型號,則應當拒絕用戶的出庫操作;倉庫中現有庫存數量是否充足,滿足出庫要求,如果庫存數量小于待出庫數量,則應當拒絕用戶的出庫操作;如果庫存數量大于待出庫數量,那么只需更改相應數據元素的數量值;如果庫存數量恰好等于待出庫數量,那么此時這種型號的電腦應從倉庫中“剔除”。具體操作是,找到該數據元素的地址,將該地址及以后的地址全部前移一位。最后,更新L.length參數。
代碼如下:
Status
Out(SqList
&L
, const char *type
, int num
) {int item
{L
.length
+ 1};for (int i
= 0; i
< L
.length
; i
++) {if (strcmp(type
, L
.elem
[i
].type
) == 0) {item
= i
;break;}}if (item
> L
.length
)return -1;if (num
< L
.elem
[item
].number
) {L
.elem
[item
].number
-= num
;return OK
;} else if (num
== L
.elem
[item
].number
) {cout
<< "提示:" << endl
<< L
.elem
[item
].type
<< "型號的電腦已全部出庫!" << endl
;for (ElemType
*p
= &L
.elem
[item
+ 1]; p
<= &L
.elem
[L
.length
- 1]; p
++) {*(p
- 1) = *p
;}L
.length
--;return OK
;} else {cout
<< "提示:" << endl
;showInfo(L
.elem
[item
]);return ERROR
;}
}
查詢電腦數據
查詢電腦數據,只需從基地址開始遍歷,直到找到數據元素的型號和待查詢的型號相同,那么返回電腦信息。
可以定義一個item參數,初始值為L.length + 1,如果找到數據元素,則將其更新為數據元素的邏輯位序。如果遍歷結束,item都未更新,說明未找到。因為數據元素的最大邏輯位序是有序表的表長(L.length),所以判斷是否找到數據元素的條件是:item > L.length。
代碼如下:
void getInfo(SqList L
, const char *type
) {int item
{L
.length
+ 1};for (int i
= 0; i
< L
.length
; i
++) {if (strcmp(type
, L
.elem
[i
].type
) == 0) {item
= i
;break;}}if (item
> L
.length
)cout
<< "沒有找到" << type
<< "型號的電腦" << endl
;else {cout
<< "信息如下:" << endl
;showInfo(L
.elem
[item
]);}
}
盤點倉庫
從基地址開始遍歷,求得關鍵數據:總臺數(numSum)、總金額(priceSum)、最高價(priceMax)、最低價(priceMin)、平均價(priceAverage)。
代碼如下:
void Check(SqList L
) {int numSum
{0};double priceSum
{0.0}, priceMax
{L
.elem
[0].price
}, priceMin
{L
.elem
[0].price
}, priceAverage
{0.0};vector
<char *> priceMaxComputer
; vector
<char *> priceMinComputer
; if (!L
.length
) {cout
<< "當前倉庫沒有數據!" << endl
;} else {for (int i
= 0; i
< L
.length
; i
++) {numSum
+= L
.elem
[i
].number
;priceSum
+= L
.elem
[i
].price
* L
.elem
[i
].number
;priceMax
= max(priceMax
, L
.elem
[i
].price
);priceMin
= min(priceMin
, L
.elem
[i
].price
);}for (int i
= 0; i
< L
.length
; i
++) {if (L
.elem
[i
].price
== priceMax
)priceMaxComputer
.push_back(L
.elem
[i
].type
);if (L
.elem
[i
].price
== priceMin
)priceMinComputer
.push_back(L
.elem
[i
].type
);}priceAverage
= priceSum
/ numSum
;cout
<< "盤點數據如下" << endl
<< "電腦的總臺數: " << numSum
<< endl
<< "電腦的總金額: " << priceSum
<< endl
<< "電腦的最高價: " << priceMax
<< endl
<< " + 對應的型號是: ";for (const auto &c
:priceMaxComputer
)cout
<< c
<< "\t";cout
<< endl
;cout
<< "電腦的最低價: " << priceMin
<< endl
<< " + 對應的型號是: ";for (const auto &c
:priceMinComputer
)cout
<< c
<< "\t";cout
<< endl
;cout
<< "電腦的平均價格: " << priceAverage
<< endl
;}
}
這里定義了兩個char*向量(priceMaxComputer、priceMinComputer),用以存儲價格最高的電腦的類型名、最低的電腦的類型名,因為可能若干種型號的電腦都是最高價或最低價,所以使用C++的向量數據結構比較合適。這樣,可以顯示出最高價、最低價相應的電腦類型名,對用戶友好。
重新對倉庫排序
只需按照給定的排序方式,對倉庫進行快速排序,然后更改L.sortWay參數為相應值。
代碼如下:
cout
<< "請輸入要重新對倉庫排序的主要方式" << endl
<< "1. 按價格升序 2. 按價格降序" << endl
<< "3. 按數量升序 4. 按數量降序" << endl
<< "選擇1-4:";
auto select
= input_number(1, 4);
switch (select
) {case 1:sort(L
.elem
, L
.elem
+ L
.length
, cmp1
);break;case 2:sort(L
.elem
, L
.elem
+ L
.length
, cmp2
);break;case 3:sort(L
.elem
, L
.elem
+ L
.length
, cmp3
);break;case 4:sort(L
.elem
, L
.elem
+ L
.length
, cmp4
);break;default:break;
}
L
.sortWay
= select
;
cout
<< "重新排序成功!" << endl
;
其中,4種排序方式的代碼如下:
bool cmp1(const Computer
&a
, const Computer
&b
) {return a
.price
< b
.price
;
}
bool cmp2(const Computer
&a
, const Computer
&b
) {return a
.price
> b
.price
;
}
bool cmp3(const Computer
&a
, const Computer
&b
) {return a
.number
< b
.number
;
}
bool cmp4(const Computer
&a
, const Computer
&b
) {return a
.number
> b
.number
;
}
導出倉庫數據到文件
原理和顯示倉庫大同小異,只是多了文件流的操作。
代碼如下:
void output(SqList L
, const string
&filename
) {ofstream
OutFile(filename
+ ".txt");OutFile
<< "Computer Warehouse Data" << endl
;if (!L
.length
)OutFile
<< "no data" << endl
;else {OutFile
<< "The information is as follows:" << endl
;switch (L
.sortWay
) {case 1:OutFile
<< "Sort Way: 1 (Ascending by price)" << endl
;break;case 2:OutFile
<< "Sort Way: 2 (Descending by price)" << endl
;break;case 3:OutFile
<< "Sort Way: 3 (Ascending by number)" << endl
;break;case 4:OutFile
<< "Sort Way: 4 (Descending by number)" << endl
;break;default:break;}OutFile
<< "Total Type: " << L
.length
<< endl
;for (int i
= 0; i
< L
.length
; i
++)OutFile
<< L
.elem
[i
].type
<< " "<< L
.elem
[i
].price
<< " "<< L
.elem
[i
].number
<< endl
;OutFile
.close();}
}
調試與測試
注意:本設計使用的編譯器是G++ 8.1.0,在Windows平臺上編譯、運行成功。
編譯命令為:“g++ main.cpp sqlist.cpp -o main”(不含引號)。
調試分析
一、創建倉庫
為方便用戶操作,本程序可以導出倉庫數據到文件,然后在下次操作時,可以用此文件做倉庫的初始化。這是在考慮到程序的友好性后增加的功能。
無論是手動創建還是從文件導入,方法是大同小異的。
本模塊時間復雜度:O(nlogn)O(nlogn)O(nlogn)(快速排序O(nlogn)O(nlogn)O(nlogn))
二、入庫、出庫、查詢、盤點倉庫
時間復雜度均為:O(n)O(n)O(n)
考慮到程序的友好性,對入庫、出庫、查詢的拒絕操作均給出具體信息。比如,在出庫時如果庫存數量小于待出庫數量,則先顯示當前庫存,然后提示庫存數量不足。
三、其他
菜單和主函數設計中,由于經常需要檢測輸入數字是否在某一范圍內,使得生成大量重復代碼。于是,將這些代碼設計成函數,增加了代碼的可讀性。并且考慮到,用戶的誤輸入(非數字)會導致程序崩潰,設計了正則表達式匹配,檢測用戶輸入。
函數代碼如下:
int input_number(int from
, int end
) {auto select
{0};string input
;regex
r("[0-9]*"); while (true) {cin
>> input
;bool isNumber
= regex_match(input
,r
);if (!isNumber
) cout
<< "輸入錯誤,請輸入數字" << from
<< "-" << end
<< ":";else {select
= atoi(input
.c_str());if (select
< from
|| select
> end
)cout
<< "輸入錯誤,重新輸入" << from
<< "-" << end
<< ":";elsebreak;}}return select
;
}
另外一個是,如果用戶在初始化倉庫的情況下,進行入庫、出庫等操作,亦會導致程序崩潰。所以,后來在有序表中增加了一個參數isInit,用于指示當前有序表是否已經初始化。在每次操作之前,檢測是否已經初始化,這樣避免了這一類型的崩潰。
用戶手冊
啟動本程序后,按照菜單的提示進行操作即可。
注意:
第一步必須先初始化倉庫,否則,您無法進行其他的操作,將返回“倉庫還未初始化!”。初始化倉庫時,可以選擇“2.從外部文件導入”,但前提是,該外部文件要存在,且是以前程序生成的導出合法文件。否則,不存在的文件將提示“沒有文件/文件無法打開”,不是合法文件將提示“文件內容不符合要求”,這會導致初始化倉庫失敗。所以,請檢查您輸入的文件名和文件內容!建議在入庫操作之前先顯示倉庫,確保增加倉庫中某一電腦型號的數量時,輸入的價格和倉庫中一致;建議在出庫操作之前先顯示倉庫,確保倉庫中有您想要出庫的電腦型號,以及其數量充足;您可以選擇菜單項“7.重新對倉庫排序”,按照您喜愛的方式對倉庫排序,本程序提供了四種主要排序方式:1-按照價格升序、2-按照價格降序、3-按照數量升序、4-按照數量降序建議每次結束程序前,導出倉庫數據到文件,這樣可以保存倉庫數據,方便下次操作。
測試過程
本設計給出了一個輸入文件info.txt
內容如下:
Computer Warehouse Data
The information is as follows:
Sort Way: 1 (Ascending by price)
Total Type: 12
RedmiBook-14 3299 30
Dell-G5 5699 14
Surface-Pro-7 5788 20
Lenovo-Air14 5999 17
Lenovo-R7000 6057 12
Lenovo-Yoga14 6299 8
Lenovo-Pro14 6299 10
MiBook-Pro-15.6 6999 13
Dell-G3 6999 15
Dell-XPS13 8888 5
MacBook-Pro-13 9199 5
MacBook-Pro-16 17399 3
下面是各種操作的輸出結果(截圖):
1、菜單顯示
2、初始化倉庫
3、顯示倉庫
4、入庫
入庫Dell-G5 5699 2:
入庫后顯示倉庫:
入庫Huawei-MateBook-X 8999 2:
入庫后顯示倉庫:
5、出庫
出庫Huawei-MateBook-X 2:
出庫后顯示倉庫:
6、查詢
查詢Lenovo-Pro14
7、盤點倉庫
8、重新對倉庫排序
按數量升序對倉庫排序:
顯示重新排序后的倉庫:
總結
通過本次課程設計,在數據結構課程中所學到的順序表基礎上,實現了有序表,并通過基本數據元素(Computer)和數據結構(SqList)的設計,實現了簡易的倉庫管理系統。
在本次課程設計中,也學習到很多的C++的知識,如STL中數據結構與算法的應用(vector、sort)、文件流(fstream)。
通過設計輸入輸出文件的功能,解決了以往繁瑣的數據輸入步驟。同時,也增加了程序的友好性。
通過輸入檢測、正則表達式匹配,解決了非法輸入導致程序崩潰的問題。
但限于編程能力,本程序仍存在某些問題,如初始化時,沒有對輸入相同型號的數據做處理,這是程序的改進方向。
參考文獻
嚴蔚敏,吳偉民.數據結構(C語言版)[M]. 北京:清華大學出版社,1997.
嚴薇敏,吳衛民.數據結構題集(C 語言版)[M].北京:清華大學出版社,1997.
[美]Y.Danie Liang. C++程序設計.[M].北京:機械工業出版社,2015.
Michael Wong,IBM XL編譯器中國開發團隊.深入理解C++11:C++11新特性解析與應用.[M].北京:機械工業出版社,2013.
附錄
源代碼見Github倉庫
總結
以上是生活随笔為你收集整理的数据结构课程设计:基于有序表的仓库管理系统的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。