單鏈表結點的插入和刪除是數據結構中很基本的操作。如果單鏈錶帶有頭結點,那麼可以把頭結點指針傳遞給插入和刪除函數;可如果對無頭結點的單鏈表進行上述操作,僅傳遞頭指針(指向第一個結點的指針),在插入或刪除操作改變鏈表頭指針時,將會有些問題。
下面我們通過一個實例說明這個問題。
//單鏈表結點定義
struct ListNode
{
int val;
ListNode* next;
ListNode(int value):val(value), next(NULL){}
};
採用“尾插法”向鏈表中插入結點,如果用以下代碼實現:
//insert.cpp version_1
void add2Tail(ListNode* pHead, int value)
{
ListNode* pNew = new ListNode(value);
if(!pHead) pHead = pNew; //?
else
{
ListNode* pNode = pHead;
while(pNode->next) pNode = pNode->next;
pNode->next = pNew;
}
}
上述代碼中,給add2Tail函數傳遞的是指向鏈表第一個結點的指針。可是這段代碼第四行有個很嚴重的問題,由於當原鏈表爲空,插入新結點時會改變“頭指針”pHead,但這種改變僅僅改動的是函數內部的局部變臉pHead,原鏈表“頭指針”(實參)實際上仍爲NULL。具體可參照下面的圖示:
tips:上圖中,當函數形參爲指向鏈表第一個結點的指針時,”傳值“方式傳遞的是地址100;而形參如果爲指向頭指針的指針,傳遞的是地址200。
因此我們要對這段代碼做一些改動,給add2Tail傳遞指向“頭指針”的指針,這樣便可達到當pHead==NULL時,插入新結點後也可改動外部的pHead。代碼如下:
//insert.cpp version_2
void add2Tail(ListNode** pHead, int value)
{
ListNode* pNew = new ListNode(value);
if(!*pHead) *pHead = pNew; //原鏈表爲NULL
else
{
ListNode* pNode = *pHead;
while(pNode->next) pNode = pNode->next;
pNode->next = pNew;
}
}
同理,刪除單鏈表中第一個含有某特定值的結點時,如果第一個結點就是待刪除結點的話,這將改變頭指針,因此也要給刪除函數傳遞指向頭指針的指針,代碼如下:
void removeNode(ListNode** pHead, int value)
{
if(!pHead || !*pHead) return;
ListNode *p2BeRemoved = NULL;
if((*pHead)->val == value) //第一個結點爲待刪除對象
{
p2BeRemoved = *pHead;
*pHead = (*pHead)->next;
}
else
{
ListNode* pNode = *pHead;
while(pNode->next && pNode->next->val != value) pNode = pNode->next;
if(pNode->next && pNode->next->val == value)
{
p2BeRemoved = pNode->next;
pNode->next = pNode->next->next;
}
}
if(p2BeRemoved)
{
delete p2BeRemoved;
p2BeRemoved = NULL; //刪除指針後,最後將其賦與NULL值,防止野指針
}
}