犹豫了很久,还是决定在这个大佬云集的地方写一些自己的东西。本人只是非计算机专业的一名本科生,在学习程序设计课程和实际应用的过程中,有很多的问题都是靠本社区的分享得以解决的。饮水思源,我也想分享一些自己的感悟,如果能帮助到其他人最好了。但限于个人水平,分享的内容难免有谬误,望谅解和指正。
PS:本人文章中的图片和代码部分来自网络或老师课件。故本人文章仅供学习参考(估计也没有别的用途)
关于内容的形式,我有考虑过:教程(能力不足),具体题目解答(局限性大),学习笔记(懒癌晚期,坚持不下去)。最终决定分享一些问题的通用解决方案和思路,如本文 “用C++ 实现单链表”,其实这也是我第一个面临的需要自行解决的问题。文体上力求简洁明了,突出重点。下面正式开篇。
单链表是一种线性表的链接存储结构。具体的数据结构知识这里就不作赘述了。
关于数据结构的构建问题,我强烈建议画图理解,明确我们需要的各部分结构,才能明确方向和方法。
根据这个结构图,我们可以看出要定义的两个部分“结点Node”和以结点为基本要素构成的“链表LinkList”,其他的函数方法也是基于这两者的定义,故要先巩固基本。这里大家初学时一定要好好明确链表中结点的指针域“next”是指向下一个结点(后继)的。并形成对链表结构根本的操作概念——“从头到尾”和“变指针域”。
结点结构体的声明与定义如下,如果有不懂模板类“template”的小伙伴,建议先去了解一下,很简单也很实用的,尤其是在数据结构这种通用性需求高的领域。
template <class DataType> // 结点结构体 struct Node { DataType data; //数据域 Node<DataType> *next;//指针域 };
链表类的大体声明如下。根据定义,每一个链表都要有一个头结点“first” 。其他的函数方法,可以按需设计。
template <class DataType> class LinkList { public: LinkList(); //构造函数 LinkList(DataType a[], int n);//含参构造函数 ~LinkList();//析构函数 int Length();//计算链表产长度 DataType Get(int i);//按位查找 int Locate(DataType x);//按值查找 void Insert(int i, DataType x);//插入值 DataType Delete(int i);//删除元素 void PrintList();//遍历链表 private: Node<DataType> *first;//头指针 };
链表默认的构造函数,给你个头,剩下的自行添加。这里要注意避免野指针哦!指针一定要有所指向,如果暂时没有指向,就要将指针赋值为NULL。
就是这样的空表一个。
template <class DataType> LinkList<DataType>::LinkList() { cout << "链表的默认构造函数" << endl; first = NULL;//防止野指针 }
链表的含参(顺序数组类型输入)构造函数。这里有两种初始化方法,头插法(链表顺序和数组储存顺序相反)和尾插法(链表顺序和数组储存顺序相同),具体的特点这里不做赘述,给出定义。
template <class DataType> LinkList<DataType>::LinkList(DataType a[], int n) { //头插法,链表顺序和数组储存顺序相反 cout << "链表的有参构造函数:头插法" << endl; first = new Node<DataType>; first->next = NULL; Node<DataType> *s; for (int i = 0; i < n; i++) { s = new Node<DataType>; s->data = a[i]; s->next = first->next; first->next = s; } cout << "链表的有参构造函数:尾插法" << endl; //尾插法,链表顺序和数组储存顺序相同 first = new Node<DataType>; //生成头结点 Node<DataType> *s, *r = first; //尾指针初始化 for (int i = 0; i < n; i++) { s = new Node<DataType>; s->data = a[i]; r->next = s; r = s; } r->next = NULL; }
析构函数,对于链表,仅需要遍历链表并释放指针即可。尽管C++目前的规范里会自动释放工作栈,也就是自动添加析构函数,但是还是建议大家有这个良好的习惯。
PS:保证链表未处理的部分不断开
template <class DataType> LinkList<DataType>::~LinkList() { cout << "链表的析构函数" << endl; Node<DataType> *q; while (first != NULL) { q = first; first = first->next; delete q; } }
计算链表的结点数,就是遍历一遍计个数。其实完全可以在构造链表和插入删除操作中嵌入这一概念,并将length作为链表的一个成员变量,以优化算法,看具体需要吧。
template <class DataType> int LinkList<DataType>::Length() {//计算长度 Node<DataType> *p = first->next; int count = 0; while (p != NULL) { p = p->next; count++; } return count; //退出循环表明查找失败 }
按位查找。查找第 i 位的数据域,遍历方法。
template <class DataType> DataType LinkList<DataType>::Get(int i) {//按位查找 Node<DataType> *p = first->next; int count = 1; while (p != NULL && count < i){ p = p->next; count++; } if (p==NULL) { cerr << "查找位置非法" << endl; exit(1); } else return p->data; //注意count的初始化和返回值之间的关系 }
按值查找。找到第一个出现 值 x 的位置,返回序号。
template <class DataType> int LinkList<DataType>::Locate(DataType x) {//按值查找 Node<DataType> *p = first->next; int count = 1; while (p != NULL) { if (p->data == x) return count; //查找成功,返回序号 p = p->next; count++; } return 0; //退出循环表明查找失败 }
按位插值。在第i 位插入新的 以 x 为数据的结点。遍历方法,在插值的过程中涉及到结点前驱和后继指针域的改变(链表的数据结构知识)。其中的异常处理也很关键哦!
template <class DataType> void LinkList<DataType>::Insert(int i, DataType x) {//按位置插入值 Node<DataType> *s, *p = first; int count = 0; //工作指针p应指向头结点 while (p != NULL && count < i - 1) { //查找第 i–1 个结点 p = p->next; count++; } if (p == NULL) { //没有找到第 i–1 个结点 cerr << "插入位置异常" << endl; return; } else { s = new Node<DataType>; s->data = x; //申请一个结点s s->next = p->next; p->next = s; //结点s插入结点p之后 } }
PS:注意边界问题,插在表头、表尾的情况。(表头无前驱,表尾无后继)
按位删除值。与按位插入值的基本逻辑是一样的。注意前驱后继的改变。
template <class DataType> DataType LinkList<DataType>::Delete(int i) {//按位删除值 Node<DataType> *q, *p = first; int count = 0; DataType x; while (p != NULL && count < i - 1) { p = p->next; count++; } if (p == NULL || p->next == NULL) { cerr << "删除位置异常" << endl; exit(1); } else { q = p->next; x = q->data; p->next = q->next; delete q; return x; } }
链表的遍历,按序输出链表的内容。PS:这里默认的跳过头节点,因为将头节点数据域定义为空,貌似是一种习惯来的。
从头节点的下一个开始。
template <class DataType> void LinkList<DataType>::PrintList() {//遍历链表 cout << "LinkList elements:" << endl; Node<DataType> *p = first->next; int i = 1; while (p != NULL) { cout << "No." << i << " " << p->data << endl; p = p->next; i++; } }
至此,单链表的大部分基本声明与定义就结束了。还有很多特殊的链表,如“循环链表”、“约瑟夫环”、“双链表”,也是基于单链表的基本思路去实现的。
下附完整代码,望诸君共勉。
#ifndef LINKLIST_H
#define LINKLIST_H
using namespace std;
template <class DataType> // 结点结构体
struct Node {
DataType data; //数据域
Node<DataType> *next;//指针域
};
template <class DataType>
class LinkList {
public:
LinkList(); //构造函数
LinkList(DataType a[], int n);//含参构造函数
~LinkList();//析构函数
int Length();//计算链表产长度
DataType Get(int i);//按位查找
int Locate(DataType x);//按值查找
void Insert(int i, DataType x);//插入值
DataType Delete(int i);//删除元素
void PrintList();//遍历链表
private:
Node<DataType> *first;//头指针
};
template <class DataType>
LinkList<DataType>::LinkList() {
cout << "链表的默认构造函数" << endl;
first = NULL;//防止野指针
}
template <class DataType>
LinkList<DataType>::LinkList(DataType a[], int n) {
//头插法,链表顺序和数组储存顺序相反
cout << "链表的有参构造函数:头插法" << endl;
first = new Node<DataType>; first->next = NULL;
Node<DataType> *s;
for (int i = 0; i < n; i++) {
s = new Node<DataType>; s->data = a[i];
s->next = first->next;
first->next = s;
}
cout << "链表的有参构造函数:尾插法" << endl;
//尾插法,链表顺序和数组储存顺序相同
first = new Node<DataType>; //生成头结点
Node<DataType> *s, *r = first; //尾指针初始化
for (int i = 0; i < n; i++) {
s = new Node<DataType>; s->data = a[i];
r->next = s; r = s;
}
r->next = NULL;
}
template <class DataType>
LinkList<DataType>::~LinkList() {
cout << "链表的析构函数" << endl;
Node<DataType> *q;
while (first != NULL) {
q = first;
first = first->next;
delete q;
}
}
template <class DataType>
int LinkList<DataType>::Length() {//计算长度
Node<DataType> *p = first->next; int count = 0;
while (p != NULL) {
p = p->next;
count++;
}
return count; //退出循环表明查找失败
}
template <class DataType>
DataType LinkList<DataType>::Get(int i) {//按位查找
Node<DataType> *p = first->next; int count = 1;
while (p != NULL && count < i){
p = p->next; count++;
}
if (p==NULL) {
cerr << "查找位置非法" << endl;
exit(1);
} else return p->data;
//注意count的初始化和返回值之间的关系
}
template <class DataType>
int LinkList<DataType>::Locate(DataType x) {//按值查找
Node<DataType> *p = first->next; int count = 1;
while (p != NULL) {
if (p->data == x) return count; //查找成功,返回序号
p = p->next;
count++;
}
return 0; //退出循环表明查找失败
}
template <class DataType>
void LinkList<DataType>::Insert(int i, DataType x) {//按位置插入值
Node<DataType> *s, *p = first; int count = 0; //工作指针p应指向头结点
while (p != NULL && count < i - 1) { //查找第 i–1 个结点
p = p->next;
count++;
}
if (p == NULL) { //没有找到第 i–1 个结点
cerr << "插入位置异常" << endl;
return;
} else {
s = new Node<DataType>; s->data = x; //申请一个结点s
s->next = p->next; p->next = s; //结点s插入结点p之后
}
}
template <class DataType>
DataType LinkList<DataType>::Delete(int i) {//按位删除值
Node<DataType> *q, *p = first; int count = 0; DataType x;
while (p != NULL && count < i - 1) {
p = p->next;
count++;
}
if (p == NULL || p->next == NULL) {
cerr << "删除位置异常" << endl;
exit(1);
} else {
q = p->next; x = q->data;
p->next = q->next;
delete q; return x;
}
}
template <class DataType>
void LinkList<DataType>::PrintList() {//遍历链表
cout << "LinkList elements:" << endl;
Node<DataType> *p = first->next; int i = 1;
while (p != NULL) {
cout << "No." << i << " " << p->data << endl;
p = p->next; i++;
}
}
#endif // LINKLIST_H