實驗2-鏈表
1、實驗目的:
(1)掌握線性表的定義;
(3)掌握線性表的基本操作,如建立、查找、插入和刪除等。
(4)掌握文件方式輸入
2、實驗內容:
定義一個包含學生信息(學號,姓名,成績)的單鏈表,使其具有如下功能(參見教材中基本操作):
(1) 從文件中輸入學生信息(前插法和后插法構建表);
(2) 逐個顯示學生表中所有學生的相關信息(顯示表,即遍歷表);
(3) 根據姓名進行查找,返回此學生的學號和成績(查找);
(4) 根據指定的位置可返回相應的學生信息(學號,姓名,成績)(取值 );
(5) 給定一個學生信息,插入到表中指定的位置(插入);
(6) 刪除指定位置的學生記錄(刪除);
(7) 統計表中學生個數(相當於求表長)。
說明
實驗指導代碼存在的一些問題:
students.txt
使用的是相對路徑,所以運行時需要將該文件放至可執行文件
所在的路徑,否則無法找到文件。對於 VisualStudio,調試運行時可執行文件的位置是項目路徑\Debug
,請將students.txt
復制到該文件夾下,使用 DevCpp 的同學同理。- 代碼沒有檢查
students.txt
中的空白行,所以可能輸入無效數據,請把students.txt
最后一行空行刪除 - 函數命名風格很難看,為了和實驗指導書一致,我沒有修改。建議自己改寫函數名,請參考 Google 開源項目風格指南
代碼參考
文件名: students.txt
學號 姓名 成績
201713160101 ZSY 85
201713160102 WYX 70
201713160103 FHY 90
201713160104 WJY 65
201713160105 LZC 75
201713160106 ZL 55
201713160107 ZYZ 95
201713160108 LZY 85
201713160109 ZC 80
201713160110 TL 70
201713160111 PYF 60
201713160112 LCG 50
201713160113 CYF 40
201713160114 KDJ 75
201713160115 KYM 85
201713160116 CPY 65
201713160117 LL 45
201713160118 LYH 55
201713160119 DJX 80
201713160120 ZXH 90
文件名: LinkList.cpp
#include <iostream>
#include <cstring>
#include <fstream>
using std::cin;
using std::cout;
using std::endl;
using std::fstream;
using std::string;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status; //Status 是函數返回值類型,其值是函數結果狀態代碼。
struct Student
{
char no[80]; //學號
char name[80]; //姓名
int price; //成績
};
struct LNode
{
Student data; //結點的數據域
struct LNode *next; //結點的指針域
};
typedef LNode *LinkList; //LinkList為指向結構體LNode的指針類型
typedef Student ElemType; //ElemType 為可定義的數據類型,此設為Student類型
// --------------------- 以下是鏈表相關函數聲明 ------------------------------
Status InitList_L(LinkList &L); //初使化鏈表
void CreateList_H(LinkList &L); //前插法構建鏈表
void CreateList_R(LinkList &L); //后插法構建鏈表
int ListLength_L(LinkList &L); //鏈表長度
Status ListDisplay(LinkList &L); //顯示
LNode *LocateElem_L(LinkList &L, char *name); //查找
Status GetElem_L(LinkList &L, int i, Student &e); //獲取元素
Status ListInsert_L(LinkList &L, int i, Student &e); //插入元素
Status ListDelete_L(LinkList &L, int i); //刪除
Status DestroyList_L(LinkList &L);
// --------------------- 一些用到的全局變量 -----------------------------------
string head_1, head_2, head_3; //文件中的標題(第一行)
int length; // 全局變量保存鏈表長度
// -------------- 下面是 main 函數, 程序入口 --------------------------------
int main()
{
int i, choose;
Student e;
char name[80];
LinkList L = NULL;
LNode *p = NULL;
cout << "---------------------\n";
cout << "1.初使化鏈表\n";
cout << "2.前插法構建鏈表\n";
cout << "3.后插法構建鏈表\n";
cout << "4.求鏈表長度\n";
cout << "5.顯示鏈表內容\n";
cout << "6.查找學生信息\n";
cout << "7.獲取學生信息\n";
cout << "8.插入學生信息\n";
cout << "9.刪除學生信息\n";
cout << "10.銷毀鏈表\n";
cout << "0.退出\n";
cout << "---------------------\n\n";
choose = -1;
while (choose != 0)
{
cout << "\n請選擇:";
cin >> choose;
switch (choose)
{
case 1: //初始化一個單鏈表
if (InitList_L(L))
cout << "成功建立鏈表!\n";
else
cout << "建立鏈表失敗!\n";
break;
case 2: //使用前插法創建單鏈表
CreateList_H(L);
cout << "輸入 students.txt 信息完畢(前插法)\n";
break;
case 3: //使用后插法創建單鏈表
CreateList_R(L);
cout << "輸入 students.txt 信息完畢(后插法)\n";
break;
case 4: //求單鏈表長度
cout << "當前鏈表長度為:" << ListLength_L(L) << endl;
break;
case 5: //顯示鏈表內容
cout << "當前鏈表內容為:\n";
ListDisplay(L);
break;
case 6: //單鏈表的按序號取值
cout << "請輸入學生序號:";
cin >> i;
if (GetElem_L(L, i - 1, e))
{
cout << "查找成功\n";
cout << "第" << i << "本學生證的信息是:\n";
cout << "\t" << e.no << "\t" << e.name << "\t" << e.price << endl;
cout << endl;
}
else
cout << "查找失敗\n\n";
break;
case 7: //單鏈表的按值查找
cout << "請輸入所要查找學生姓名:";
cin >> name;
p = LocateElem_L(L, name);
if (p)
{
cout << "查找成功\n";
cout << "對應的學生信息為:\n";
cout << "\t" << p->data.no << "\t" << p->data.name << "\t" << p->data.price << endl;
}
else
cout << "查找失敗! " << name << " 沒有找到\n\n";
break;
case 8: //單鏈表的插入
cout << "請輸入插入的位置:";
cin >> i;
cout << "輸入學生信息(學號 姓名 成績):";
cin >> e.no >> e.name >> e.price;
if (ListInsert_L(L, i - 1, e))
cout << "插入成功.\n\n";
else
cout << "插入失敗!\n\n";
break;
case 9: //單鏈表的刪除
cout << "請輸入所要刪除的學生的編號:";
cin >> i;
if (ListDelete_L(L, i - 1))
cout << "刪除成功!\n\n";
else
cout << "刪除失敗!\n\n";
break;
case 10: //銷毀鏈表
if (DestroyList_L(L))
cout << "成功銷毀鏈表!\n\n";
break;
}
}
return 0;
}
// ------------------------ 鏈表相關函數的實現 -------------------
Status InitList_L(LinkList &L)
{
//構造一個空的單鏈表L
L = new LNode;
L->next = NULL;
length = 0;
return OK;
}
void CreateList_H(LinkList &L)
{
fstream file; //打開文件進行讀寫操作
file.open("students.txt");
if (!file)
{
cout << "未找到相關文件,無法打開!" << endl;
exit(ERROR);
}
file >> head_1 >> head_2 >> head_3; //文件數據中的標題:第一行
while (!file.eof())
{ //將文件中的信息運用前插法插入到鏈表中
LNode *p = new LNode; //生成新結點
file >> p->data.no >> p->data.name >> p->data.price; //輸入元素值賦給新結點*p的數據域
p->next = L->next;
L->next = p;
length++; //同時對鏈表長度進行統計
}
file.close();
}
void CreateList_R(LinkList &L)
{ //算法2.12 后插法創建單鏈表(文件輸入)
//正位序輸入n個元素的值,建立帶表頭結點的單鏈表L
LNode *r = L; // 指向最后一個結點
fstream file; //打開文件進行讀寫操作
file.open("students.txt");
if (!file)
{
cout << "未找到相關文件,無法打開!" << endl;
exit(ERROR);
}
file >> head_1 >> head_2 >> head_3; //文件數據中的標題:第一行,不需要直接丟棄
while (!file.eof())
{ //將文件中的信息運用后插法插入到鏈表中
LNode *p = new LNode; //生成新結點
file >> p->data.no >> p->data.name >> p->data.price; //輸入元素值賦給新結點*p的數據域
p->next = NULL;
r->next = p;
r = p;
length++; //同時對鏈表長度進行統計
}
file.close();
}
Status ListDisplay(LinkList &L)
{
LNode *p = L->next; //p指向第一個結點(跳過頭結點)
int i = 1;
while (p)
{
cout << "[" << i << "]\t" << p->data.no << "\t" << p->data.name << "\t" << p->data.price << "\n";
p = p->next;
i++;
}
cout << endl;
return OK;
}
int ListLength_L(LinkList &L)
{
//返回L中數據元素個數
LNode *p = L->next; //p指向第一個數據結點
int i = 0;
while (p)
{
i++;
p = p->next;
}
return i; // 其實可以直接return length;
}
LNode *LocateElem_L(LinkList &L, char *name)
{
LNode *p = L->next;
while (p && strcmp(p->data.name, name) != 0)
p = p->next;
return p;
}
Status GetElem_L(LinkList &L, int i, Student &e)
{
if (i < 0 || i >= length)
return ERROR; // 下標越界了
LNode *p = L->next;
for (int j = 0; j < i; j++)
p = p->next;
e = p->data;
return OK;
}
Status ListInsert_L(LinkList &L, int i, Student &e)
{
if (i < 0 || i > length)
return ERROR;
LNode *p = L->next;
for (int j = 0; j < i - 1; j++) // p 指向 i - 1 號結點
p = p->next;
LNode *n = new LNode;
n->data = e;
n->next = p->next; // 把新的結點 n 插入 i 號位置
p->next = n;
return OK;
}
Status ListDelete_L(LinkList &L, int i)
{
if (i < 0 || i >= length)
return ERROR;
LNode *p = L->next;
for (int j = 0; j < i - 1; j++) // p 指向 i -1 號結點
p = p->next;
LNode *t = p->next; // 指向i號結點
p->next = t->next;
delete t; // 刪除i號結點釋放內存
return OK;
}
Status DestroyList_L(LinkList &L)
{
LNode *p = L->next;
LNode *n = NULL;
while (p)
{
n = p->next; // 記下p的后繼結點
delete p; // 刪除p指向的結點
p = n;
}
L->next = NULL; // 頭結點后繼置空
length = 0;
return OK;
}
運行截圖
OOP 版
使用 C++11
#include <iostream>
#include <string>
#include <exception>
#include <fstream>
using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::string;
using Score = int; // 成績的類型
struct Student
{
string name; // 姓名
size_t id; // 學號
Score score; // 成績
};
// 單鏈表
template <typename ElemType>
class LinkList
{
struct LNode
{
ElemType data;
LNode *next;
};
private:
LNode *head_; // 頭結點
size_t length_; // 當前元素個數
public:
LinkList();
~LinkList();
LinkList(const LinkList &) = delete;
LinkList &operator=(const LinkList &) = delete;
LNode *GetPreNode(size_t pos); // 返回 pos 的前驅結點
bool IsEmpty() { return length_ == 0; } // 判空
size_t Length() { return length_; } // 獲取數據長度
ElemType &operator[](size_t pos); // 取值
bool Insert(size_t pos, ElemType elem); // 在 pos 插入一個元素
bool TailInsert(ElemType elem); // 尾插法插入元素
bool HeadInsert(ElemType elem); // 頭插法插入元素
ElemType Pop(size_t pos); // 彈出 pos 位置的元素
bool Delete(size_t pos); // 刪除 pos 位置的元素
};
template <typename ElemType>
LinkList<ElemType>::LinkList() : length_(0)
{
head_ = new LNode(); // 創建頭結點
if (!head_)
throw std::bad_alloc(); // 分配內存失敗
head_->next = nullptr;
}
template <typename ElemType>
LinkList<ElemType>::~LinkList()
{
LNode *p = head_->next;
while (p)
{
head_->next = p->next;
delete p;
p = head_->next;
}
delete head_;
}
template <typename ElemType>
typename LinkList<ElemType>::LNode *LinkList<ElemType>::GetPreNode(size_t pos)
{
if (pos < 0 || pos > length_)
throw std::out_of_range("out of range at position " + pos);
LNode *p = head_;
int pos_ = pos - 1;
for (int i = -1; i < pos_; i++)
p = p->next;
return p;
}
template <typename ElemType>
bool LinkList<ElemType>::Insert(size_t pos, ElemType elem)
{
LNode *pre = GetPreNode(pos);
if (!pre)
return false;
LNode *node = new LNode();
node->data = elem;
node->next = pre->next;
pre->next = node;
length_++;
}
template <typename ElemType>
bool LinkList<ElemType>::HeadInsert(ElemType elem)
{
LNode *node = new LNode();
if (!node)
return false;
node->data = elem;
node->next = head_->next; // 原 pos 位的結點變成插入結點的后繼
head_->next = node; // pos 位的前驅結點指向插入結點
length_++;
return true;
}
template <typename ElemType>
bool LinkList<ElemType>::TailInsert(ElemType elem)
{
LNode *last = GetPreNode(Length()); // 找到最后一個元素
LNode *node = new LNode();
if (!node)
return false;
node->data = elem;
node->next = nullptr;
last->next = node;
length_++;
return true;
}
template <typename ElemType>
ElemType &LinkList<ElemType>::operator[](size_t pos)
{
LNode *pre = GetPreNode(pos);
if (!pre || !pre->next)
throw std::out_of_range("out of range at position " + pos);
return pre->next->data;
}
template <typename ElemType>
ElemType LinkList<ElemType>::Pop(size_t pos)
{
LNode *pre = GetPreNode(pos);
if (!pre || !pre->next)
throw std::out_of_range("out of range at position " + pos);
ElemType elem = pre->next->data; // 保存原始值
LNode *tmp = pre->next->next; // pos + 1 號結點
delete pre->next; // 刪除 pos 號結點
pre->next = tmp;
length_--;
return elem;
}
template <typename ElemType>
bool LinkList<ElemType>::Delete(size_t pos)
{
try
{
Pop(pos);
return true;
}
catch (const std::out_of_range)
{
return false;
}
}
// ------------ End of LinkList ----------------------
class StudentManager
{
private:
LinkList<Student> data_;
string data_path_ = "./students.txt";
public:
StudentManager() = default;
~StudentManager() = default;
StudentManager(const StudentManager &) = delete;
StudentManager &operator=(const StudentManager &) = delete;
void Run(); // 運行
void ReadDataWithHeadInsert(); // 前插法構建數據鏈表
void ReadDataWithTailInsert(); // 后插法構建數據鏈表
void ClearScreen(); // 清屏
void ShowMenu(); // 顯示主目錄
void ShowAllStudentInfo(); // 顯示所有學生信息
void InsertStudent(); // 插入一個學生
void DeleteStudent(); // 刪除學生
void SearchByName(); // 根據姓名查找學生
void ShowListStatus(); // 顯示鏈表狀態
};
void StudentManager::ClearScreen()
{
#if defined(__linux__)
system("clear");
#elif defined(_WIN32)
system("cls");
#endif
}
void StudentManager::ReadDataWithHeadInsert()
{
ifstream file(data_path_);
if (!file)
{
cout << "打開文件失敗: " << data_path_ << endl;
return;
}
string line;
getline(file, line); // 首行丟棄
while (!file.eof())
{
Student stu;
file >> stu.id >> stu.name >> stu.score;
data_.HeadInsert(stu);
}
cout << "數據讀取完成!" << endl;
}
void StudentManager::ReadDataWithTailInsert()
{
ifstream file(data_path_);
if (!file)
{
cout << "打開文件失敗: " << data_path_ << endl;
return;
}
string line;
getline(file, line);
while (!file.eof())
{
Student stu;
file >> stu.id >> stu.name >> stu.score;
data_.TailInsert(stu);
}
cout << "數據讀取完成!" << endl;
}
void StudentManager::ShowAllStudentInfo()
{
if (data_.IsEmpty())
{
cout << "沒有學生信息可以刪除!" << endl;
return;
}
cout << "序號\t姓名\t學號\t\t成績" << endl;
for (size_t i = 0; i < data_.Length(); ++i)
cout << "[" << i + 1 << "]\t" << data_[i].name << '\t' << data_[i].id << '\t' << data_[i].score << endl;
}
void StudentManager::InsertStudent()
{
size_t pos;
cout << "插入位置(1-" << data_.Length() << "): ";
cin >> pos;
if (pos < 1 || pos > data_.Length())
{
cout << "輸入有誤!" << endl;
return;
}
string name;
size_t id;
Score score;
cout << "輸入學號: ";
cin >> id;
cout << "輸入姓名: ";
cin >> name;
cout << "輸入成績: ";
cin >> score;
Student stu = {name, id, score};
if (data_.Insert(pos - 1, stu))
cout << "插入數據成功!" << endl;
else
cout << "插入數據失敗!" << endl;
}
void StudentManager::DeleteStudent()
{
if (data_.IsEmpty())
{
cout << "沒有學生信息可以刪除!" << endl;
return;
}
ShowAllStudentInfo();
size_t index;
cout << "輸入學生編號: ";
cin >> index;
if (index < 1 || index > data_.Length())
{
cout << "編號有誤!" << endl;
return;
}
if (data_.Delete(index - 1))
cout << "刪除成功!" << endl;
else
cout << "刪除失敗!" << endl;
}
void StudentManager::SearchByName()
{
string name;
cout << "輸入學生姓名:";
cin >> name;
bool is_found = false; // 標記是否找到信息
for (size_t i = 0; i < data_.Length(); ++i)
if (data_[i].name == name)
{
cout << "姓名: " << name << "\t學號: " << data_[i].id << "\t成績: " << data_[i].score << endl;
is_found = true;
}
if (!is_found)
cout << "查無此人: " << name << endl;
}
void StudentManager::ShowListStatus()
{
cout << "學生數量: " << data_.Length() << endl;
}
void StudentManager::ShowMenu()
{
ClearScreen();
cout << endl;
cout << "=================" << endl;
ShowListStatus();
cout << "=================" << endl;
cout << "1.讀取文件(前插法)" << endl;
cout << "2.讀取文件(后插法)" << endl;
cout << "3.全部學生信息" << endl;
cout << "4.查找學生信息" << endl;
cout << "5.刪除學生信息" << endl;
cout << "6.插入學生信息" << endl;
cout << "7.退出程序" << endl;
cout << "=================" << endl;
cout << endl;
int choose;
cout << "輸入序號: ";
cin >> choose;
if(cin.fail())
{
cin.clear();
cin.ignore(100, '\n'); // 清除 cin 的fail的狀態, 否則無限循環
return;
}
switch (choose)
{
case 1:
ReadDataWithHeadInsert();
break;
case 2:
ReadDataWithTailInsert();
break;
case 3:
ClearScreen();
ShowAllStudentInfo();
break;
case 4:
SearchByName();
break;
case 5:
ClearScreen();
DeleteStudent();
break;
case 6:
InsertStudent();
break;
case 7:
exit(0);
default:
cout << "序號有誤!" << endl;
}
// 故意暫停, 回車繼續
cout << "\n按 Enter 繼續...";
cin.get(); // 讀取緩沖區的換行符
cin.get(); // 等待用戶按 Enter
}
void StudentManager::Run()
{
while (true)
{
ShowMenu();
}
}
// -------------- End of StudentManager -------------
int main(int argc, char const *argv[])
{
StudentManager manager;
manager.Run();
}