[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;
}

希望我的讲解能够给读者带来些什么,总之感谢你的阅读

posted @ 2020-07-04 21:29  _Alex_Mercer  阅读(775)  评论(4编辑  收藏  举报