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

希望我的講解能夠給讀者帶來些什么,總之感謝你的閱讀


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM