C++:从0开始学习链表
在C++中有一种很重要的数据容器:链表.
链表内部不是连续的存储单元,而是若干非连续的数据单元,通过记录每个数据的地址,将其连接形成一种链式结构
1.链表基本概念:
(1)包含值域和指针域:
值域:该节点存的数据的值
指针域:指向下一个节点的地址
(2)一般用结构体创建
struct ListNode {int val;//值域ListNode* next;//指针域,指向下一个节点的地址//如果不写构造函数,系统默认提供一个无参构造ListNode(int x) {//构造函数用于初始化链表val = x;next = nullptr;}
};
2.创建链表:
一般使用new关键字在堆区创建,也可以在栈区创建,但是不推荐使用
返回值是一个地址,要用指针变量接收
int main() {ListNode* node2 = new ListNode(2);//在堆区创建,初始化值域为2cout << node2->val << endl;//2return 0;
}
3.链表的头节点和尾节点
初始化为空指针,空指针不能访问成员变量,使用前需要判断是否为空
ListNode* head = nullptr;//记录头节点
ListNode* tail = nullptr;//记录尾节点//使用时
if (head != nullptr) { }
下面我们来创建一个链表并输入数据
输入n个数,按顺序组成链表
int n;
cin >> n;
for (int i = 0;i < n;i++) {int x;cin >> x;ListNode* node = new ListNode(x);if (head == nullptr) {//当前链表中没有节点head = node;tail = node;}else {//当前链表中有节点tail->next = node;//将原尾节点的指针域更新为指向nodetail = node;//将尾节点更新为node}
}
4.遍历链表
(1)链表只能从前往后遍历,不能反向遍历
(2)链表的头节点不能改变,所以需要用一个p接收头节点,并使用p进行遍历
ListNode* p = head;
while (p != nullptr) {//最后一个节点指向空,遍历到最后一个节点后,循环结束cout << p->val << " ";p = p->next;
}
链表的概念和创建我们已经了解了,接下来我们来学学如何使用链表
链表操作:
1.查询节点
(1)查询值等于val的节点
遍历链表,加入if条件判断
if判断可以将其输出,也可以直接退出循环
如果退出循环此时指针指向的就是等于val的节点,我们可以对其做一些处理,如修改,删除等
int main(){ int val = 3;//查询值等于3的节点ListNode* l = head;while (l != nullptr) {if (l->val == val) {cout << l->val<< endl;}l = l->next;//将l指向下一个节点}return 0;
}
(2)查询位置为n的节点
int main(){ int n = 4;ListNode* l = head;int count = 0;while (l != nullptr&&count < n-1) {count++;l = l->next;}//此时l指向位置为n的元素,然后我们将其输出cout << l->val;return 0;
}
2.插入节点
在链表中插入一个节点,我们需要知道该节点前一个节点的地址
(1)在尾部插入
//创建要插入的节点
ListNode* node1 = new ListNode(6);
//将原尾节点的next指向该节点
tail->next = node1;
//将尾节点赋值为该节点
tail = node1;
(2)在头部插入
ListNode* node2 = new ListNode(3);
//将要插入的节点挂到链表上
node2->next = head;
//修改头部指针
head = node2;
(3)在链表中间部分特定元素前插入节点
int val = 3;//在元素3之前插入节点
ListNode* node3 = new ListNode(9);
ListNode* l = head;
while (l->next) {//让l指向节点的下一个节点的值为3,这样就直接在l后插入元素就行if (l->next->val == val) {//将新节点插入到l指向节点后node3->next = l->next;l->next = node3;break;}l = l->next;
}
(4)通过下标插入特定点
通过一个count变量控制p1节点遍历完的位置(也可以理解成通过下标查询元素)
int index = 2;//在索引为2的元素前插入
ListNode* node4 = new ListNode(0);//创建一个新节点
int cnt = 0;//控制p1的位置
ListNode* p1 = head;
while (p1 && cnt < index - 1) {//让p1遍历完在索引前一位p1 = p1->next;cnt++;
}
node4->next = p1->next;
p1->next = node4;
3.删除节点
在链表中删除一个节点,我们需要知道该节点前一个节点的地址,该节点的地址以及该节点后一个节点的地址
(1)通过val值查找需要删除的节点
int target = 3;//删除val为3的节点
ListNode* p1 = head;
//找到要删除节点之前的节点
while (p1->next) {if (p1->next->val == target) {break;//退出循环,使p1指向需要删除节点的前一个节点}
p1 = p1->next;
}
//进行节点删除
ListNode* p2 = p1->next->next;//指向要删除的元素的下一个节点
ListNode* cur = p1->next;//指向要删除的节点
p1->next = p2;//将前节点直接指向后节点
delete cur;//删除该元素节点
cur = nullptr;//将指针置为空,避免野指针问题
(2)删除头部节点
先判断一个节点是否为头部节点,如果是,直接将头节点指向下一个节点
int index = 0;//头节点
if (index == 0) {//判断是否是头节点ListNode* p3 = head;//指向头节点head = head->next;//将头节点指向下一个节点delete p3;//删除原头节点p3 = nullptr;//置空
}
如果每次删除节点,我们都需要判断是否是头节点,这样会使代码变得复杂,所以我们使用一个通用的方法来删除节点
虚拟头节点:(使用该方法,无需判断是否是头节点)
创建一个虚拟头节点,将其next指向头节点,使头节点变为链表中元素,然后使用方法(1)删除,最后将头节点重新指向原头节点,并删除虚拟头节点
//创建一个虚拟头节点
ListNode* vtnode = new ListNode(0);
//将其next指向头节点
vtnode->next = head;
ListNode* p = vtnode;//此时虚拟头节点是第一个节点int target = 5;//需要删除的元素
while (p->next) {if (p->next->val == target) {break;}p = p->next;
}
//删除节点
ListNode* p1 = p->next->next;
ListNode* p2 = p->next;
p->next = p1;
delete p2;
p2 = nullptr;//由于之后需要遍历数组,所以将头节点重新定义为原头节点
head = vtnode->next;
//删除创建的虚拟头节点
delete vtnode;
vtnode = nullptr;
这种自定义结构体类型的链表的概念和基础操作我们就学完了,下一篇会带大家一起做一些关于链表的算法题