數(shù)據(jù)結(jié)構(gòu)第4-2講雙向鏈表

鏈表是線性表的鏈式存儲方式,邏輯上相鄰的數(shù)據(jù)在計算機內(nèi)的存儲位置不一定相鄰,那么怎么表示邏輯上的相鄰關(guān)系呢?

可以給每個元素附加一個指針域,指向下一個元素的存儲位置。這種鏈表稱為單向鏈表,簡稱單鏈表,如圖1所示:

單鏈表中每個結(jié)點除了存儲自身數(shù)據(jù)之后,還存儲了下一個結(jié)點的地址,因此可以輕松訪問下一個結(jié)點,以及后面的后繼結(jié)點,但是如果想訪問前面的結(jié)點就不行了,再也回不去了。例如刪除結(jié)點p時,要先找到它的前一個結(jié)點q,然后才能刪掉p結(jié)點,單向鏈表只能往后走,不能向前走。如果需要向前走,怎么辦呢?

可以給每個元素附加兩個指針域,一個存儲前一個元素的地址,一個存儲下一個元素的地址。這種鏈表稱為雙向鏈表,如圖2所示:

從圖2中可以看出,雙向鏈表每個結(jié)點包含三個域:數(shù)據(jù)域和兩個指針域,指針域分別存儲前后兩個元素結(jié)點的地址,即前驅(qū)和后繼。因此指針指向的類型也是結(jié)點類型。

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

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

1.雙向鏈表初始化

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

bool InitList_L(DuLinkList &L)//構(gòu)造一個空的雙向鏈表L

{

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

if(!L)

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

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

return true;

}

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

創(chuàng)建雙向鏈表也可以用前插法尾插法,前插法創(chuàng)建的鏈表和輸入順序正好相反,因此稱為逆序建表,尾插法創(chuàng)建的鏈表和輸入順序一致,因此稱為正序建表。

前插法建表如圖:

(1)初始狀態(tài)

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

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

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

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

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

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

解釋:

注意:賦值語句的右側(cè)是一個地址,左側(cè)是一個結(jié)點的指針域。

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

因為一旦修改了L結(jié)點的next指針域,那么原來L的后繼結(jié)點就找不到了,要最后修改L->next指針。

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

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

拉直鏈表之后:

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

void CreateDuList_H(DuLinkList &L)//前插法創(chuàng)建雙向鏈表

{

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

int n;

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

L=new DuLNode;

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

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

cin>>n;

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

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

while(n--)

{

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

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

if(L->next)

{

L->next->prior=s;

}

s->next=L->next;

s->prior=L;

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

}

}

尾插法建表同單鏈表的尾插法建表,需要有一個尾指針,不再贅述。

3.雙向鏈表取值、查找如同單向鏈表,不再贅述。

4.雙向鏈表插入

單鏈表只有一個指針域,是向后操作的,不可以向前處理,因此單鏈表如果要在第i個結(jié)點之前插入一個元素,則必須先找到第i-1個結(jié)點。第i個結(jié)點之前插入一個元素相當于把新結(jié)點放在第i-1個結(jié)點之后。而雙向鏈表不需要,因為有兩個指針,可以向前后操作,直接找到第i個結(jié)點,就可以把新結(jié)點插入到第i個結(jié)點之前。

解釋:

因為p的前驅(qū)結(jié)點無標記,一旦修改了p結(jié)點的prior指針,p的前驅(qū)結(jié)點就找不到了,因此,最后修改這個指針。

bool ListInsert_L(DuLinkList &L, int i, int &e)//雙向鏈表的插入

{

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

int j;

DuLinkList p, s;

p=L;

j=0;

while (p&&j

{

p=p->next;

j++;

}

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

return false;

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

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

p->prior->next=s;

s->prior=p->prior;

s->next=p;

p->prior=s;

return true;

}

6.雙向鏈表刪除

刪除一個結(jié)點,實際上是把這個結(jié)點跳過去。要想跳過第i個結(jié)點,可以先找到第i個結(jié)點。然后修改指針,如圖:

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

p->next->prior =p->prior;含義是將p的前驅(qū)結(jié)點的地址賦值給p的后繼結(jié)點的prior指針域。即p的后繼結(jié)點的prior指針指向p的前驅(qū)結(jié)點。

這樣,就把p結(jié)點跳過去了。然后用delete p釋放被刪除結(jié)點的空間。刪除結(jié)點修改指針沒有順序,先修改那個都可以。

bool ListDelete_L(DuLinkList &L, int i) //雙向鏈表的刪除

{

//在帶頭結(jié)點的雙向鏈表L中,刪除第i個位置

DuLinkList p;

int j;

p=L;

j=0;

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

{

p=p->next;

j++;

}

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

return false;

p->prior->next=p->next;

p->next->prior=p->prior;

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

return true;

}

雙向鏈表基本操作完整代碼:

[cpp]view plaincopy

#include

#include

usingnamespacestd;

typedefstructDuLNode?{

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

structDuLNode?*prior,*next;//結(jié)點的指針域

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

boolInitDuList_L(DuLinkList?&L)//構(gòu)造一個空的雙向鏈表L

{

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

if(!L)

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

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

returntrue;

}

voidCreateDuList_H(DuLinkList?&L)//前插法創(chuàng)建雙向鏈表

{

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

intn;

DuLinkList?s;//定義一個指針變量

L=newDuLNode;

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

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

cin>>n;

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

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

while(n--)

{

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

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

if(L->next)

{

L->next->prior=s;

}

s->next=L->next;

s->prior=L;

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

}

}

boolGetElem_L(DuLinkList?L,inti,int&e)//雙向鏈表的取值

{

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

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

intj;

DuLinkList?p;

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

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

while(j

{

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

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

}

if(!p?||?j>i)

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

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

returntrue;

}

boolLocateElem_L(DuLinkList?L,inte)//按值查找

{

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

DuLinkList?p;

p=L->next;

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

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

if(!p)

returnfalse;//查找失敗p為NULL

returntrue;

}

boolListInsert_L(DuLinkList?&L,inti,int&e)//雙向鏈表的插入

{

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

intj;

DuLinkList?p,?s;

p=L;

j=0;

while(p&&j

{

p=p->next;

j++;

}

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

returnfalse;

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

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

p->prior->next=s;

s->prior=p->prior;

s->next=p;

p->prior=s;

returntrue;

}

boolListDelete_L(DuLinkList?&L,inti)//雙向鏈表的刪除

{

//在帶頭結(jié)點的雙向鏈表L中,刪除第i個位置

DuLinkList?p;

intj;

p=L;

j=0;

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

{

p=p->next;

j++;

}

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

returnfalse;

p->prior->next=p->next;

p->next->prior=p->prior;

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

returntrue;

}

voidListprint_L(DuLinkList?L)//雙向鏈表的輸出

{

DuLinkList?p;

p=L->next;

while(p)

{

cout?<data?<<"\t";

p=p->next;

}

cout<

}

intmain()

{

inti,x,e,choose;

DuLinkList?L;

choose=-1;

while(choose!=0)

{

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

cout?<<"2.?創(chuàng)建雙向鏈表(前插法)\n";

cout?<<"3.?取值\n";

cout?<<"4.?查找\n";

cout?<<"5.?插入\n";

cout?<<"6.?刪除\n";

cout?<<"7.?輸出\n";

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

cout<<"請輸入數(shù)字選擇:";

cin>>choose;

switch(choose)

{

case1://初始化一個空的雙向鏈表

if(InitDuList_L(L))

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

break;

case2://創(chuàng)建雙向鏈表(前插法)

CreateDuList_H(L);

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

Listprint_L(L);

break;

case3://雙向鏈表的按序號取值

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

cin?>>?i;

if(GetElem_L(L,i,e))

{

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

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

}

else

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

break;

case4://雙向鏈表的按值查找

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

cin>>x;

if(LocateElem_L(L,x))

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

else

cout?<<"查找失敗!?"<

break;

case5://雙向鏈表的插入

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

cin?>>?i;

cin?>>?x;

if(ListInsert_L(L,?i,?x))

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

else

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

break;

case6://雙向鏈表的刪除

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

cin>>i;

if(ListDelete_L(L,?i))

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

else

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

break;

case7://雙向鏈表的輸出

cout?<<"當前雙向鏈表的數(shù)據(jù)元素分別為:\n";

Listprint_L(L);

cout?<<?endl;

break;

}

}

return0;

}

blog.csdn.net/rainchxy

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容