要想理解鏈表,一定得理解指針。鏈表的本質就是指針。
關於指針的理解,小白可以看這篇文章,比較容易理解。https://blog.csdn.net/u014095878/article/details/104559879
要想學習指針,第一步,就是學會接受它,不要覺得它很難。第二步,要立體的去思考,要想着這個指針裏面裝了什麼東西,而不是簡單的給這個變量賦值。
創建單鏈表有四種情況:不帶頭節點的頭插法,不帶頭節點的尾插法,帶頭節點的頭插法,帶頭節點的尾插法。
看完下面我的分析。你就可以掌握單向鏈表的創建了。
先來看一個不帶頭節點的尾插法的實例:
#include <stdio.h>
#include <malloc.h>
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct Node_s //定義鏈表節點的結構體
{
ElemType data; //數據域
struct Node_s* next; //指針域
}Node, * LinkList; //Node相當於struct Node_s Linklist相當於struct Node_s *
int Output_L(LinkList head) //輸出不帶頭節點的單鏈表的元素
{
if (!head)
return -1;
LinkList p;
p = head;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
int main()
{
int i;
Node *head = NULL;
Node *tail = NULL;
Node *new_node = NULL;
for(i=0;i<5;i++)
{
new_node = (LinkList)malloc(sizeof(Node));
new_node->data = i+1;
new_node->next = NULL; //將即將要插入的節點打包(數據域放什麼,指針域放什麼)
if(head == NULL) //如果爲空鏈表
{
head = new_node; //頭指針指向第一個節點,頭結點
tail = new_node; //尾指針指向第一個節點
}
else
{
tail->next = new_node; //尾指針指向的節點(上一個節點)的指針域指向新插入的節點
tail = new_node; //這兩句不能反
}
cout << "i =" << i << ",tail = " << tail << endl;
}
Output_L(head);
}
剛接觸這個很難去理解這個鏈表。我們知道鏈表是由一個結點一個結點串起來的。也知道一個結點有兩部分組成,一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。卻不知道具體是是怎麼實現的,儘管有一堆代碼告訴我們實現的方法,我們卻難以消化。
剛看到這個代碼時,我只理解一句,new_node = (LinkList)malloc(sizeof(Node));這個是申請一塊內存。
說說不理解的點。
1.head只賦值了一次,爲什麼後面打印輸出是一個完整的單向鏈表?
2.爲什麼tail->next = new_node;賦值之後又賦值tail = new_node; ?
帶着這兩個疑問,我們重新梳理一下上面的代碼。
Node *head = NULL;
Node *tail = NULL;
Node *new_node = NULL;
有3個變量,一個是head 頭指針,一個是tail尾指針,一個是new_node不斷的申請新內存的指針。
1.第一次進循環來,i=0;我們已經理解了new_node = (LinkList)malloc(sizeof(Node));每循環一次就申請一個新內存,也就新節點的地址;
2.new_node->data = i+1;給數據域賦值。i=0的時候,這個時候我們知道了第一個結點的地址是什麼,地址裏存放的數據域是什麼,但是還不知道下一個結點的地址。
if(head == NULL) //如果爲空鏈表
{
head = new_node; //頭指針指向第一個節點,頭結點
tail = new_node; //尾指針指向第一個節點
}
3.知道了第一個結點的地址,也就是頭結點,趕緊把這個地址賦值給head(head = new_node;)。
我們疑惑的問題點到了,爲什麼只賦值一次?
因爲,頭結點賦值了之後,就不必再變了。後面變化的一直是tail尾指針,因爲你不斷的往後面插入數據。所以,尾指針是一直變化的。
這裏我們也看到了,tail第一次跟head指向的地址一樣(tail = new_node;)。
這個時候第一個結點的數據域知道了,但是指針域還不知道,head->next = null,tail->next = null,new_node->next = null,還不知道下一個地址在哪裏。
4.緊接着,第二次進來,i= 1,new_node又申請了新的內存地址,即第二個結點。
new_node->data = i+1 =2;這個時候tail還是指向第一個地址(頭結點),第一個地址存放的數據tail->data=1,tail->next=null.
所以這個時候,給tail->next賦值,tail->next = new_node,
把第一個結點的指針指向第二個結點,這個時候就串起來鏈表了。但是隻有兩個結點的鏈表。
後面是我們的第二個問題點,爲什麼又賦值tail = new_node; 因爲後面還要繼續往後面插入數據。
tail已經完成了第一次兩個結點的串聯,tail這個時候要移到第二個結點來,它要搬家了。
head頭結點地址不變,tail從頭結點地址移到第二個結點的地址,專業術語是,指向第二個結點的地址。
else
{
tail->next = new_node; //尾指針指向的節點(上一個節點)的指針域指向新插入的節點
tail = new_node; //這兩句不能反
}
如果還不理解的,可以看看這段代碼。我們知道指針就是存地址的嘛。
int i = 1,j=2;
int *p = &i;
cout<<"p的地址:"<<p<<endl;
p = &j;
cout<<"p的地址:"<<p<<endl;
指針p的地址從指向i的地址,到第二次指向j的地址。
tail從第一個結點的地址,指向第二個結點的地址。結點地址裏存着數據data和一個地址,這個地址指向下一個結點的地址。
後面的循環,你可以自己在草稿紙裏演算,演算成功了,你就掌握了這個鏈表的創建了。
如果你還想了解單鏈表的其他的不帶頭節點的頭插法,帶頭節點的頭插法,帶頭節點的尾插法,請參看下面這位博主的這篇文章
https://blog.csdn.net/TAlice/article/details/82112921。
感謝這位博主的介紹。感謝C/C++小白基礎交流羣裏@雁的指導幫助。希望你們能完全掌握鏈表。