鏈表的基本概念
鏈表的基本概念
在鏈表存儲中,每個結點不僅包含所存元素的信息,還包含元素之間邏輯關係的消息,,如單鏈表中前驅結點包含後繼結點的地址信息,這樣就通過前驅結點中的地址信息找到後繼結點的位置。
鏈表的示意圖如圖所示:
如上圖所示,4個房間是散落存在的,每個房間的右邊有走向下一個房間的方向指示箭頭。因此,如果想要訪問最後一個房間,就必須從第一個房間開始,就必須從第一個房間開始,依次走過前3個房間,才能來到最後一個房間,而不能直接找出最後一個房間的位置,即鏈表不支持隨機訪問。
如上圖所示,鏈表中的每一個結點需要劃出一部分空間來存儲指向下一個結點位置的指針,因此鏈表中結點的存儲空間利用率較順序表稍低一些。
如上圖所示,鏈表的結點可以散落在內存中的任意位置,且不需要一次性地劃分所有結點所需地空間給鏈表,而是需要幾個結點就臨時劃分幾個。由此可見鏈表支持存儲空間地動態分配。
如上圖所示,如果想要在第一個和第二個房間之間插入一個新房間,則只需改動房間後邊的方向指示箭頭即可,將第一個房間的箭頭指向新插入的房間,然後將新插入的房間的箭頭指向第二個房間,即在鏈表中進行插入操作無須移動元素。
單鏈表的基本概念
單鏈表:在每個結點中除了包含數據域外,還包含一個指針域,用以指向其後繼結點。
如下圖所示爲帶頭結點的單鏈表中
帶頭結點的單鏈表中,頭指針Head指向頭結點,頭結點的值域不含任何信息,從頭結點的後繼結點開始存儲數據信息。頭指針Head始終不等於NULL,Head->next等於NULL的時候,鏈表爲空。
如下圖所示爲不帶頭結點的單鏈表中
不帶頭結點的單鏈表中:的頭指針head直接指向開始結點,,即圖中的結點1,當Head等於NULL的時候,鏈表爲空。
注意:在題目中要區分頭指針和頭結點,不論是帶頭結點的鏈表還是不帶頭結點的鏈表,頭指針都指向鏈表的第一個結點,而頭結點是帶頭結點的鏈表中的第一個結點,只作爲鏈表存在的標誌。
單鏈表的結點定義
typedf struct Lnode
{
int data;//data中存放結點數據域(默認是int型)
struct Lnode *next;//指向後繼結點的指針
}Lnode;//定義單鏈表結點類型
說明:結點是內存中一片由用戶分配的存儲空間,只有一個地址來表示它的存在,沒有顯式的名稱,因此我們會在分配鏈表結點空間的時候,同時定義一個指針,來存儲這片空間的地址,(這個過程通俗的講叫指針指向結點),並且常用這個指針的名稱來作爲結點的名稱。
例如下面代碼:
Lnode *A =(Lnode *)malloc(sizeof(Lnode));
用戶分配了一片Lnode型空間,也就是構造了一個Lnode型的結點,
這時候定義一個名字爲A的指針來指向這個結點,同時我們把A也當作這個結點的名字。
注意,這裏A命名了兩個東西,一個是結點,另一個是指向這個結點的指針。
對單鏈表的操作
(1)不帶頭結點的尾插法
用尾插法建立鏈表(不帶頭結點的)
主要參考此圖來寫代碼
代碼如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定義一個頭指針
lnode *new_lnode;//定義一個新節點的指針,用來指向malloc分配的地址
int i;
lnode *tail;//定義一個新的指針,始終指向終端節點
for(i=0; i<10; i++)//循環申請10個節點來存放數字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申請的節點
memset(new_lnode, 0, sizeof(lnode));//對新申請的節點進行內存清
new_lnode->next=NULL;//將節點的next域清0,這句可以省略
new_lnode->data=i+1;//對節點data域賦值
if( hand == NULL)//判斷頭指針是否爲空,如果爲空,鏈表爲空,如果不爲空,讓hand指向第新申請的節點
{
hand=new_lnode;
}
else//如果頭指針不爲空,就讓tail指向新申請的節點
{
tail->next = new_lnode;//新申請的tail指針,始終指向終端節點
}
tail = new_lnode;//tail始終爲終端節點。
}
lnode *search=NULL;//定義一個新的指針,來遍歷鏈表
for(search=hand;search!=NULL;search=search->next)
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
代碼運行結果如下
(2)不帶頭結點的頭插法
用頭插法建立鏈表(不帶頭結點的)
對不帶頭節點的(如下圖)操作
代碼如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定義一個頭指針
lnode *new_lnode;//定義一個新節點的指針,用來指向malloc分配的地址
int i;
for(i=0; i<10; i++)//循環申請10個節點來存放數字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申請的節點
memset(new_lnode, 0, sizeof(lnode));//對新申請的節點進行內存清
new_lnode->next=NULL;//將節點的next域清0,這句可以省略
new_lnode->data=i+1;//對節點data域賦值
if( hand == NULL)//判斷頭指針是否爲空,如果爲空,鏈表爲空,如果不爲空,讓hand指向第新申請的節點
{
hand=new_lnode;
}
else
{
new_lnode->next = hand;//頭指針始終指向新申請的節點的指針域
hand = new_lnode;//hand指向新節點
}
}
lnode *search=NULL;//定義一個新的指針,來遍歷鏈表
for(search=hand;search!=NULL;search=search->next)
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
運行結果如下:
通過上面寫無頭結點的頭插法和尾插法,我們可以發現他們變化的地方是:尾插法定義了一個新的tail指針,用來指向申請新節點的最後一個,而頭插法,沒有定義,直接讓頭指針,指向每次新來的節點。可以看以下如下圖的兩個對比
(3)帶頭結點的尾插法
接下來我們將要寫一下帶頭結點的鏈表的尾插法
用尾插法建立鏈表(帶頭結點的)
頭結點的鏈表圖
代碼如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定義一個頭指針
lnode *new_lnode;//定義一個新節點的指針,用來指向malloc分配的地址
int i;
lnode *tail=NULL;
lnode *node;//定義一個新的指針,指向頭節點的
node=(lnode *) malloc(sizeof(lnode));
memset(node,0 ,sizeof(lnode));
node->next=NULL;
tail=hand=node;//hand指向頭結點,因此此時頭節點就是終端節點
for(i=0; i<10; i++)//循環申請10個節點來存放數字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申請的節點
memset(new_lnode, 0, sizeof(lnode));//對新申請的節點進行內存清0
new_lnode->next=NULL;//將節點的next域清0,這句可以省略
new_lnode->data=i+1;//對節點data域賦值
tail->next=new_lnode;//用tail來指向新的節點
tail=tail->next;//tail指向終端節點
}
lnode *search=NULL;//定義一個新的指針,來遍歷鏈表
for(search=node;search!=NULL;search=search->next)//最後遍歷的結果爲0~1,因爲頭節點的data爲0
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
代碼運行結果如下:
(4)帶頭結點的頭插法
接下來我們將要寫一下帶頭結點的鏈表的頭插法
用頭插法建立鏈表(帶頭結點的)
頭結點的鏈表圖
代碼如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定義一個頭指針
lnode *new_lnode;//定義一個新節點的指針,用來指向malloc分配的地址
int i;
lnode *node;//定義一個新的指針,指向頭節點的
node=(lnode *) malloc(sizeof(lnode));
memset(node,0 ,sizeof(lnode));
node->next=NULL;
hand=node;//hand指向頭結點,因此此時頭節點就是終端節點
for(i=0; i<10; i++)//循環申請10個節點來存放數字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申請的節點
memset(new_lnode, 0, sizeof(lnode));//對新申請的節點進行內存清0
new_lnode->next=NULL;//將節點的next域清0,這句可以省略
new_lnode->data=i+1;//對節點data域賦值
new_lnode->next=node->next;//new_lnode所指新節點的指針域next指向頭結點開始的節點。
node->next=new_lnode;//頭結點的指針域next指向new_lnode節點,使得new_lnode成爲新的開始節點。
}
lnode *search=NULL;//定義一個新的指針,來遍歷鏈表
for(search=node;search!=NULL;search=search->next)//最後遍歷的結果爲0~1,因爲頭節點的data爲0
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
代碼結果如下:
單鏈表的帶頭結點和不帶頭結點的頭插法和尾插法的代碼都已經寫完了,大家可以把帶頭的尾插法和不帶頭的尾插法聯合起來學習,會更好的理解,如果有錯誤的地方還行指出!謝謝!