數據結構 第4講 單鏈表

本文來自本人著作《趣學數據結構》

鏈表是線性表的鏈式存儲方式,邏輯上相鄰的數據在計算機內的存儲位置不一定相鄰,那么怎么表示邏輯上的相鄰關系呢?可以給每個元素附加一個指針域,指向下一個元素的存儲位置。如圖所示:

從圖中可以看出,每個結點包含兩個域:數據域和指針域,指針域存儲下一個結點的地址,因此指針指向的類型也是結點類型。

結點結構體的定義:

定義了結點之后,我們就可以把若干個結點連接在一起,形成一個鏈表:

是不是像一個鐵鏈子,一環扣一環的連在一起?

不管這個鐵鏈子有多長,只要我們找到它的頭,就可以拉起整個鐵鏈子,因此我們給這個鏈表設置一個頭指針,這個鏈表中的每個結點都可以找到了。

有時候為了操作方便,我們還會給鏈表增加一個不存放數據的頭結點:

就像是給鐵鏈子加個鑰匙扣:

我們可以看到剛才的鏈表每個指針都是指向下一個結點,都是朝向一個方向的,這樣的鏈表稱為單向鏈表,或單鏈表。

在順序表中,想找第i個元素,就可以立即通過L.elem[i-1]找到,稱為隨機存取方式,而在單鏈表中,想找第i個元素?沒那么容易,必須從頭開始,按順序一個一個來,一直數到第i個元素,稱為順序存取方式。

下面以帶頭結點的單鏈表為例,講解單鏈表的初始化、創建、取值、查找、插入、刪除操作。

1. 單鏈表初始化

單鏈表初始化是指構建一個空表:

bool InitList_L(LinkList &L)//構造一個空的單鏈表L

{

L=new LNode; //生成新結點作為頭結點,用頭指針L指向頭結點

if(!L)

return false; //生成結點失敗

L->next=NULL; //頭結點的指針域置空

return true;

}

2. 單鏈表的創建

創建單鏈表分為前插法尾插法兩種,前插法創建的單鏈表和輸入順序正好相反,因此稱為逆序建表,尾插法創建的單鏈表和輸入順序一致,因此稱為正序建表。

前插法建表如圖:

初始狀態

輸入數據元素1,創建新結點,把元素1放入新結點數據域:

s=new LNode; //生成新結點s

cin>>s->data; //輸入元素值賦給新結點的數據域

前插操作,插入到頭結點的后面:

輸入數據元素2,創建新結點,把元素2放入新結點數據域:

前插操作,插入到頭結點的后面:

解釋:

為什么要先修改后面那個指針呢?

因為一旦修改了L結點的指針域指向s,那么原來L結點后面的結點就找不到了,

注意:修改指針順序的原則:先修改沒有指針標記的那一端。

如果要插入結點的兩端都有標記,例如再定義一個指針q指向第1個結點,那么先修改哪個指針都無所謂了。

拉直鏈表之后:

繼續依次輸入數據元素3,4,5,6,7,8,9,10,前插法創建鏈表的結果:

void CreateList_H(LinkList &L)//前插法創建單鏈表

{

int n; //輸入n個元素的值,建立到頭結點的單鏈表L

LinkList s; //定義一個指針變量

L=new LNode;

L->next=NULL; //先建立一個帶頭結點的空鏈表

cout <<"請輸入元素個數n:" <

cin>>n;

cout <<"請依次輸入n個元素:" <

cout <<"前插法創建單鏈表..." <

while(n--)

{

s=new LNode; //生成新結點s

cin>>s->data; //輸入元素值賦給新結點的數據域

s->next=L->next;

L->next=s; //將新結點s插入到頭結點之后

}

}

尾插法建表如圖:

初始狀態(尾插法需要一個尾指針永遠指向鏈表的尾結點)

輸入數據元素1,創建新結點,把元素1放入新結點數據域:

s=new LNode; //生成新結點s

cin>>s->data; //輸入元素值賦給新結點的數據域

尾插操作,插入到尾結點的后面:

解釋:

輸入數據元素2,創建新結點,把元素2放入新結點數據域:

尾插操作,插入到尾結點的后面:

繼續依次輸入數據元素3,4,5,6,7,8,9,10,前插法創建鏈表的結果:

void CreateList_R(LinkList &L)//尾插法創建單鏈表

{

//輸入n個元素的值,建立帶表頭結點的單鏈表L

int n;

LinkList s, r;

L=new LNode;

L->next=NULL; //先建立一個帶頭結點的空鏈表

r=L; //尾指針r指向頭結點

cout <<"請輸入元素個數n:" <

cin>>n;

cout <<"請依次輸入n個元素:" <

cout <<"尾插法創建單鏈表..." <

while(n--)

{

s=new LNode;//生成新結點

cin>>s->data; //輸入元素值賦給新結點的數據域

s->next=NULL;

r->next=s;//將新結點s插入尾結點r之后

r=s;//r指向新的尾結點s

}

}

3. 單鏈表取值

單鏈表的取值不像順序表那樣可以隨機訪問任何一個元素,單鏈表只有頭指針,各個結點的物理地址是不連續的,要想找到第i個結點,就必須從第一個結點開始按順序往后數,一直數到第i個結點。

那么具體怎么做呢?

注意:鏈表的頭指針不可以隨意改動!一個鏈表是由頭指針來標識的,一旦頭指針改動或丟失,這個鏈表就不完整或找不到了。想想看,你拉著鐵鏈子一頭,另一端綁著水桶,到井里打水,你手一松,鏈子掉井里,找不到了。所以鏈表的頭指針是不能隨意改動的,如果需要用指針移動,可定義一個指針變量進行移動。

可以定義一個p指針,指向第一個元素結點,用j作為計數器,j=1;

然后p指向p的下一個結點,即:

p=p->next; //p指向下一個結點

j++; //計數器j加1

直到p為空或者j=i停止,p為空說明沒有數到i,鏈表就結束了,j=i說明找到了第i個結點。

bool GetElem_L(LinkList L, int i, int &e)//單鏈表的取值

{

//在帶頭結點的單鏈表L中查找第i個元素

//用e記錄L中第i個數據元素的值

int j;

LinkList p;

p=L->next; //p指向第一個數據結點

j=1; //j為計數器

while (j

{

p=p->next; //p指向下一個結點

j++; //計數器j相應加1

}

if (!p || j>i)

return false; //i值不合法i>n或i<=0

e=p->data; //取第i個結點的數據域

return true;

}

4. 單鏈表查找

在一個單鏈表中查找是否存在元素e,可以定義一個p指針,指向第一個元素結點,比較p指向結點的數據域是否為e,如果相等,查找成功返回true,如果不等,則p指向p的下一個結點,即:

p=p->next; //p指向下一個結點

繼續比較,如果p為空,查找失敗返回false。

bool LocateElem_L(LinkList L, int e) //在帶頭結點的單鏈表L中查找值為e的元素

{

LinkList p;

p=L->next;

while (p && p->data!=e)//沿著鏈表向后掃描,直到p空或p所指結點數據域等于e

p=p->next; //p指向下一個結點

if(!p)

return false; //查找失敗p為NULL

return true;

}

5. 單鏈表插入

如果要在第i個結點之前插入一個元素,則必須先找到第i-1個結點,想一想:為什么?

單鏈表只有一個指針域,是向后操作的,不可以向前處理,第i個結點之前插入一個元素相當于把新結點放在第i-1個結點和第i個結點之間。

解釋:

是不是有似曾相識的感覺?

前面講的前插法建鏈表,就是每次將新結點插入到頭結點和第一個結點之間。

bool ListInsert_L(LinkList &L, int i, int &e)//單鏈表的插入

{

//在帶頭結點的單鏈表L中第i個位置插入值為e的新結點

int j;

LinkList p, s;

p=L;

j=0;

while (p&&j

{

p=p->next;

j++;

}

if (!p || j>i-1) //i>n+1或者i<1

return false;

s=new LNode; //生成新結點

s->data=e; //將新結點的數據域置為e

s->next=p->next; //將新結點的指針域指向結點ai

p->next=s; //將結點p的指針域指向結點s

return true;

}

6. 單鏈表刪除

刪除一個結點,實際上是把這個結點跳過去。如圖:

要想跳過第i個結點,就必須先找到第i-1個結點,否則是無法跳過去的。

p->next=q->next;含義是將q結點的下一個結點地址賦值給p結點的指針域。在這些有關指針的賦值語句中,很多同學不理解,容易混淆,在此說明一下,等號的右側是結點的地址,等號的左側是結點的指針域:

q結點的下一個結點地址存儲在q->next里面,等號右側的q->next就相當于把q結點的下一個結點地址讀出來,賦值給p結點的next指針域,這樣,就把q結點跳過去了。然后用delete q釋放被刪除結點的空間。

bool ListDelete_L(LinkList &L, int i) //單鏈表的刪除

{

//在帶頭結點的單鏈表L中,刪除第i個位置

LinkList p, q;

int j;

p=L;

j=0;

while((p->next)&&(j

{

p=p->next;

j++;

}

if (!(p->next)||(j>i-1))//當i>n或i<1時,刪除位置不合理

return false;

q=p->next; //臨時保存被刪結點的地址以備釋放空間

p->next=q->next; //改變刪除結點前驅結點的指針域

delete q; //釋放被刪除結點的空間

return true;

}

單鏈表基本操作完整代碼:

完整代碼:

#include

#include

#include

#include

using namespace std;

typedef struct LNode {

int data; //結點的數據域

struct LNode *next; //結點的指針域

}LNode, *LinkList; //LinkList為指向結構體LNode的指針類型

bool InitList_L(LinkList &L)//構造一個空的單鏈表L

{

L=new LNode; //生成新結點作為頭結點,用頭指針L指向頭結點

if(!L)

return false; //生成結點失敗

L->next=NULL; //頭結點的指針域置空

return true;

}

void CreateList_H(LinkList &L)//前插法創建單鏈表

{

//輸入n個元素的值,建立到頭結點的單鏈表L

int n;

LinkList s; //定義一個指針變量

L=new LNode;

L->next=NULL; //先建立一個帶頭結點的空鏈表

cout <<"請輸入元素個數n:" <

cin>>n;

cout <<"請依次輸入n個元素:" <

cout <<"前插法創建單鏈表..." <

while(n--)

{

s=new LNode; //生成新結點s

cin>>s->data; //輸入元素值賦給新結點的數據域

s->next=L->next;

L->next=s; //將新結點s插入到頭結點之后

}

}

void CreateList_R(LinkList &L)//尾插法創建單鏈表

{

//輸入n個元素的值,建立帶表頭結點的單鏈表L

int n;

LinkList s, r;

L=new LNode;

L->next=NULL; //先建立一個帶頭結點的空鏈表

r=L; //尾指針r指向頭結點

cout <<"請輸入元素個數n:" <

cin>>n;

cout <<"請依次輸入n個元素:" <

cout <<"尾插法創建單鏈表..." <

while(n--)

{

s=new LNode;//生成新結點

cin>>s->data; //輸入元素值賦給新結點的數據域

s->next=NULL;

r->next=s;//將新結點s插入尾結點r之后

r=s;//r指向新的尾結點s

}

}

bool GetElem_L(LinkList L, int i, int &e)//單鏈表的取值

{

//在帶頭結點的單鏈表L中查找第i個元素

//用e記錄L中第i個數據元素的值

int j;

LinkList p;

p=L->next;//p指向第一個結點,

j=1; //j為計數器

while (j

{

p=p->next; //p指向下一個結點

j++; //計數器j相應加1

}

if (!p || j>i)

return false; //i值不合法i>n或i<=0

e=p->data; //取第i個結點的數據域

return true;

}

bool LocateElem_L(LinkList L, int e) //按值查找

{

//在帶頭結點的單鏈表L中查找值為e的元素

LinkList p;

p=L->next;

while (p && p->data!=e)//順鏈域向后掃描,直到p為空或p所指結點的數據域等于e

p=p->next; //p指向下一個結點

if(!p)

return false; //查找失敗p為NULL

return true;

}

bool ListInsert_L(LinkList &L, int i, int &e)//單鏈表的插入

{

//在帶頭結點的單鏈表L中第i個位置插入值為e的新結點

int j;

LinkList p, s;

p=L;

j=0;

while (p&&j

{

p=p->next;

j++;

}

if (!p || j>i-1)//i>n+1或者i<1

return false;

s=new LNode; //生成新結點

s->data=e; //將新結點的數據域置為e

s->next=p->next; //將新結點的指針域指向結點ai

p->next=s; //將結點p的指針域指向結點s

return true;

}

bool ListDelete_L(LinkList &L, int i) //單鏈表的刪除

{

//在帶頭結點的單鏈表L中,刪除第i個位置

LinkList p, q;

int j;

p=L;

j=0;

while((p->next)&&(j

{

p=p->next;

j++;

}

if (!(p->next)||(j>i-1))//當i>n或i<1時,刪除位置不合理

return false;

q=p->next; //臨時保存被刪結點的地址以備釋放空間

p->next=q->next; //改變刪除結點前驅結點的指針域

delete q; //釋放被刪除結點的空間

return true;

}

void Listprint_L(LinkList L) //單鏈表的輸出

{

LinkList p;

p=L->next;

while (p)

{

cout <data <<"\t";

p=p->next;

}

cout<

}

int main()

{

int i,x,e,choose;

LinkList L;

cout << "1. 初始化\n";

cout << "2. 創建單鏈表(前插法)\n";

cout << "3. 創建單鏈表(尾插法)\n";

cout << "4. 取值\n";

cout << "5. 查找\n";

cout << "6. 插入\n";

cout << "7. 刪除\n";

cout << "8. 輸出\n";

cout << "0. 退出\n";

choose=-1;

while (choose!=0)

{

cout<<"請輸入數字選擇:";

cin>>choose;

switch (choose)

{

case 1: //初始化一個空的單鏈表

if (InitList_L(L))

cout << "初始化一個空的單鏈表!\n";

break;

case 2: //創建單鏈表(前插法)

CreateList_H(L);

cout << "前插法創建單鏈表輸出結果:\n";

Listprint_L(L);

break;

case 3: //創建單鏈表(尾插法)

CreateList_R(L);

cout << "尾插法創建單鏈表輸出結果:\n";

Listprint_L(L);

break;

case 4: //單鏈表的按序號取值

cout << "請輸入一個位置i用來取值:";

cin >> i;

if (GetElem_L(L,i,e))

{

cout << "查找成功\n";

cout << "第" << i << "個元素是:"<

}

else

cout << "查找失敗\n\n";

break;

case 5: //單鏈表的按值查找

cout<<"請輸入所要查找元素x:";

cin>>x;

if (LocateElem_L(L,x))

cout << "查找成功\n";

else

cout << "查找失敗! " <

break;

case 6: //單鏈表的插入

cout << "請輸入插入的位置和元素(用空格隔開):";

cin >> i;

cin >> x;

if (ListInsert_L(L, i, x))

cout << "插入成功.\n\n";

else

cout << "插入失敗!\n\n";

break;

case 7: //單鏈表的刪除

cout<<"請輸入所要刪除的元素位置i:";

cin>>i;

if (ListDelete_L(L, i))

cout<<"刪除成功!\n";

else

cout<<"刪除失敗!\n";

break;

case 8: //單鏈表的輸出

cout << "當前單鏈表的數據元素分別為:\n";

Listprint_L(L);

cout << endl;

break;

}

}

return 0;

}

本文來自本人著作《趣學數據結構》。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容