鏈表是線性表的鏈式存儲方式,邏輯上相鄰的數(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;
}