如需轉載請與作者聯絡
作者姓名: 快果(linchao14)
郵件地址: linchao14@xinhuanet.com
本文
我在論壇上看到有網友說,在C++學習過程中認為鏈表部分不是太好理解,在這我想寫寫我學習鏈表的一些體會。希望能對這些朋友有所協助!如果大家有什麼好的方法,也不妨拿出來讓我們分享!
首先,要明白C++引入鏈表概念是為了什麼,也就是說鏈表的作用是什嗎?其次,要弄清鏈表的本質;最後是鏈表的使用方法。那我先舉個例子,如果建立一個有關學生的分數管理系統需要建立一個結構,如下:
struct Student
{
char Name[20];
long id;
float score;
}
如果要是學生的人數確定,只要聲明一個這個結構的數組就OK了。可是如果人數是不確定的,那就需要用到鏈表了。鏈表之所以能解決這個問題,這就涉及到了鏈 表的本質問題——使用動態記憶體分配技術。其實現在的資料庫系統的內部實現正是在動態分配技術基礎上實現的。要把結構變成鏈表,只需在結構成員中加入一個自 身的指標成員(一會有解釋)。到這裡有一個問題是需要注意的:結構成員不能是自身結構的變數,但指標可以。為什麼需要加入一個自身結構的指標,請看圖。畢竟圖形是非常直觀的。
我們清晰的看到,每個結構成員在記憶體中的地址是不連續的,“鏈表”這個名字起的非常形象,就是把結構成員像鏈子一樣,一環扣一環的串起來,以便於查詢、刪除和插入。能擔此重任的只有指標!看:
一目瞭然,在結構中引入自身的結構指標成員,是為了儲存下一個結構成員的地址。使它們串起來! 現在我們來看一個完整的例子(遍曆鏈表也稱查詢鏈表),有一個概念要說一下——結點,在這個結構中,每個Student結構變數稱為結點。
#include <iostream.h>
struct Student
{
long lId;
float fScore;
Student* pNext;
};
Student* pHead; //建立表頭指標
Student* Create()
{
Student* pStart; //建立結點指標
Student* pEnd; //建立結點尾指標,用於在其後插入結點
pStart = new Student; //建立一個結點
cin >> pStart->lId >> pStart->fScore; //給結點賦值
pHead = NULL; //初始化表頭指標
pEnd = pStart; //第一個結點的開始地址也裝入該結點的尾指標中,因為pStart要裝入下一個結點的開始地址
while(pStart->lId != 0)
{
if (pHead ==NULL) //如果是新表的時候...
pHead = pStart; //表頭指標裝入第一個結點的地址
else
pEnd->pNext = pStart; //不是新表的話,把新的結點接在上一個結點的後面
pEnd = pStart;
pStart = new Student; //為pStart分配新的空間,準備接下一個結點
cin >> pStart->lId >> pStart->fScore;
}
pEnd->pNext = NULL; //當lId被置入0時,跳出迴圈,當前的結點是最後一個結點,所以指向NULL
delelte pStart; //因為在跳出迴圈前,pStart被多分配了一個新的空間,所以要回收!
return (pHead);
void Print (Student* head)
{
cout << " List of the items/n";
while (head)
{
cout << head->lId << "," << head -> fScore;
pHead = pHead->pNext; //這條語句比較重要,它配合迴圈使表頭指標移動,為的是按順序輸出成員值
}
}
void main()
{
Print(Create()); //利用Create()返回的pHead指標的值調用Print()函數
}
刪除鏈表結點
鏈表刪除操作要保證不破壞鏈表的連結關係,這也是痛點之所在——一個結點刪除後,它的前一個結點的指標成員應指向它的後一結點,這就不會因為刪除操作而使連結中斷。關係如:
刪除鏈首結點:
刪除非鏈首結點:
下面的函數實現了鏈表結點的刪除
void Delete(Student* head, long number)
{
Student* p;
if (!head)
{
cout << "This is the last node /n";
return;
if (pHead->lId == number) //刪除鏈首的結點
{
p=head;
head = pHead->pNext;
delete p;
cout << "the head of list have been deleted/n";
return;
}
/* 總之,最重要的是操作順序。如果p不指向鏈表的首結點,則當pHead指向鏈首結點的下一個結點後,原鏈首結點脫鏈,導致結點地址丟失,以致無法釋放堆空 間。如果先刪除鏈首結點,則pHead指標無法向由鏈首結點匯出的下一結點地址。p指標的作用也就起了緩衝的作用。*/
for (Student* pGuard = head; pGuard->pNext; pGuard = pGuard->pNext)
{
if (pGuard->pNext->lId == number) //pGuard指向的是要刪結點的前一個結點
{
p = pGuard->pNext; //p指向的是要刪的結點
pGuard->pNext = p->pNext; //把要刪的結點的後一結點成為其前一結點的下一結點(使要刪的結點脫鏈)
delete p;
cout << "have been deleted!/n";
return;
}
}
cout << "not found/n";
}
/*在找到待刪除結點時,pGuard指向待刪除結點的前一個結點是重要的。否則,待刪除結點的前一結點地址丟失,其pNext成員無法與待刪除結點的後一結點連結。在資料結構中,通常稱pGuard指標為“哨兵”。*/
在main() 主函數中的調用如下:
pHead = Create(); //這一步不可丟下,這是為了使pHead指向表頭
Delete(pHead, fScore); //把number改成相應的成績數
插入鏈表結點
同樣,插入操作也不能破壞連結關係。應將插入結點的pNext成員指向它的後一個結點,然後將前一個結點的pNext成員指向新插入的結點,這樣就得了新的鏈表。
我們假設建立鏈表時,結點是按lId值由小到大排列的。
void insert (Student* head, Student* stud)
{
if (head ==NULL) //將結點插入一個空的鏈表,操作完成後這個鏈表中就有一個新插入的結點
head = stud;
stud.pNext = NULL;
return;
}
if (head->lId > stud->lId) //將一個結點插入鏈表的頭部
{
stud.pNext = head;
head = stud;
return;
}
Student* pGuard = head; //將結點插入到兩個結點之間
while (pGuard->pNext && pGuard->pNext->lId > stud->lId)
pGuard = pGuard->pNext; //配合迴圈尋找合格結點
stud->pNext = pGuard->pNext; //使插入結點指向當前結點的下一個結點(從右往左想)
pGuard->pNext = stud; //將當前結點指向插入結點
}
到此為止,鏈表的控制已經敘述完畢。總之,其中的精髓之處是如何按排刪除或插入結點的操作順序。用一句白話說就是在操作之前想好下一步,給自己留一條後路 ——當你意識到下一步沒辦法走的時候,肯定是上一步沒有作好準備工作。這隻是我個人的一些感受,也不知合適與否,反正書上是不會這麼寫的!