链表是线性表的链式存储方式
给每个元素加一个指针指向下一个元素的存储位置
数据域存数据,指针域存指针
在C++中表示链表,需要有一个表示链表中单个结点的数据类型。从结点的属性来看,这样的一个数据类型不但需要存储的数据结构,还要有一个指向另一个相同类型结点的指针。
这里假设每个结点将存储一个类型为int的数据项,则可以声明以下类型来存放结点:
struct ListNode{
int date;
ListNode *next;
}
在以上代码中,ListNode是要存储在链表中的结点的类型,结构成员value是结点的数据部分,而另一个结构成员next则被声明为ListNode的指针,它是指向下一个结点的后续指针。
ListNode 结构有一个有趣的属性,它包含一个指向相同类型数据结构的指针,因此可以说是一个包含对自身引用的类型。像这样的类型称为自引用数据类型或自引用数据结构。
当我们声明了一个数据类型来表示结点之后,可以用这个数据类型来定义一个初始为空的链表,方法是定义一个用作链表头的指针并将其初始化为null:
ListNode *head = NULL;
现在可以创建一个链表,其中包含一个结点,将这个节点存储值为12.5:head = new ListNode; //分配新结点
head->value = 12; //存储值
head->next = NULL; //表示链表的结尾
因为ListNode的数据类型包含了指向于先一个结点的指针,所以在没有下一个结点的情况之下,要使其等于null。
如果我们创建一个新结点,在其中存储一个值为13.5的值,将其作为链表中的第二个结点,代码如下所示:
ListNode *secondPtr = new ListNode;
secondPtr->value = 13;
secondPtr->next = nullptr; //第二个结点是链表的结尾
head->next = secondPtr; //第一个结点指向第二个
通过 head->next = secondPtr; 语句将链表头的后继指针改为指向第二个结点;指针 secondPtr->next 设置为 nullptr,可以使第二个结点成为链表的结尾。
链表的函数封装
1.链表初始化:首先判断内存分配是否成功,再对头节点的数据域赋值,指针指向空
//初始化一个空链表
bool initLinkList(LinkList* &L) {
L = new LinkList;
if (!L) {
return false;
}
L->date = 666;
L->next = NULL;
return true;
}
2.打印链表:此处打印链表:定义一个链表f为L头结点指向的地方,打印头结点(一般情况不进行头结点遍历),然后在判断f(L指向的地方)是否存在,之后一次类推进行信息打印
bool LinkPrint(LinkList* &L) {
LinkList* f = NULL;
f = L->next;
cout << L->date << "\t";
while (f) {
cout << f->date << "\t";
f = f->next;
}
cout << endl;
return 0;
}
3.前插法 这里的前插法其实是在头结点和第1个节点之间插入,因为头结点之前没有空间分配
此处先进行非法判断,再让头结点指向插入的元素,插入的元素指向原本头结点指向的地方
//前插法 插入的地方其实是头结点之后
bool LinkInsert_front(LinkList*& L, LinkNode* node) {
if (!L || !node) {
return false;
}
//L(头节点)指向node, node 指向L指向的地方
node->next = L->next;
L->next = node;
return true;
}
4.尾插法
尾插法接收在链表的尾部添加元素此处先进行非法判断,再定义一个last,使用循环到达链表的尾部,最后进行元素添加
//尾插法
bool LinkInsert_back(LinkList*& L, LinkNode* node) {
if (!L || !node)
{
cout << "出错了" << endl;
return false;
}
LinkNode* last;
last = L; //注意这里应该是用指向L,
//而不是指向L->next,因为当一开始没有节点的时候
//如果用L->next,那么后面就超出范围了
while (last->next) {
last = last->next;
}
last->next = node;
node->next = NULL;
return true;
}
5.指定位置插入元素
此处是通过while进行判断找到需要插入的节点位置,再进行插入
//指定位置插入元素
bool LinkInsert(LinkList* &L,int i,int e) {
if (!L) {
cout << "出错了" << endl;
return false;
}
LinkNode* f;
LinkNode* s;
int j = 0;
f = L;
while (f && (i - 1) > j) {
f = f->next;
j++;
}
if (!f || (i - 1 < j)) {
cout << "插入失败" << endl;
return false;
}
s = new LinkNode;
s->date = e;
s->next = f->next;
f->next = s;
return true;
}
6.获取指定位置的元素:找到位置的机制同上
bool Link_GetElem(LinkList*& L, int i, int& e) {
//根据指定位置获取元素
if (!L || !(L->next))
return false;
int index = 1;
LinkList* p;
p = L->next;
while (p && index < i) {
p = p->next;
index++;
}
if (!p || index > i)
return false;
e = p->date;
return true;
}
7.返回元素位置
bool Link_FindElem(LinkList*& L, int e, int& index) {
//单链表根据值查询元素所在的位置
if (!L || !(L->next))
return false;
int dex = 1;
LinkList* p = L->next;
while (p && (e != p->date)) {
p = p->next;
dex++;
}
if (!p)
return false;
index = dex;
return true;
}
8.删除指定位置的元素
bool LinkDelete_index(LinkList*& L, int i) {
if (!L || !(L->next))
return false;
LinkList* p = L;
int index = 0;
while (p && index < i - 1) {
p = p->next;
index++;
}
if (!p || index > i - 1) {
return false;
}
LinkList* q = p->next;
p->next = q->next;
//delete q;释放空间
return true;
}
9.销毁链表
bool LinkDestroy(LinkList*& L) {
LinkList* p = L;
cout << "列表嘎啦" << endl;
while (p) {
L = L->next;
delete p;
p = L;
}
return true;
}