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;
}
希望我的講解能夠給讀者帶來些什么,總之感謝你的閱讀