一、关于逻辑概念
链表包含有带头和不带头,循环和不循环这些结构。前面我们讲到单链表的实现,主要是一个节点一个节点存放数据,每个节点依次指向下一个节点的地址。带头双向循环链表主要是多了一个指向数据的哨兵节点,它是不放数据的;每个节点多了一个指向前一个节点的指针,就形成了双向;最后我们让尾节点指向头节点,头节点的前一个节点指向尾节点,就形成了封闭的循环,这就是带头双向循环链表的大致思路。
二、链表的实现
我们已经了解了逻辑概念,接下来就来实践一下,实践是检验真理的唯一标准。
1、创建返回链表的头结点.
注意我们这个节点是不放数据的,因为这个时候还没有插入数据,所以我们让这个节点的两个指针都指向自己就可以了。
2、链表销毁
3、链表打印
这里我的思路出现一点问题,注释部分可以不用理会,阿里嘎多。
4、链表尾插
当我们插入数据时,我们创建一个函数去申请一个节点用来放入数据,最后尾插头插都引用这个数据,整体代码就会比较优雅,后面头插就不过多说明了,阿里嘎多。
5、链表尾删
6、链表头插
7、链表头删
8、链表查找
9、在pos位置的前面插入数据
这里pos都引用了查找函数,后面源码会提到
10、删除pos位置的节点数据
三、源码分享
从上到下依次是头文件,函数文件和测试文件。测试文件调用的是头文件,头文件调用函数文件。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
#define _CRT_SECURE_NO_WARNINGS
#include"Dlist.h"
ListNode* ListCreate() //头结点(哨兵位)
{
//创建返回链表的头结点
ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
pHead->prev = pHead;
pHead->next = pHead;
return pHead;
}
ListNode* ListBuyNewNode(LTDataType x)
{
ListNode* pp = (ListNode*)malloc(sizeof(ListNode));
if (!pp)
{
perror("malloc fail");//如果扩容失败,显示扩容失败提示
}
pp->next = pp;
pp->prev = pp;
pp->data = x;
return pp;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
ListNode* tail = pHead;
while (pHead != pHead->next)
{
tail = pHead->prev;
pHead->prev = tail->prev;
pHead->prev->next = pHead;
free(tail);
}
free(pHead);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
ListNode* tail = pHead;
while (tail->next != pHead)
{//pHead->next != pHead此时* pHead只是对地址进行引用,不会改变原来的地址,
//所以两个地址一直不会相等,二级指针则会修改原地址的值。
printf("%d\n", tail->next->data);
tail = tail->next;
}
printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
ListNode* newnode = ListBuyNewNode(x);
ListNode* tail = pHead->prev;
tail->next = newnode;
newnode->prev = tail;
pHead->prev = newnode;
newnode->next = pHead;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead->next != pHead);//避免链表中没有数据仍在删除造成越界
pHead->prev = pHead->prev->prev;
pHead->prev->next = pHead;
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListNode* newnode = ListBuyNewNode(x);
ListNode* tail = pHead;
newnode->next = pHead->next;
pHead->next->prev = newnode;
//倒反天罡会死循环,先把后面的连接,再前。否则丢失数据。
tail->next = newnode;
newnode->prev = tail;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead->next != pHead);//避免链表中没有数据仍在删除造成越界
pHead->next = pHead->next->next;
pHead->next->prev = pHead;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
ListNode* tail = pHead->next;
while (tail != pHead)
{
if (x == tail->data)
{
/*printf("找到了,在%p",tail);*/
return tail;
}
tail = tail->next;
}
printf("找不到了");
return -1;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
ListNode* newnode = ListBuyNewNode(x);
ListNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos->next != pos);//其实不用的,查找的时候找不到是没有pos的
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
}
#define _CRT_SECURE_NO_WARNINGS
#include"Dlist.h"
void test1()
{
ListNode* pHead = ListCreate();
ListPushBack(pHead, 1);
ListPushBack(pHead, 2);
ListPushBack(pHead, 3);
ListPushBack(pHead, 4);
ListPushBack(pHead, 5);
ListPopBack(pHead);
ListPopBack(pHead);
ListPopBack(pHead);
ListPushFront(pHead, 7);
ListPushFront(pHead, 8);
ListPushFront(pHead, 9);
ListPopFront(pHead);
ListPopFront(pHead);
/*ListPrint(pHead);*/
ListNode* pos = ListFind(pHead, 1);
ListInsert(pos, 3);
ListErase(pos);
pos = ListFind(pHead, 7);
ListErase(pos);
pos = ListFind(pHead, 3);
ListErase(pos);
pos = ListFind(pHead, 2);
ListErase(pos);
ListPrint(pHead);
ListDestory(pHead);
}
int main()
{
test1();
return 0;
}
四、总结
带头双向循环链表相比单链表,结构上更加完善,插入删除也更加方便。在理解了单链表的结构后,再来理解带头双向循环链表,就会变得简单很多,学习之路亦是如此,一步一脚印!
写的不好的地方欢迎多多指正批评,道阻且长,加油吧!