1. 设计思路
本项目的实质是完成对考生信息的建立、查找、插入、修改、删除等功能,可以首先定义项目的数据结构,然后将每个功能写成一个函数来完成对数据的操作,最后完成主函数以验证各个函数功能并得出运行结果。
2. 数据结构
本项目的数据是一组考生信息,每条考生信息由准考证号、姓名、性别、年龄、报考类别等信息组成,这组考生信息具有相同特性,属于同一数据对象,相邻数据元素之间存在序偶关系。由此可以看出,这些数据也具有线性表中数据元素的性质,所以该系统的数据可以采用线性表来存储。
从上一节的例子中可见,线性表的顺序存储结构的特点是逻辑关系相邻的两个元素在物理位置上也相邻,因此可以随机存储表中任一元素,它的存储位置可用一个简单、直观的公式来表示。然而,从另一个方面来看,这个特点也铸成了这种存储结构的弱点:在做插入或删除操作时,需要移动大量元素。
为克服这一缺点,我们引入另一种存储形式――链式存储。链式存储是线性表的另一种表示方法,由于它不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构的弱点,但同时也失去了顺序表可随机存取的特点。
链式存储的优点是插入或删除元素时很方便,使用灵活。缺点是存储密度小,存储空间利用率低。事实上,链表插入、删除运算的快捷是以空间代价来换取时间。
顺序表适宜于做查找这样的静态操作;链表宜于做插入、删除这样的动态操作。
- 若线性表的长度变化不大,且其主要操作是查找,则采用顺序表;
- 若线性表的长度变化较大,且其主要操作是插入、删除操作,则采用链表;
本项目主要进行插入、删除、修改等操作,所以采用链式存储结构比较适合。用结构体类型定义每个考生信息,故该单链表中的每个结点的结构可描述为:
// 单链表中单个结点的完整数据结构定义
typedef struct LNodeTag
{
Student stu; // 值域
LNodeTag *next; // 指针域
}LNode;
假设 L
为 LNode*
型变量,则 L
为单链表的头指针,它指向表中的第一个结点。若 L
为空 L=NULL
,则表示的线性表为空表,其长度 n
为 0。
有时我们在单链表的第一个结点之前设置一个结点,称之为头结点,头结点的数据域可以不存储任何信息,也可以存储线性表的长度等类附加信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置),如图 2.7a 所示,此时单链表的头指针指向头结点。
若线性表为空表,则头结点的指针域为空,如图 2.7b 所示。
根据这里的分析不难发现,链表在新增、删除数据都比较容易,可以在 O(1)
的时间复杂度内完成。但对于查找,不管是按照位置的查找还是按照数值条件的查找,都需要对全部数据进行遍历。这显然就是 O(n)
的时间复杂度。
虽然链表在新增和删除数据上有优势,但仔细思考就会发现,这个优势并不实用。这主要是因为,在新增数据时,通常会伴随一个查找的动作。例如,在第五个结点后,新增一个新的数据结点,那么执行的操作就包含两个步骤:
-
查找第五个结点;
-
再新增一个数据结点;
整体的复杂度就是 O(n) + O(1)
2.1 创建空链表
使用下面代码创建一个仅有头结点的单链表(空单链表)
LinkList::LinkList()
{
head = new LNode; // 产生头结点,并使 head 头指针 指向此头结点
head->next = NULL; // 指针域为空
std::cout << "constructor done" << std::endl;
}
创建结果见下图
2.2 销毁链表
销毁单链表
LinkList::~LinkList()
{
LNode *q;
while (head)
{
q = head->next;
delete head;
head = q;
}
std::cout << "destructor done" << std::endl;
}
销毁结果见下图:
2.3 插入元素
要在第 i
个位置插入一个新的结点, 则需要先找到第 i-1
个结点,如下图
2.4 删除结点
同样要在第 i
个位置删除一个结点, 则需要先找到第 i-1
个结点,如下图
3. 程序清单
project.h
#include <iostream>
#include <string>
// 单链表中结点的值域数据结构定义
typedef struct StudentTag
{
std::string id;
std::string name;
std::string gender;
int age;
// 结构体初始化
StudentTag(std::string i="", std::string n="", std::string g="", int a=0)
{
id = i;
name = n;
gender = g;
age = a;
}
}Student;
// 单链表中单个结点的完整数据结构定义
typedef struct LNode
{
Student stu; // 值域
LNode *next; // 指针域
}LNode;
class LinkList
{
public:
LinkList();
~LinkList();
void init_list();
void clear_list();
bool is_empty();
int print_list();
int get_list_length();
int get_element(int i, Student &data); // 返回单链表中指定序号的结点值
bool search_element(Student data); // 从单链表中查找元素
int insert_element(int i, const Student data);
int delete_element(int i, Student &data);
int update_element(const Student data);
private:
LNode *head;
};
project.cpp
#include "project.h"
LinkList::LinkList()
{
head = new LNode; // 产生头结点,并使 head 指向此头结点
head->next = NULL; // 指针域为空
std::cout << "constructor done" << std::endl;
}
LinkList::~LinkList()
{
LNode *q;
while (head)
{
q = head->next;
delete head;
head = q;
}
std::cout << "destructor done" << std::endl;
}
void LinkList::clear_list()
{
std::cout << "clear_list start" << std::endl;
LNode *q;
LNode *p = head->next; // p 指向第一个结点
while (p) // 循环到表尾
{
q = p->next;
delete p;
p = q;
}
head->next = NULL; // 头结点指针域为空
std::cout << "clear_list done" << std::endl;
}
bool LinkList::is_empty()
{
if(head->next)
{
return false;
}
else
{
return true;
}
// return head->next ? false : true;
}
int LinkList::print_list()
{
LNode *p = head->next;
int length = get_list_length();
if(length == 0)
{
std::cout << "list length is 0" << std::endl;
return 0;
}
while (p)
{
std::cout << "id:" << p->stu.id << " "
<< "name:" << p->stu.name << " "
<< "age:" << p->stu.age << " "
<< "gender:" << p->stu.gender << " "
<< std::endl;
p = p->next;
}
return 0;
}
int LinkList::get_list_length()
{
int length = 0;
LNode *p = head->next;
while (p)
{
length += 1;
p = p->next;
}
return length;
}
int LinkList::get_element(int i, Student &data)
{
int k = 1;
LNode *p = head->next;
while (p)
{
if(k++ == i)
{
data = p->stu;
return 0;
}
p = p->next;
}
return 1;
// 如果函数返回的是获取到的结构体值,则使用 return head->data;
}
bool LinkList::search_element(Student data)
{
LNode *p = head->next;
while (p)
{ // 只要学生的学号相等就认为这两个结构体相等
if(data.id == p->stu.id)
{
return true;
}
p = p->next;
}
return false;
}
int LinkList::insert_element(int i, Student data)
{
int k = 1;
// 找到要插入的第 i 个元素的前一个位置,并让 p 指向该结点
LNode *p = head;
while(p->next && k < i)
{
p = p->next;
k++;
}
// 新建一个结点用于存储要输入的数据 data
LNode *q = new LNode;
q->stu = data;
q->next = NULL;
// 将新数据插入到链表中
q->next = p->next;
p->next = q;
return 0;
}
int LinkList::delete_element(int i, Student &data)
{
int k = 1;
int length = get_list_length();
if(is_empty() || i < 1 || i > length)
{
std::cout << "要删除的元素位置不合理" << std::endl;
return -1;
}
// 找到要删除的第 i 个元素的前一个位置,并让 p 指向该结点
LNode *p = head;
while(p->next && k < i)
{
p = p->next;
k++;
}
if(!(p->next) || k > i)
{
return -1;
}
// 将要删除的数据赋值给 data
/*
data = p->next->stu;
// 删除对应的结点
delete p->next;
p->next = p->next->next;
return 0;
*/
LNode *q;
q = p->next;
data = q->stu;
// 删除对应的结点
delete p->next;
p->next = q->next;
return 0;
}
int LinkList::update_element(Student data)
{
// 找到要更新的元素,假设要修改数据的 data.id 与链表中的 p->stu.id 相等
LNode *p = head;
while(p->next)
{
p = p->next;
if(p->stu.id == data.id)
{
p->stu.name = data.name;
p->stu.age = data.age;
p->stu.gender = data.gender;
}
}
return 0;
}
main.cpp
#include "project.cpp"
int main()
{
LinkList L;
Student sa("001", "A", "male", 18);
L.insert_element(1, sa);
Student sb("002", "B", "male", 19);
L.insert_element(2, sb);
Student sc("003", "C", "male", 20);
L.insert_element(3, sc);
std::cout << "插入元素之后的结果" << std::endl;
L.print_list();
// L.clear_list();
// L.print_list();
bool is_empty = L.is_empty();
std::cout << "is_empty: " << is_empty << std::endl;
std::cout << "length: " << L.get_list_length() << std::endl;
Student s;
L.get_element(3, s);
std::cout << "第 3 个元素为 " << "id:" << s.id << " "
<< "name:" << s.name << " "
<< "age:" << s.age << " "
<< "gender:" << s.gender << " "
<< std::endl;
Student sd("004", "D", "female", 21);
L.insert_element(4, sd);
std::cout << "插入元素之后的结果" << std::endl;
L.print_list();
Student se;
L.delete_element(4, se);
std::cout << "要删除的元素为 " << "id:" << se.id << " "
<< "name:" << se.name << " "
<< "age:" << se.age << " "
<< "gender:" << se.gender << " "
<< std::endl;
std::cout << "删除元素之后的结果" << std::endl;
L.print_list();
Student sf("001", "A", "male", 20);
std::cout << "要更改的元素为 " << "id:" << sf.id << " "
<< "name:" << sf.name << " "
<< "age:" << sf.age << " "
<< "gender:" << sf.gender << " "
<< std::endl;
L.update_element(sf);
std::cout << "更新元素之后的结果" << std::endl;
L.print_list();
}