链表的基础操作III
时间限制:1.000S 空间限制:128MB
题目描述
请编写一个程序,实现以下链表操作:构建一个单向链表,链表中包含一组整数数据。
1. 实现在链表的第 n 个位置插入一个元素,输出整个链表的所有元素。
2. 实现删除链表的第 m 个位置的元素,输出整个链表的所有元素。
要求:
1. 使用自定义的链表数据结构。
2. 提供一个 linkedList 类来管理链表,包含构建链表、插入元素、删除元素和输出链表元素的方法。
3. 在 main 函数中,创建一个包含一组整数数据的链表,然后根据输入的 n 和 m,调用链表的方法插入和删除元素,并输出整个链表的所有元素。
输入描述
每次输出只有一组测试数据。
每组的第一行包含一个整数 k,表示需要构建的链表的长度。
第二行包含 k 个整数,表示链表中的元素。
第三行包含一个整数 S,表示后续会有 S 行输入,每行两个整数,第一个整数为 n,第二个整数为 x ,代表在链表的第 n 个位置插入 x。
S 行输入...
在 S 行输入后,后续会输入一个整数 L,表示后续会有 L 行输入,每行一个整数 m,代表删除链表中的第 m 个元素。
L 行输入...
输出描述
包含多组输出。
每组第一行输出构建的链表,链表元素中用空格隔开,最后一个元素后没有空格。
然后是 S 行输出,每次插入一个元素之后都将链表输出一次,元素之间用空格隔开,最后一个元素后没有空格;
如果插入位置不合法,则输出“Insertion position is invalid.”。
然后是 L 行输出,每次删除一个元素之后都将链表输出一次,元素之间用空格隔开,最后一个元素后没有空格;
如果删除位置不合法,则输出“Deletion position is invalid.”。
输入示例
5
1 2 3 4 5
3
4 3
3 4
9 8
2
1
0
输出示例
1 2 3 3 4 5
1 2 4 3 3 4 5
Insertion position is invalid.
2 4 3 3 4 5
Deletion position is invalid.
之前我们也提到过,往数组的中间插入一个元素,后续所有元素都需要往后挪动一位,而链表则不必这么麻烦,那往链表的中间添加或者删除一个节点具体应该怎么做呢?
插入链表的过程
我们可以假设这样一个场景:在传递情报过程中,A的下线是B, 也就是A -> next = B
, 现在我们要引入一个C充当A和B之间的中间人,A的下线是C, C的下线是B,我们可以直接将A的next
指向C,即A -> next = C
, 然后将C的next
指向B, 但是B无法直接表示,之前是用A -> next
来表示B的,现在A -> next
已经指向C了,所以在操作之前,我们需要实现定义一个变量保存B,假设为tmp
, 然后将C的next
指针指向B即可。
在链表中,具体插入的过程如下:
- 找到要插入的位置的前一个节点,将之命名为
cur
, 要插入的位置的下一个节点,将之命名为tmp
, 它们之间的关系是cur -> next = tmp
- 创建一个新的链表
newNode
, 将cur
的next指针
指向newNode
, 即cur -> next = nowNode
- 将
newNode
的next指针
指向tmp
, 即newNode -> next = tmp
这样就完成了链表节点的插入过程。转换成代码如下:
// 创建了一个新的 ListNode 结构的节点,值为x, 并将其地址赋给指针 newNode
ListNode *newNode = new ListNode(x);
// 创建了一个名为 tmp 的指针,临时存储当前节点 cur 的下一个节点的地址。
ListNode *tmp = cur ->next;
// 将当前节点 cur 的 next 指针更新为指向新节点 newNode,将新节点插入到当前节点后面。
cur->next = newNode;
// 将新节点 newNode 的 next 指针设置为指向之前临时存储的节点 tmp
newNode->next = tmp;
删除链表的过程
删除链表的过程则比较简单,只需要找到删除节点的前一个节点cur
, 并将其next
指针设置为指向当前节点的下下个节点,从而跳过了下一个节点,实现了节点的删除操作。
// cur->next 表示当前节点 cur 的下一个节点
// cur->next->next 表示要删除的节点的下一个节点
// 当前节点 cur 的 next 指针不再指向要删除的节点,而是指向了要删除节点的下一个节点
cur->next = cur->next->next;
打印链表
在函数内部,定义了一个名为 cur
的指针,它初始化为指向 head
,即链表的头节点。
什么时候链表迭代到最后一个节点呢?检查当前节点 cur
的下一个节点是否存在(不为 NULL), 当前节点的下一个节点为NULL
时说明下一个节点为空节点,即链表的末尾。
while(cur -> next != NULL) {
}
在循环体内,打印当前节点 cur
的下一个节点(cur->next
)的值(val
)。接下来,将 cur
更新为指向链表中的下一个节点,以便在下一次循环中打印下一个节点的值。
while (cur->next != NULL) {
cout << cur->next->val << " ";
cur = cur -> next;
}
循环体结束时,表示已经遍历完了整个链表。最后打印一个换行符,用于分隔不同的输出行。
// 打印链表
void printLinklist(ListNode* head) {
ListNode* cur = head;
while (cur->next != NULL) {
cout << cur->next->val << " ";
cur = cur -> next;
}
cout << endl;
}
代码编写
我们照例,先把代码的基础结构以及定义链表的结构体写出来。
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
int main() {
}
首先,需要接收整数k, 表示需要构建链表的长度
int main() {
int k, val; // k表示链表长度,val表示构建链表时输入的值
cin >> k;
}
然后我们需要构建一个长度为k的链表,在构建链表之前,可以先创建一个虚拟头节点以及初始化一个指针cur
指向虚拟头节点,方便后续节点的接入。
ListNode* dummyHead = new ListNode(0); // 创建了一个虚拟头结点
ListNode* cur = dummyHead; // 定义一个指向当前节点的指针 cur,初始指向虚拟头结点
for (int i = 0; i < k; i++) { // 或者使用while(n--)
cin >> val;
ListNode *newNode = new ListNode(val); // 构造一个新的节点
cur -> next = newNode; // 将新节点接入链表
cur = cur -> next; // cur 指向下一个节点
}
之后,需要接收一个整数s
表示s
行输入,每行两个整数,第一个整数为 n,第二个整数为 x ,代表在链表的第 n 个位置插入 x。
int s, n, x; // s表示s行输入,n代表插入位置,x代表插入元素的值
cin >> s; // 输入s
while (s--) {
cin >> n >> x; // 输入n和x
}
题目中要求,插入位置不合法时,输出"Insertion position is invalid."
什么时候才会插入位置不合法呢,当插入的位置n
是一个小于等于0的数或者n大于链表的长度时,插入位置不合法。
⚠️ 需要注意,插入后链表的长度会变化,所以我们可以提前定义一个变量
listLen
指代链表的长度
int listLen = k; // 定义链表的长度
if (n <= 0 || n > listLen) { // 当要插入的位置非法时
cout << "Insertion position is invalid." << endl; // 输出错误提示
continue;
}
如果不满足上面的if
语句,说明插入的位置合法,后续执行的操作是完成插入以及打印链表。
插入链表节点的过程在前面已经描述过,只需找到需要插入位置的前一个节点即可, 也就是第n-1
个节点,从虚拟头节点开始遍历,需要走n-1
步
// 指针重新指向虚拟头结点,准备用cur遍历链表
cur = dummyHead;
// 寻找添加节点的位置,i从1开始,
for (int i = 1; i < n; i++) {
cur = cur->next;
}
插入节点的代码直接拿来使用即可,不要忘记将链表长度加1
// 插入节点
ListNode *newNode = new ListNode(x); // 构造一个新的节点
ListNode *tmp = cur ->next;
cur->next = newNode;
newNode->next = tmp;
// 链表长度加1
listLen++;
接着完成打印链表即可
// 打印链表
printLinklist(dummyHead);
链表节点删除的前置步骤类型,需要接收一个整数 L,表示后续会有 L 行输入,每行一个整数 m,代表删除链表中的第 m 个元素。
int l,m;
cin >> l;
while (l--) {
cin >> m;
}
判断是否是非法输入
if (m <= 0 || m > listLen) {
cout << "Deletion position is invalid." << endl;
continue;
}
找到要删除节点的前一个节点
// 指针重新指向虚拟头结点,准备用cur遍历链表
cur = dummyHead;
//开始寻找删除节点的位置,i从1开始,因为节点计数从1开始
for (int i = 1; i < m; i++) cur = cur->next;
// 删除节点
cur->next = cur->next->next;
listLen--; // 链表长度减一
// 如果删除节点后链表为空则不打印
if (listLen != 0) printLinklist(dummyHead);
c++代码实现:
#include<iostream>
using namespace std;
typedef struct ListNode{
int data;
ListNode *next;
ListNode(int x):data(x),next(nullptr){}
}*L;
void printLinkList(ListNode *dummyHead){
ListNode *cur=dummyHead;
while(cur->next!=NULL){
cout<<cur->next->data<<" ";
cur=cur->next;
}
cout<<endl;
}
int main(){
int k;
L dummyHead=new ListNode(0);//创建一个虚拟头节点
cin>>k;
int listlen=k;//记录链表的长度
L cur=dummyHead;//定义一个指向当前节点的指针cur,初始指向虚拟头节点
int val;
for(int i=0;i<k;i++){
cin>>val;
L newNode=new ListNode(val);
cur->next=newNode;
cur=cur->next;
}
int s,n,x;
cin>>s;
while(s--){
cin>>n>>x;
if(n<=0||n>listlen){
cout << "Insertion position is invalid." << endl;
continue;
}
//指针重新指向虚拟头节点。准备用cur遍历链表
cur=dummyHead;
//
for(int i=1;i<n;i++)
cur=cur->next;
L newNode=new ListNode(x);
L tmp=cur->next;
cur->next=newNode;
newNode->next=tmp;
listlen++;
printLinkList(dummyHead);
}
int l,m;
cin>>l;
while(l--){
cin>>m;
if(m<=0||m>listlen){
cout << "Deletion position is invalid." << endl;
continue;
}
cur=dummyHead;
for(int i=1;i<m;i++)
cur=cur->next;
cur->next=cur->next->next;
listlen--;
if(listlen!=0){
printLinkList(dummyHead);
}
}
}