本文來自本人著作《趣學數據結構》
鏈表是線性表的鏈式存儲方式,邏輯上相鄰的數據在計算機內的存儲位置不一定相鄰,那么怎么表示邏輯上的相鄰關系呢?可以給每個元素附加一個指針域,指向下一個元素的存儲位置。如圖所示:
從圖中可以看出,每個結點包含兩個域:數據域和指針域,指針域存儲下一個結點的地址,因此指針指向的類型也是結點類型。
結點結構體的定義:
定義了結點之后,我們就可以把若干個結點連接在一起,形成一個鏈表:
是不是像一個鐵鏈子,一環扣一環的連在一起?
不管這個鐵鏈子有多長,只要我們找到它的頭,就可以拉起整個鐵鏈子,因此我們給這個鏈表設置一個頭指針,這個鏈表中的每個結點都可以找到了。
有時候為了操作方便,我們還會給鏈表增加一個不存放數據的頭結點:
就像是給鐵鏈子加個鑰匙扣:
我們可以看到剛才的鏈表每個指針都是指向下一個結點,都是朝向一個方向的,這樣的鏈表稱為單向鏈表,或單鏈表。
在順序表中,想找第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;
}
本文來自本人著作《趣學數據結構》。