06. 双向链表
一、什么是双向链表
双向链表在数据结构上附加一个域,使它包含指向前一个单元的指针即可。其开销是增加了一个附加的链,它增加了空间的需求,同时也使得插入和删除的开销增加一倍。
typedef int ElementType;
typedef struct LNode
{
ElementType Data;
struct LNode * Prior;
struct LNode * Next;
} LNode, * List;
二、求表长
/**
* @brief 求双向链表的表长
*
* @param PtrL 双向链表
* @return int 双向链表的长度
*/
int Length(List PtrL)
{
List p = PtrL; // p指向表的第一个节点
int j = 0;
while (p != NULL)
{
p = p->Next;
j++; // 当前p指向的是第j个节点
}
return j;
}
三、查找元素
/**
* @brief 按序号查找
*
* @param PtrL 双向链表
* @param K 要查找的序号
* @return List 如果找到返回指向第K个的指针,否则返回NULL
*/
List FindKth(List PtrL, int K)
{
List p = PtrL; // p指向表的第一个节点
int i = 1;
if (PtrL == NULL)
{
printf("链表为空!\n");
return NULL;
}
if (i < 1 || i > Length(PtrL) + 1)
{
printf("查找的位置不合法\n");
return PtrL;
}
while (p != NULL && i < K)
{
p = p->Next;
i++; // 当前p指向的是第j个节点
}
return (i == K) ? p : NULL;
}
/**
* @brief 按值查找
*
* @param PtrL 双向链表
* @param X 要查找的元素
* @return List 如果找到返回指向X的指针,否则返回NULL
*/
List FindElement(List PtrL, ElementType X)
{
List p = PtrL; // p指向表的第一个节点
if (PtrL == NULL)
{
printf("链表为空!\n");
return NULL;
}
while (p != NULL && p->Data != X)
{
p = p->Next;
}
return p;
}
四、插入元素
- 先构造一个新节点,用 s 执行。
- 再找到链表的第 i-1 个结点,用 p 指向。
- 然后修改指针,插入结点(p 之后插入新结点是 s)。
/**
* @brief 插入一个元素
*
* @param PtrL 双向链表
* @param i 要插入的位置
* @param X 要插入的元素
*
* @return List 指向添加元素的双向链表的头指针
*/
List Insert(List PtrL, int i, ElementType X)
{
List p = NULL, s = NULL;
if (i < 1 || i > Length(PtrL) + 1)
{
printf("插入的位置不合法\n");
return PtrL;
}
if (PtrL == NULL)
{
PtrL = (List)malloc(sizeof(LNode));
PtrL->Data = X;
PtrL->Prior = PtrL->Next = NULL;
return PtrL;
}
if (i == 1) // 新节点插入在表头
{
s = (List)malloc(sizeof(LNode)); // 申请新节点
s->Data = X; // 新节点的数据域为表头
s->Prior = NULL; // 新节点的前驱为NULL
s->Next = PtrL; // 新节点指向原来的表头
PtrL->Prior = s; // 原来表头节点的前驱为新节点
return s;
}
p = FindKth(PtrL, i - 1); // 找到第i-1个节点
if (p == NULL) // 插入的位置不合法
{
printf("插入的位置不合法\n");
return PtrL;
}
s = (List)malloc(sizeof(LNode)); // 申请新节点
s->Data = X;
s->Next = p->Next; // 新节点指向p的下一个节点
s->Prior = p; // 新节点的前驱为p
if (p->Next != NULL) // 如果p的后继不为NULL
{ // p的后继的前驱指向新节点
p->Next->Prior = s;
}
p->Next = s; // 节点的后继指向新节点
return PtrL;
}
五、删除元素
- 先找到链表的第 i 个节点,用 p 指向。
- 然后修改指针,删除 p 所指节点。
- 最后释放 p 所指节点的空间。
/**
* @brief 删除一个节点
*
* @param PtrL 双向链表
* @param node 要删除的节点
* @return List 指向删除后的双向链表的头指针
*/
List DeleteNode(List PtrL, LNode * node)
{
List p = PtrL, s = NULL;
if (PtrL == NULL)
{
printf("链表为空!\n");
return NULL;
}
if (node == PtrL) // 如果是头节点
{
if (node->Next == NULL) // 如果只有一个节点
{
free(PtrL);
return NULL;
}
s = PtrL; // s指向表头
p->Next->Prior = NULL;
p = p->Next; // 删除表头,表头指向下一个节点
free(s); // 释放s
return p;
}
if (node->Next == NULL) // 如果是最后一个节点
{
node->Prior->Next = NULL;
}
else
{
node->Prior->Next = node->Next;
node->Next->Prior = node->Prior;
}
free(node);
return PtrL;
}
/**
* @brief 删除一个元素
*
* @param PtrL 双向循环链表
* @param i 要删除元素的位置
* @return List 指向删除后的双向循环链表的头指针
*/
List DeleteByPositon(List PtrL, int i)
{
List p = NULL, s = NULL;
if (PtrL == NULL)
{
printf("链表为空!\n");
return NULL;
}
if (i < 1 || i > Length(PtrL))
{
printf("删除的位置不合法\n");
return PtrL;
}
p = FindKth(PtrL, i); // 找到第i个节点
if (p == NULL)
{
printf("第%d个节点不存在!\n", i);
}
else
{
PtrL = DeleteNode(PtrL, p);
}
return PtrL;
}
/**
* @brief 按元素删除
*
* @param PtrL 双向循环链表
* @param X 要删除的元素
* @return List 指向删除后的双向循环链表的头指针
*/
List DeleteByElement(List PtrL, ElementType X)
{
List p = PtrL;
LNode * node = FindElement(PtrL, X);
if (p == NULL)
{
printf("链表为空!\n");
return NULL;
}
if (node == NULL)
{
printf("要删除的元素不存在!\n");
}
else
{
PtrL = DeleteNode(PtrL, node);
}
return PtrL;
}
六、遍历链表
/**
* @brief 遍历双向循环链表
*
* @param PtrL 双向循环链表
*/
void PrintDoubleCircularLinkedList(List PtrL)
{
List p = PtrL;
while (1)
{
printf("%d ", p->Data);
if (p->Next == PtrL)
{
break;
}
p = p->Next;
}
printf("\n");
}