[c++指针教程]用简单链表练习指针
P1160 队列安排 AKA 指针模板题
链表模板题,本题目下\(list\)的教学已经很多了,我来讲点更加基础的\(\rightarrow\)
基础指针(结构体内)
写在前面:本教程假设读者基本了解指针的定义以及使用,能会用指针写a+b就行
写主席树,平衡树时我们会发现数组套数组的写法十分难受
...
int sub=nd[nd[y].ls].sum-nd[nd[x].ls].sum;
...
ch[old_root][opr]=ch[x][opr^1];
f[ch[old_root][opr]]=old_root;
...
我愿称其为"后 置 性 定 语 从 句"
这种样子写起来很不舒服,实际上,指针才是最适合写这种数据结构的"法宝",
本题是极其简单的链表的模板题,正好方便我们练习用指针处理数据结构.
结构体指针的声明
我们首先需要知道结构体本身的一些性质:
在结构体\(node\)中:
struct node{
int number;
node *pre,*next;
node(int Number=0):number(Number){pre=next=NULL;}
...
};
我们知道前两行是定义了结构体内的\(int\)变量和两个\(node\)类型的指针,所要指向的便是该元素的前一个元素\(pre\)和后一个元素\(next\)(这里就是一个简单的定义,不用管)
第三行则是一个简单的"构造函数",格式如下(同样以结构体\(node\)为例,故函数名为\(node\)):
struct node{
int a,b,c;
node(int A=一个数,int B=另一个数,int C=又一个数):a(A),b(B),c(C){...}
};
意思是把结构体内的变量\(a,b,c\)与函数传入的参数\(A,B,C\)一一对应赋值,再执行函数内的操作
若是以结构体\(node\)定义了结构体变量,但是并没用函数对该变量进行赋值,只是单单定义了该变量,则该变量内对应变量就是我们自己定义的默认值,也就是自己在函数传参时初始化的那许多"一个数"
同时我们需要知道,这个构造函数返回的是一个\(node\)类型的结构体变量,也就是说,这个函数写出来就可以当做变量使了,同时我们知道:
int *a=new int;
这个操作可以使得新定义的指针得到了一个新的\(int\)地址,进一步的,我们去了解这样一种操作:
node *root=new node(a,b,c);
这种操作的结果是让\(root\)指针指向一个内容为\(a,b,c\)的结构体,这个结构体正是刚刚我们用在\(new\)后面的那个函数造出来的,我们可以用这种操作完成新变量的指针连接
对于已经存在的变量,它们的指针连接就更加简单,因为我们知道指针之间可以直接赋值,即:
node *a,*b;
...//中间的一些步骤,使他们分别指向不同的目标
a=b;//这样他们就指向了同一个目标
这样一来我们就能够直接使指针指向我们所希望它指向的目标了.
以上是结构体指针的声明与基本赋值(连接)
结构体内的特殊指针操作
一般结构体变量内元素的调用都需要形如"\(nd.a\)或者\(b\)或者\(c\)"的格式,加入现有一指针\(rt\)指向一个内容为\(a,b,c\)的结构体变量,那么我们在用指针调用时就需要这样写:
rt->a;
对于结构体内的函数(成员函数),需要这样写:
rt->function();
就是由点变成了箭头,不要认为两种形式都可以,必须对应起来,不然会CE
另外,在结构体内写成员函数的时候,我们可以省去变量名等前缀,
很人性化的是,在结构体中,\(this\)表示当前结构体的地址,这样就方便了我们在结构体内捯饬指针的时候无法处理"自己"的问题,
当然,没有人想要在结构体里用\(this\)来访问自己的元素,因为能访问\(this\)的时候可以直接调用该结构体内元素值,\(this\)只是用来让别人指向自己的
来看下结构体内成员函数有什么变化:
//结构体node[a,b,*c]外,结构体变量名nd,指针c指向其他node结构体:
inline void insert(){
nd.a=...;
nd.b=...;
nd.c->a=...;
nd.c->b=...;
}
//结构体内:
struct node{
int a,b;
node *c;
inline void insert(){
a=...;
b=...;
c->a=...;
c->b=...;
}
}nd(或者*nd);
需要注意的是,要达到第一个函数的效果,第二个函数必须搭配着这样写:
//nd是结构体变量的情况下:
nd.insert();
//nd是指针的的情况下:
nd->insert();
这样写意味着我们需要把已有的结构体变量(或指针)作为对象进行操作,也就是我们可以指定函数操作的对象,从而去指定我们用成员函数调用谁体内的元素.
但是我们知道,指针有种说法叫做"空指针",即指向空地址,对空指针指向的地址进行任何形式的访问都会RE,因此我们在进行相关的指针连接的时候需要进行指针的判断
也许这样看着写起来很麻烦,读起来很难受,但是实际上这是很符合中国人语言习惯的写法,这一点写的数据结构多了,就自然会明白,就自然会感叹指针的妙处.
指针的链表应用
链表,用结构体伪代码表示的话,大概需要以下变量:
struct node{
int 链表元素所包含的数据,可以是多个;
int 该元素前一个元素的下标,该元素后一个元素的下标,没有就是0;
或者
node *前一个元素的地址,*后一个元素的地址
...构造函数,成员函数等
};
下面我们就指针写法做详细讨论
链表本身并不复杂,毕竟操作就只有元素之间的插入,删除,牵扯元素最多的怕就是链表的遍历,此时只要记录好链表的第一个元素的地址,跟着指针的指引,按照指针遍历就好了;
- 对于插入操作
对于"前插"与"后插"进行区分,下面给出前插的代码及详解,注意这里是结构体内成员函数:
inline void insert0(ci u){ //基于某一元素的前插,现在我们的位置就在输入数据给出的元素那里
node *now=new node(u); //既然是插入,就要新建元素
if(pre) //判断一下,如果当前元素是第一,那么访问pre空指针就会RE
pre->next=now; //原先前面元素的后一个元素改为新插入的元素
now->pre=pre; //新插入的元素的前一个元素为原先前面的元素
now->next=this; //新插入的元素后一个元素是当前元素
pre=now; //新的元素是我们的前一个元素
}
这是基本前插的思想,对于后插请独立思考得出结论怂什么,还有后面的总代码呢
- 对于删除操作
单靠上面的条件比较恶心,题目所给出的,是被删去同学的序号,此时我们新建一个指针数组\(nd[]\),指向对应编号的元素,\(nd[i]\)指向的是\(i\)号同学,
我们这样一种指针的添加可以理解为为链表提供了直接的指向,比如样例输出数据中的:
2 4 1
实际上就是
元素[2,前一个空,下一个是元素4的地址]
->元素[4,前一个是元素2的地址,后一个是元素1的地址]
->元素[1,前一个是元素4的地址,后一个空]
//另外的*nd数组,可以直接通过*nd数组找到对应元素的位置进而访问对应元素
nd[1]=元素1的地址,nd[2]=元素2的地址, nd[3]=早删了,nd[4]=元素4的地址
其中我们利用\(nd[4]->pre\)调用的指针,实际上就是\(nd[2]\)里存放的指针
由此,我们可以方便地访问到数据给出的元素,
inline void remove(){
if(pre) pre->next=next; //如果有前一个元素的话就指向后面的元素
if(next) next->pre=pre; //类比上面一条
nd[number]=NULL; //number是元素值,元素i里存的number是i
}
- 对于细节
我们还需要一个指针\(^*rt\)来保存链表的第一个元素,这样才能输出整个链表
我们在结构体之前这样写:
struct node *nd[N],*rt;
struct node{
int number;
node *pre,*next;
...
};
这样我们就可以在结构体中的成员函数中用这些指针了
对于本题目,我们注意到它的操作是默认\(1\)已经在队列中了,所以我们需要在主函数中先建立元素\(1\),并且把第一个元素设定为\(1\)
nd[1]=new node(1); rt=nd[1];
另外的插入删除操作及一些改动会在完整代码中给出注释,
完整代码:
#include<iostream>
#include<cstdio>
#define ci const int &
using namespace std;
const int N=100005;
int n,m;
struct node *nd[N],*rt;
struct node{
int number;
node *pre,*next;
node(ci Number=0):number(Number){pre=next=NULL;}
inline void insert0(ci u){
node *now=new node(u); nd[u]=now; //把对应的nd指针赋值
if(rt==this) rt=now; //前插时如果在第一个元素之前插入则新元素为第一
if(pre) pre->next=now;
now->pre=pre;
now->next=this; pre=now;
}
inline void insert1(ci u){
node *now=new node(u); nd[u]=now; //同理
if(next) next->pre=now;
now->next=next;
now->pre=this; next=now;
}
inline void remove(){
if(rt==this) rt=next; //如果不巧删到了第一,就把第一后面的设成第一
if(pre) pre->next=next;
if(next) next->pre=pre;
nd[number]=NULL;
}
};
int main(){
scanf("%d",&n);
nd[1]=new node(1); rt=nd[1];
for(int i=2;i<=n;++i){
int k,p; scanf("%d%d",&k,&p); //下面将按照符号进行相应插入操作
if(!p) nd[k]->insert0(i);
else nd[k]->insert1(i);
}scanf("%d",&m);
for(int i=1;i<=m;++i){
int x; scanf("%d",&x);
if(nd[x]) nd[x]->remove(); //把对应的元素删去
}
while(rt){ //当前指针不为空则遍历没有结束
printf("%d ",rt->number); //输出
rt=rt->next; //传递
}return 0;
}
希望我的讲解能够给读者带来些什么,总之感谢你的阅读