目录
- 链表介绍
- 初始化链表
- 销毁链表
- 查找节点
- 打印链表
- 增加节点
- 尾插
- 头插
- 在指定位置之后插入节点
- 删除节点
- 尾删
- 头删
- 删除指定位置节点
- 链表判空
链表介绍
前面说到,链表的结构一共有八种:带头单向循环链表、带头单向非循环链表、带头双向循环链表、带头双向非循环链表、无头单向循环链表、无头单向非循环链表、无头双向循环链表、无头双向非循环链表。
在这八种结构中,我们只挑两种来进行刨析,即无头单向非循环链表和带头双向循环链表。
而今天我们要介绍的双向链表就是带头的,头结点head也被称为哨兵位,如果链表为空,则链表只有一个哨兵位。双向链表的结构为
typedef int LTDataType; //方便以后修改双向链表存储的数据类型
typedef struct ListNode
{
LTDataType val;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
分别用一个变量存储数据,前驱指针prev指向前节点,next指针指向后节点,这样链表就可以进行随机访问。
初始化链表
LTNode* LTBuyNode(LTDataType x) //尾插,头插等都需要创建新的节点,不妨封装一个函数,每个节点的next,prev指针都先指向自己,
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTDataType));
if (newnode == NULL)
{
perror("malloc failed");
exit(1);
}
newnode->val = x;
newnode->prev = newnode->next = newnode;
return newnode;
}
因为插入都需要创建节点,所以封装一个函数
注意:这些节点的next指针和prev指针都指向自己,这是为了兼容空链表只有一个哨兵位时,和后面插入节点时利用这两个指向自己的指针调成被影响的节点的指针
//void LTInit(LTNode** pphead)
//{
// assert(pphead);
// *pphead = LTBuyNode(-1);
//}
//优化接口一致性版本
LTNode* LTInit(LTNode* phead)
{
LTNode* newnode = LTBuyNode(-1);
return newnode;
}
首先,我们要对链表进行初始化,上面说了空双向链表是只有一个哨兵位,所以我们创建一个节点作为哨兵位.
销毁链表
//void LTDestroy(LTNode** pphead)
//{
// assert(pphead && *pphead); //*pphead防止释放空链表
// LTNode* pcur = (*pphead)->next;
// while (pcur != *pphead)
// {
// LTNode* Next = pcur->next;
// free(pcur);
// pcur = NULL;
// pcur = Next;
// }
// free(*pphead);
// *pphead = NULL;
//}
//优化接口一致性版本
void LTDestroy(LTNode* pphead)
{
assert(pphead);
LTNode* pcur = pphead->next;
while (pcur != pphead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = NULL;
pcur = Next;
}
free(pphead);
pphead = NULL; //这里设置NULL了不影响实参,需要手动设置NULL,但是释放了空间是同一块空间,
}
销毁链表,从头结点的后一个结点处开始向后遍历并释放结点,直到遍历到头结点时,停止遍历并将头结点也释放掉。
查找节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
if (!LTEmpty(phead))
{
return NULL;
}
else
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->val == x)
{
return pcur;
}
pcur = pcur->next;
}
}
return NULL;
}
给定一个值,在链表中寻找与该值相同的结点,若找到了,则返回结点地址;若没有找到,则返回空指针(NULL)。如果为空链表,就直接返回NULL.这里因为双向楼面是循环的,用pcur从头结点(哨兵位下一个节点)开始遍历,如果回到哨兵位表示已经查找完一圈,没找到。
打印链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->val);
pcur = pcur->next;
}
}
打印双向链表时也是从头结点的后一个结点处开始向后遍历并打印,直到遍历哨兵位结点处时便停止遍历和打印(哨兵位结点数据不打印)。
增加节点
尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
尾插,申请一个新结点,将节点插入到旧尾节点之后,改变旧尾节点的next指针和新尾节点的next指针和prev指针,改变哨兵为的prev指针
头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
头插,即申请一个新结点,将新结点插入在哨兵位后即可。
在指定位置之后插入节点
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
我们只需申请一个新结点插入到指定位置结点和其后一个结点之间即可。
删除节点
尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(LTEmpty(phead));
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;
}
头删
头删,即释放哨兵位的后一个结点,并建立哨兵位节点与被删除结点的后一个结点之间的双向关系即可。
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(LTEmpty(phead));
LTNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
删除指定位置节点
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
删除指定位置结点,释放掉目标结点后,建立该结点前一个结点和后一个结点之间的双向关系即可。
链表判空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next != phead;
}
链表判空,即判断头结点的前驱或是后驱指向的是否是自己即可(因为是循环)