數(shù)據(jù)結(jié)構(gòu) 第4講 單鏈表

本文來自本人著作《趣學(xué)數(shù)據(jù)結(jié)構(gòu)》

鏈表是線性表的鏈?zhǔn)酱鎯Ψ绞?,邏輯上相鄰的?shù)據(jù)在計算機(jī)內(nèi)的存儲位置不一定相鄰,那么怎么表示邏輯上的相鄰關(guān)系呢?可以給每個元素附加一個指針域,指向下一個元素的存儲位置。如圖所示:

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

結(jié)點結(jié)構(gòu)體的定義:

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

是不是像一個鐵鏈子,一環(huán)扣一環(huán)的連在一起?

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

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

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

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

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

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

1. 單鏈表初始化

單鏈表初始化是指構(gòu)建一個空表:

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

{

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

if(!L)

return false; //生成結(jié)點失敗

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

return true;

}

2. 單鏈表的創(chuàng)建

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

前插法建表如圖:

初始狀態(tài)

輸入數(shù)據(jù)元素1,創(chuàng)建新結(jié)點,把元素1放入新結(jié)點數(shù)據(jù)域:

s=new LNode; //生成新結(jié)點s

cin>>s->data; //輸入元素值賦給新結(jié)點的數(shù)據(jù)域

前插操作,插入到頭結(jié)點的后面:

輸入數(shù)據(jù)元素2,創(chuàng)建新結(jié)點,把元素2放入新結(jié)點數(shù)據(jù)域:

前插操作,插入到頭結(jié)點的后面:

解釋:

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

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

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

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

拉直鏈表之后:

繼續(xù)依次輸入數(shù)據(jù)元素3,4,5,6,7,8,9,10,前插法創(chuàng)建鏈表的結(jié)果:

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

{

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

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

L=new LNode;

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

cout <<"請輸入元素個數(shù)n:" <

cin>>n;

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

cout <<"前插法創(chuàng)建單鏈表..." <

while(n--)

{

s=new LNode; //生成新結(jié)點s

cin>>s->data; //輸入元素值賦給新結(jié)點的數(shù)據(jù)域

s->next=L->next;

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

}

}

尾插法建表如圖:

初始狀態(tài)(尾插法需要一個尾指針永遠(yuǎn)指向鏈表的尾結(jié)點)

輸入數(shù)據(jù)元素1,創(chuàng)建新結(jié)點,把元素1放入新結(jié)點數(shù)據(jù)域:

s=new LNode; //生成新結(jié)點s

cin>>s->data; //輸入元素值賦給新結(jié)點的數(shù)據(jù)域

尾插操作,插入到尾結(jié)點的后面:

解釋:

輸入數(shù)據(jù)元素2,創(chuàng)建新結(jié)點,把元素2放入新結(jié)點數(shù)據(jù)域:

尾插操作,插入到尾結(jié)點的后面:

繼續(xù)依次輸入數(shù)據(jù)元素3,4,5,6,7,8,9,10,前插法創(chuàng)建鏈表的結(jié)果:

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

{

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

int n;

LinkList s, r;

L=new LNode;

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

r=L; //尾指針r指向頭結(jié)點

cout <<"請輸入元素個數(shù)n:" <

cin>>n;

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

cout <<"尾插法創(chuàng)建單鏈表..." <

while(n--)

{

s=new LNode;//生成新結(jié)點

cin>>s->data; //輸入元素值賦給新結(jié)點的數(shù)據(jù)域

s->next=NULL;

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

r=s;//r指向新的尾結(jié)點s

}

}

3. 單鏈表取值

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

那么具體怎么做呢?

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

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

然后p指向p的下一個結(jié)點,即:

p=p->next; //p指向下一個結(jié)點

j++; //計數(shù)器j加1

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

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

{

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

//用e記錄L中第i個數(shù)據(jù)元素的值

int j;

LinkList p;

p=L->next; //p指向第一個數(shù)據(jù)結(jié)點

j=1; //j為計數(shù)器

while (j

{

p=p->next; //p指向下一個結(jié)點

j++; //計數(shù)器j相應(yīng)加1

}

if (!p || j>i)

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

e=p->data; //取第i個結(jié)點的數(shù)據(jù)域

return true;

}

4. 單鏈表查找

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

p=p->next; //p指向下一個結(jié)點

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

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

{

LinkList p;

p=L->next;

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

p=p->next; //p指向下一個結(jié)點

if(!p)

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

return true;

}

5. 單鏈表插入

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

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

解釋:

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

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

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

{

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

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; //生成新結(jié)點

s->data=e; //將新結(jié)點的數(shù)據(jù)域置為e

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

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

return true;

}

6. 單鏈表刪除

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

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

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

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

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

{

//在帶頭結(jié)點的單鏈表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))//當(dāng)i>n或i<1時,刪除位置不合理

return false;

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

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

delete q; //釋放被刪除結(jié)點的空間

return true;

}

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

完整代碼:

#include

#include

#include

#include

using namespace std;

typedef struct LNode {

int data; //結(jié)點的數(shù)據(jù)域

struct LNode *next; //結(jié)點的指針域

}LNode, *LinkList; //LinkList為指向結(jié)構(gòu)體LNode的指針類型

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

{

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

if(!L)

return false; //生成結(jié)點失敗

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

return true;

}

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

{

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

int n;

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

L=new LNode;

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

cout <<"請輸入元素個數(shù)n:" <

cin>>n;

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

cout <<"前插法創(chuàng)建單鏈表..." <

while(n--)

{

s=new LNode; //生成新結(jié)點s

cin>>s->data; //輸入元素值賦給新結(jié)點的數(shù)據(jù)域

s->next=L->next;

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

}

}

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

{

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

int n;

LinkList s, r;

L=new LNode;

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

r=L; //尾指針r指向頭結(jié)點

cout <<"請輸入元素個數(shù)n:" <

cin>>n;

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

cout <<"尾插法創(chuàng)建單鏈表..." <

while(n--)

{

s=new LNode;//生成新結(jié)點

cin>>s->data; //輸入元素值賦給新結(jié)點的數(shù)據(jù)域

s->next=NULL;

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

r=s;//r指向新的尾結(jié)點s

}

}

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

{

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

//用e記錄L中第i個數(shù)據(jù)元素的值

int j;

LinkList p;

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

j=1; //j為計數(shù)器

while (j

{

p=p->next; //p指向下一個結(jié)點

j++; //計數(shù)器j相應(yīng)加1

}

if (!p || j>i)

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

e=p->data; //取第i個結(jié)點的數(shù)據(jù)域

return true;

}

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

{

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

LinkList p;

p=L->next;

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

p=p->next; //p指向下一個結(jié)點

if(!p)

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

return true;

}

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

{

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

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; //生成新結(jié)點

s->data=e; //將新結(jié)點的數(shù)據(jù)域置為e

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

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

return true;

}

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

{

//在帶頭結(jié)點的單鏈表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))//當(dāng)i>n或i<1時,刪除位置不合理

return false;

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

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

delete q; //釋放被刪除結(jié)點的空間

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. 創(chuàng)建單鏈表(前插法)\n";

cout << "3. 創(chuàng)建單鏈表(尾插法)\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<<"請輸入數(shù)字選擇:";

cin>>choose;

switch (choose)

{

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

if (InitList_L(L))

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

break;

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

CreateList_H(L);

cout << "前插法創(chuàng)建單鏈表輸出結(jié)果:\n";

Listprint_L(L);

break;

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

CreateList_R(L);

cout << "尾插法創(chuàng)建單鏈表輸出結(jié)果:\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 << "當(dāng)前單鏈表的數(shù)據(jù)元素分別為:\n";

Listprint_L(L);

cout << endl;

break;

}

}

return 0;

}

本文來自本人著作《趣學(xué)數(shù)據(jù)結(jié)構(gòu)》。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容