1.思維導圖
2.數據結構
2.1結構體
typedef struct BOOK
{
string ISBN,book,author;
double price;
}BOOK;
typedef struct node
{
BOOK message;
struct node *next;
}List,*Link;
2.2為什么選擇這樣的數據結構
這樣的數據結構使程序邏輯條理顯得更加清晰,兩個結構體分工明確
一開始只定義了一個 BOOK的結構體,結構體成員除了書籍信息還包括BOOK型的結構指針,這樣使原來的BOOK結構體擁有兩個結構體的作用,結構體功能不夠明確
3.關鍵代碼
3.1主要函數聲明
bool insert(Link &L,Link newbook);//增加書籍
void insert_menu();//重載增加書籍函數
bool del_book(Link &L,string Isbn);//刪除書籍
void del_book_menu();//刪除書籍菜單
void findmenu();//查找目錄
void find(Link L,string search,int chose);//查找書籍
Link find(Link &L,string name);//重載的查找函數,用於定位需修改的書籍
void findshow(int n,Link p);//顯示查找到的書籍信息
void findshow(string search);//提示查找的書籍不存在
void changebook();//修改書籍信息函數
void put_file(Link L);//將書籍信息輸出到文件
void in_file(Link &L);//將書籍信息從文件讀取到鏈表
3.2 主要功能函數
生成/保存書籍鏈表
void put_file(Link L)//將書籍信息輸出到文件
{
ofstream outfile("books.txt");
Link p,tail=L,r;
for(p=L->next;p;)
{
r=p->next;
outfile<<p->message.ISBN<<" "<<p->message.book<<" "<<p->message.author<<" "<<p->message.price<<endl;
p=r;
}
outfile.close();
}
void in_file(Link &L)//將書籍信息從文件讀取到鏈表
{
ifstream infile("books.txt");
L=new List;
L->next=NULL;
Link p,tail=L;
p=new List;
p->next=NULL;
while((infile>>p->message.ISBN>>p->message.book>>p->message.author>>p->message.price)>0)
{ //這么寫可以防止文件為空時發生錯誤 (這里貌似並沒有什么卵用)
tail->next=p;
tail=p;
p=new List;
p->next=NULL;
}
infile.close();
}
查找類函數
void findmenu()//查找目錄
{
int chose=1;
while(chose==1)
{
system("cls");
printf("請選擇查找類型:\n");
printf("1:按ISBN碼查找\n");
printf("2:按書名查找\n");
printf("3:按作者查找\n");
char search[60];
while(1)
{
int chose_1;
cin>>chose_1;
switch(chose_1)
{
case 1:cout<<"輸入查找的ISBN碼:"<<endl;break;
case 2:cout<<"輸入查找的書名:"<<endl;break;
case 3:cout<<"輸入查找的作者:"<<endl; break;
default:cout<<"操作錯誤,請重新輸入:"<<endl;break;
}
if(0<chose_1&&chose_1<4) break;
}
cin>>search;
find(L_book,search,chose);
printf("\n\n輸入 1 繼續查找,其他任意數字返回上一級\n");
cin>>chose;
system("cls");
}
}
void find(Link L,string search,int chose)//查找書籍
{
Link p,tail=L;
int cnt=0;
for(p=L->next;p;p=p->next)
{
if(chose==1&&p->message.ISBN==search)//ISBN 碼查找
{
cnt++;
findshow(cnt,p);
break;
}
if(chose==2&&p->message.book==search) //書名查找
{
cnt++;
findshow(cnt,p);
break;
}
if(chose==3&&p->message.author==search)//作者查找
{
cnt++;
findshow(cnt,p);
}
}
if(cnt==0) findshow(search);
}
void findshow(int n,Link p)
{
cout<<n<<" "<<p->message.ISBN<<" "<<p->message.book<<" "<<p->message.author<<" "<<p->message.price<<endl;
}
void findshow(string search)
{
cout<<search<<" 不存在"<<endl;
}
Link find(Link &L,string name)//重載的查找函數,用於定位需修改的書籍
{
Link p,tail=L;
for(p=L->next;p;p=p->next)
{
if(name==p->message.book)
return p;
tail=p;
}
return NULL;
}
一開始字符串用的是字符數組,在查找時,只能調用strcmp()函數,較為不方便,后來改成string字符串,這樣就能直接用"==,>,<"這些比較字符進行比較了,而且,string 字符串還可以直接用"+"在字符串尾部追加字符串和字符,都十分方便,除此之外string字符串還有很方便的操作,具體的C++里的STL里都有相關描述。
插入函數
bool insert(Link &L,Link newbook)//插入書籍 ,按價格升序插入
{
Link p,tail=L;
p=L->next;
for(p=L->next;p;p=p->next)
{
if(p->message.ISBN==newbook->message.ISBN)// 以ISBN碼判斷書籍是否已存在
{
return false;
break;
}
if(p->message.price>=newbook->message.price)//插入書籍
{
newbook->next=p;
tail->next=newbook;
break;
}
tail=p;
}
if(p==NULL) //尾部插入
tail->next=newbook;
return true;
}
void insert_menu()//插入書籍,輸入增加的書目信息
{
printf("請輸入增加的書籍書目:");//可批量增加書籍
int n;
cin>>n;
Link p;
printf("請輸入待增加書籍的所有信息:");
while(n--)
{
p=new List;
p->next=NULL;
cin>>p->message.ISBN>>p->message.book>>p->message.author>>p->message.price;
if(insert(L_book,p)==0)
cout<<"Error insert, "<<p->message.book<<" exited!"<<endl;
else
cout<<p->message.book<<" 已存入"<<endl;
}
Sleep(2000);
}
這里講一下插入的思路吧,就是遍歷鏈表,比較新書的價格,然后把要插入的書籍插入在價格比它高的第一本書前面,但是這樣的話,如果遍歷完鏈表,沒有找到價格新插入的書籍價格高的書,插入就失敗了,所以后面加了個判斷指針是否為空的語句來避免這種情況。(本來想調用STL 里的llist()和sort(),但list()雙向鏈表排序貌似時間復雜度還蠻高的,所以直接有序插入了)
關於stl_list的sort算法
原文地址
這邊的話一般sort()使用的是快排,把需要排序的序列一分為二,然后各自遞歸調用mergesort,再使用Merge算法用O(n)的時間將已排完序的兩個子序列歸並,從而總時間效率為nlg(n)。list_sort所使用的mergesort形式上大不一樣:將前兩個元素歸並,再將后兩個元素歸並,歸並這兩個小子序列成為4個元素的有序子序列;重復這一過程,得到8個元素的有序子序列,16個的,32個的。。。,直到全部處理完。主要調用了swap和merge函數,而這些又依賴於內部實現的transfer函數(其時間代價為O(1))。該mergesort算法時間代價亦為nlg(n),計算起來比較復雜。list_sort中預留了64個temp_list,所以最多可以處理2^64-1個元素的序列,這應該足夠了:)
刪除函數
void del_book_menu()//刪除書籍界面
{
system("cls");
printf("請輸入刪除的書籍數量:");//可批量刪除書籍
int n;
cin>>n;
cout<<"請輸入所有待刪除書籍的ISBN碼:";
string Isbn;
while(n--)
{
cin>>Isbn;
if(del_book(L_book,Isbn))
cout<<"IBSN碼為 "<<Isbn<<" 的書籍已刪除"<<endl;
else
cout<<"IBSN碼為 "<<Isbn<<" 的書籍不存在"<<endl;
}
Sleep(2000);
}
bool del_book(Link &L,string Isbn)//刪除書籍
{
Link p,tail=L;
for(p=L->next;p;p=p->next)
{
if(p->message.ISBN==Isbn)
{
tail->next=p->next;
delete p;
break;
}
tail=p;
}
if(p==NULL) //刪除書籍不存在
return false;
else return true;
}
修改函數
void changebook()//修改書籍
{
int chose=1;
Link p;
while(chose==1) //循環操作
{
system("cls");
char name[60];
cout<<"輸入要修改書籍的書名"<<endl;
cin>>name;
p=find(L_book,name);
if(p=NULL)
cout<<"該書不存在"<<endl;
else
{
printf("%s %s %s %.2f\n",p->message.ISBN,p->message.book,p->message.author,p->message.price);
printf("請選擇需要修改的信息:\n");
printf("1:修改ISBN碼\n");
printf("2:修改書名\n");
printf("3:修改作者\n");
printf("4:修改價格\n");
printf("5:修改該書所有信息(ISBN 書名 作者 價格)\n");
while(1)
{
int chose_1;
cin>>chose_1;
switch(chose_1) //按選擇修改書籍指定信息
{
case 1:
cout<<"輸入修改后的ISBN碼:"<<endl;
cin>>p->message.ISBN;break;
case 2:
cout<<"輸入修改后的書名:"<<endl;
cin>>p->message.book;break;
case 3:
cout<<"輸入修改后的作者:"<<endl;
cin>>p->message.author;break;
case 4:
cout<<"輸入修改后的價格:"<<endl;
cin>>p->message.price;break;
case 5:cout<<"輸入修改后的所有該書信息(ISBN 書名 作者 價格)"<<endl;
cin>>p->message.ISBN>>p->message.book>>p->message.author>>p->message.price;break;
default:cout<<"操作錯誤,請重新輸入:"<<endl;break;
}
if(0<chose_1&&chose_1<=5) break;
}
cout<<"修改成功!"<<endl;
}
printf("\n\n輸入 1 修改其他書籍,其他任意數字返回上一級\n");
cin>>chose;
}
}
4.待提高的地方
4.1 查找的算法不夠優化。
采用單鏈表遍歷的方式查找書籍,只能將鏈表遍歷一遍,逐個比較查找到所搜索的書籍,如果書目數量太大,查找速度就會很慢;思考了下老師所說的字典序查找,可將書目信息按拼音字母進行分級分類,查找時根據分級的信息科較快的定位到所查找的書籍。這樣查找速度應該會快很多
4.2 文件操作過於頻繁。
起初的代碼只有兩次文件操作,程序開始運行時執行一次讀取文件的操作,和結束程序保存數據到文件的操作,但這樣如果非正常關閉程序,會使之前的操作數據全部丟失,因此改為每次循環保存一次,但這樣的話,如果書籍信息太多,卻會大大降低運行速度
在修改過程中,老師一直強調要減少函數之間的依賴性,可以把函數分成 菜單函數和業務函數 ,菜單函數 主要負責輸入和輸出,也就是運行程序時,我們所能看到的部分,而 業務函數 則負責內部操作,實現的功能,並不能直觀的看到,是一個純粹的功能函數
下面以 查找函數 為例:
一開始的代碼
void find(Link L,char *search,int chose)//查找書籍
{
Link p,tail=L;
int cnt=0;
for(p=L->next;p;p=p->next)
{
if(chose==1&&strcmp(p->message.ISBN,search)==0)
{
cnt++;
printf("%d %s %s %s %.2f\n",cnt,p->message.ISBN,p->message.book,p->message.author,p->message.price);
break;
}
if(chose==2&&strcmp(p->message.book,search)==0)
{
cnt++;
printf("%d %s %s %s %.2f\n",cnt,p->message.ISBN,p->message.book,p->message.author,p->message.price);
break;
}
if(chose==3&&strcmp(p->message.author,search)==0)
{
cnt++;
printf("%d %s %s %s %.2f\n",cnt,p->message.ISBN,p->message.book,p->message.author,p->message.price);
}
}
if(cnt==0)
printf("該書不存在\n");
}
這里就把查找和輸出完全糅合在一起,函數能使用的范圍有很大程度的限制,而業務函數因為功能純粹能減小這樣限制。
下面附上主函數
int main()
{
in_file(L_book);
int chosen;
while(1)
{
system("cls");
printf("請選擇操作:\n");
printf("1:查閱書籍\n");
printf("2:增加書籍\n");
printf("3:刪除書籍\n");
printf("4:修改書籍\n");
cin>>chosen;
switch(chosen)
{
case 1: findmenu();break;
case 2: insert_menu();break;
case 3: del_book_menu();break;
case 4: changebook();break;
default: printf("輸入錯誤,請重新輸入\n");Sleep(1200);system("cls");
break;
}
put_file(L_book);
}
}