链表节点是由数据+指针组成的结构体。
typedef int dataType;
typedef struct ListNode ListNode;
struct ListNode
{
dataType data;
ListNode* next;
};
开辟一块内存空间存放节点,并将该节点地址作为返回值。
ListNode* createNode(dataType n)
{
ListNode* node = malloc(sizeof (ListNode));
node->data = n;
node->next = NULL;
return node;
}
当链表为空时,尾插等同于头插,由于没有哨兵位的头结点,所以需要对实参即存放链表首节点地址的指针变量进行修改,因此在传入参数时,我们选择传入实参的地址,同时形参也需要用二级指针来接收;当链表不为空时,需要遍历找到尾节点,将尾节点的next指向创建的新节点。
void SLTPushBack(ListNode** pplist, dataType n)
{
ListNode* node = createNode(n);
if (*pplist == NULL)
{
*pplist = node;
}
else
{
ListNode* cur = *pplist;
while (cur->next)//遍历找尾节点
cur = cur->next;
cur->next = node;//将尾节点链向新创建的节点 新节点作为新的尾节点
}
}
无论链表是否为空,头插都会修改首节点的地址,将创建的新节点作为新的首节点,需要修改实参即存放链表首节点地址的指针变量,所以需要传入实参的地址,同时形参也用二级指针来接收。
void SLTPushFront(ListNode** pplist, dataType n)
{
ListNode* node = createNode(n);
//将创建的新节点链向当前首节点 再将创建的新节点作为新的首节点
node->next = *pplist;
*pplist = node;
}
链表只有一个节点时,free该节点后,链表变为空链表,因此需要将首节点地址置为NULL;链表有多个节点时,需要遍历找到尾节点和尾节点的前一个节点,free尾节点并让尾节点的前一个节点的next指向NULL。
void SLTPopBack(ListNode** pplist)
{
if (*pplist == NULL)//链表为空时
return;
if ((*pplist)->next == NULL)//链表只有一个节点时
{
free(*pplist);
*pplist = NULL;
}
else//当链表有两个及以上节点时
{
ListNode* cur = *pplist;
ListNode* prev = NULL;
while (cur->next)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = NULL;
}
}
只要链表不为空,只需要free当前首节点,将第二个节点(链表只有一个节点时为NULL)作为新的首节点地址。由于free掉当前首节点后会无法找到第二个节点,所以在free之前需要保存首节点的下一个节点的地址。
void SLTPopFront(ListNode** pplist)
{
if (*pplist == NULL)
return;
struct ListNode* next = (*pplist)->next;
free(*pplist);
*pplist = next;
}
在实现后插之前,需要先实现查找节点的功能,这里我们用data值作为查找的依据,找到对应的节点就返回节点的地址,没有找到节点或链表为空就返回NULL。注意:在实际项目中,最好将唯一能够确定一个节点的数据作为查找的依据。
ListNode* SLTFindNode(ListNode* plist, dataType n)
{
ListNode* cur = plist;
while (cur)
{
if (cur->data == n)
return cur;
cur = cur->next;
}
return NULL;
}
后插是在查找到一个节点的基础上,将找到的这个节点的地址作为参数传入,把创建的新节点的next指向找到的这个节点的下一个节点(如果找到的节点是尾节点,则下一个节点为NULL),再把找到的这个节点的next指向创建的新节点。
void SLTInsertAfter(ListNode* pos, dataType n)
{
ListNode* node = createNode(n);
node->next = pos->next;
pos->next = node;
}
在不传入首节点地址的情况下实现前插,可以先进行后插,再将插入节点的data值与查找到的这个节点的data值进行交换,这样也相当于“变相”实现了前插。这种实现方式不仅避免了传递不必要的参数,并且当查找到的节点为首节点时,便需要进行头插,使用这种实现方式也不用修改首节点地址。
void SLTInsertBefore(ListNode* pos, dataType n)
{
SLTInsertAfter(pos, n);
dataType tmp = pos->data;
pos->data = pos->next->data;
pos->next->data = tmp;
}
要删除的节点是首节点时,直接复用头删接口即可。要删除的节点不是首节点时,需要遍历找到要删除的节点的前一个节点,将前一个节点的next指向要删除的节点的next,再free要删除的节点。
void SLTErase(ListNode** pplist, ListNode* pos)
{
if (*pplist == pos)//要删除的节点就是首节点
{
SLTPopFront(pplist);
return;
}
//要删除的节点不是首节点 遍历找到要删除的节点的前一个节点
ListNode* cur = *pplist;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
为了观察链表的逻辑结构,我们需要对链表的节点进行打印。
void SLTPrint(struct ListNode* plist)
{
struct ListNode* cur = plist;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}