单链表结点的插入和删除是数据结构中很基本的操作。如果单链表带有头结点,那么可以把头结点指针传递给插入和删除函数;可如果对无头结点的单链表进行上述操作,仅传递头指针(指向第一个结点的指针),在插入或删除操作改变链表头指针时,将会有些问题。
下面我们通过一个实例说明这个问题。
//单链表结点定义
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值,防止野指针
}
}