2.链表
实验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();
}