目录
[利用 ListInsert()函数改造头插尾插函数](#利用 ListInsert()函数改造头插尾插函数)
[利用ListEarse()函数改造头删 尾删函数](#利用ListEarse()函数改造头删 尾删函数)
链表的分类
- 单向/双向
单向列表:每一个结点结构中只保存下一结点的地址,所以很难从后一结点找到前一节点;
双向列表:每一个结点结构中不仅保存下一结点的地址,还保存上一节点的地址;方便寻找前一节点和后一节点;
- 带头/不带头
带头:在头结点之前有一个哨兵位结点,哨兵位的数据域不存储有效数据,指针域指向头结点;
不带头:没有哨兵位结点,尾插尾删考虑头结点情况;
- 循环/非循环
循环:头结点与尾结点相连;
非循环:头结点与尾结点不相连;
上述情况相互组合,共有8种情况, 实际中使用的链表数据结构,都是带头双向循环链表,带头双向循环链表虽然结构复杂,但是其结构具有很多优势,实现反而简单;
带头双向循环链表的实现
带头双向循环链表的结构
cpp
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;//前址域-存放前一个结点的地址
LTDataType data;//数据域
struct ListNode* next;//后址域-存放后一个结点的地址
}ListNode;
逻辑图:
物理图:
带头双向循环链表的结构示意图
空链表结构示意图
由图可知,head->prev=head; head->next=head;
单结点链表结构示意图
由图可知:
head->next=FirstNode;
head->prev=FirstNode;
FirstNode->prev=head;
FirstNode->next=head;
多结点链表结构示意图
由图可知:
head->next=firstnode;
head->prev=tail;
tail->next=head;
firstnode->prev=head;
链表创建结点
cpp
//创建链表结点,返回链表结点地址
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc failed:");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
双向链表初始化
注:函数调用时得到动态开辟的链表空间起始地址的两种方案如下
方案一: 当传参时为链表结点的地址,函数的形参设计为二级指针,只有通过传址调用,可以将动态开辟的链表的起始地址带出函数;
方案二: 设计函数的返回类型为结点指针,返回动态开辟的链表结点指针,如此可以得到链表空间的起始地址;
cpp
//初始化链表(空链表)
ListNode* ListInit()
{
//创建哨兵位结点
ListNode* head = BuyListNode(0);//0不是有效数据
//初始化哨兵位结点的指针域
head->next = head;
head->prev = head;
return head;
}
销毁双向链表
- 循环遍历释放结点,包含哨兵位结点;
- 释放前保存下一结点地址,避免地址丢失;
cpp
//销毁链表,包含哨兵位结点
void DestoryList(ListNode* phead)
{
assert(phead);
//创建寻址指针
ListNode* cur = phead;
//断开循环链表
phead->prev->next = NULL;
while (cur != NULL)
{
//记录下一结点地址
ListNode* next = cur->next;
//释放当前结点
free(cur);
//寻找下一节点
cur = next;
}
return;
}
打印双向链表
- 循环遍历链表打印数据,不显示哨兵位结点的数据域;
- 以哨兵位头结点作为结束标志;
cpp
void PrintList(ListNode* phead)
{
assert(phead != NULL);
ListNode* cur = phead->next;
printf("phead<==>");
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
双向链表尾插
- 尾插先找尾,哨兵位的前址域即为尾结点即tail=head->prev;
- 当链表为空时,连接的逻辑关系相同(创建三个指针变量,按照新结点的前址域指向谁,谁指向新结点,新结点的后址域指向谁,谁指向新结点进行连接);
cpp
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
//寻找尾结点
ListNode* tail = phead->prev;
//创建新结点
ListNode* newnode = BuyListNode(x);
//尾插
newnode->prev = tail;
tail->next = newnode;
newnode->next = phead;
phead->prev = newnode;
}
尾插函数测试
cpp
void Test1()
{
ListNode* plist=ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
PrintList(plist);
}
int main()
{
Test1();
return 0;
}
运行结果:
双向链表头插
- 头插前先保存哨兵位结点的下一节点即原先真正的首节点;
- 按照按照新结点的前址域指向谁,谁指向新结点,新结点的后址域指向谁,谁指向新结点进行连接从而实现头插,链表为空时,头插逻辑仍然相同;
cpp
//链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
//保存原先的首节点
ListNode* firstnode = phead->next;
//创建新结点
ListNode* newnode = BuyListNode(x);
//头插
newnode->prev = phead;
phead->next = newnode;
newnode->next = firstnode;
firstnode->prev = newnode;
}
头插函数测试
cpp
void Test2()
{
ListNode* plist = ListInit();
ListPushFront(plist, 10);
ListPushFront(plist, 20);
ListPushFront(plist, 30);
ListPushFront(plist, 40);
ListPushFront(plist, 50);
PrintList(plist);
}
int main()
{
Test2();
return 0;
}
运行结果:
双向链表尾删
- 链表中只剩哨兵位结点,此时链表为空,不再进行尾删;
- 尾删前记录前一节点的地址,方便修改逻辑关系;
cpp
//链表尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
//链表中只剩哨兵位的情况
assert(phead->next != phead);
//查找尾结点
ListNode* tail = phead->prev;
//保存尾结点的上一节点
ListNode* tailprev = tail->prev;
//尾删
free(tail);
//建立链接关系
tailprev->next = phead;
phead->prev = tailprev;
}
尾删函数测试
cpp
void Test3()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
PrintList(plist);
ListPopBack(plist);
PrintList(plist);
ListPopBack(plist);
PrintList(plist);
ListPopBack(plist);
PrintList(plist);
}
int main()
{
Test3();
return 0;
}
运行结果:
双向链表头删
- 链表中只剩哨兵位结点,此时链表为空,不再进行头删;
- 头删前记录下一节点的地址,方便修改逻辑关系;
cpp
//链表头删
void ListPopFront(ListNode* phead)
{
assert(phead);
//只剩哨兵位,不再头删
assert(phead->next != phead);
//保存原先的首节点
ListNode* head = phead->next;
//保存首结点的下一节点
ListNode* headnext = phead->next->next;
//头删
free(head);
//建立链接关系
headnext->prev = phead;
phead->next = headnext;
}
头删函数测试
cpp
void Test4()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
PrintList(plist);
ListPopFront(plist);
PrintList(plist);
ListPopFront(plist);
PrintList(plist);
ListPopFront(plist);
PrintList(plist);
}
int main()
{
Test4();
return 0;
}
运行结果:
双向链表查找
- 循环遍历链表,从首节点开始遍历,以哨兵位头结点作为结束标志;
- 根据数据域进行查找,找到返回数据域的结点地址,找不到返回空指针;
cpp
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
//创建遍历指针
ListNode* cur = phead->next;
//遍历链表
while (cur != phead)
{
if ((cur->data) == x)
{
//找到返回下标
return cur;
}
cur = cur->next;
}
//没找到返回空指针
return NULL;
}
双向链表pos位置前插
- 前插时保存pos位置的前一个节点,方便修改逻辑关系;
- 按照按照新结点的前址域指向谁,谁指向新结点,新结点的后址域指向谁,谁指向新结点进行链接;
cpp
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos != NULL);
//创建新结点
ListNode* newnode = BuyListNode(x);
//保存pos位置的前一个结点
ListNode* posprev = pos->prev;
//前插
newnode->prev = posprev;
posprev->next = newnode;
newnode->next = pos;
pos->prev = newnode;
}
插入函数测试
cpp
void Test5()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
int x = 0;
printf("请输入查找的数值:");
scanf("%d", &x);
ListNode* pos = ListFind(plist, x);
if (pos == NULL)
{
printf("要查找的值不存在\n");
return;
}
//在查找到数值前插入100
ListInsert(pos, 100);
PrintList(plist);
}
int main()
{
Test5();
return 0;
}
运行结果:
双向链表删除pos位置的结点
- 链表删除pos位置处的结点前先保存前结点和后结点的地址,方便处理链接关系;
cpp
//双向链表删除pos位置
void ListEarse(ListNode* pos)
{
assert(pos);
//保存pos位置处的前一个和后一个结点;
ListNode* posprev = pos->prev;
ListNode* posnext = pos->next;
//删除pos位置结点
free(pos);
//建立前后节点的链接关系
posprev->next = posnext;
posnext->prev = posprev;
}
删除函数测试
cpp
void Test6()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
PrintList(plist);
int x = 0;
printf("请输入删除的数值:");
scanf("%d", &x);
ListNode* pos = ListFind(plist, x);
if (pos == NULL)
{
printf("要删除的值不存在\n");
return;
}
ListEarse(pos);
PrintList(plist);
}
int main()
{
Test6();
return 0;
}
运行结果:
利用 ListInsert()函数改造头插尾插函数
尾插函数改造版本
cpp
void Listpushback(ListNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead, x);
}
头插函数改造版本
cpp
void Listpushfront(ListNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
利用ListEarse()函数改造头删 尾删函数
头删函数改造版本
cpp
void Listpopfront(ListNode* phead)
{
assert(phead);
//只剩哨兵位,不再头删
assert(phead->next != phead);
ListEarse(phead->next);
}
尾删函数改造版本
cpp
void Listpopback(ListNode* phead)
{
assert(phead);
//链表中只剩哨兵位的情况
assert(phead->next != phead);
ListEarse(phead->prev);
}
计算双向链表长度
cpp
int ListLength(ListNode* phead)
{
assert(phead);
int size = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}