目录
1.双向链表的介绍
2.带头双向循环链表
编辑 主体
编辑准备工作
编辑链表头部节点
编辑添加新节点
编辑打印
编辑头插头删尾插尾删
编辑头插
编辑尾插
编辑头删
编辑尾删
编辑查找
编辑pos位置前插入
编辑删除pos位置结点
结言:
前言回顾
C语言数据结构(单链表)
上期我们介绍了数据结构中,单链表的结构和实现,链表的每个结点都只能存储一个地址,用来访问下一个结点的数据,这样的话有很多事情就会显得很不方便,很拘束。那有没有更好的链表呢,本期交给大家带来双向链表
双向链表的节点通常包含两个部分:数据部分和指针部分。数据部分用于存储节点所包含的数据,指针部分包含两个指针,一个指向前一个节点,一个指向后一个节点。
双向链表的优点是可以在常数时间内在任意位置插入或删除节点,因为只需要修改相邻节点的指针即可。而在单向链表中,如果要在某个位置插入或删除节点,则需要遍历链表找到该位置的前一个节点。
双向链表相对于单向链表也有一些缺点。首先,双向链表需要额外的指针来存储前一个节点的地址,因此占用的内存空间比单向链表更大。其次,双向链表在插入或删除节点时需要修改两个指针的值,而单向链表只需要修改一个指针的值,因此操作起来更复杂。
到这里,想必大家就对双向链表有了个大概的认识,告诉你个小秘密哦:其实双向链表的实现比单链表要简单上不少,只是在数据的结构上双向链表看起来不让人觉得简单,别怕都是纸老虎,往下看一步步手撕它。
有没有感觉光听名字就觉得它不简单,还是那句话:别怕都是纸老虎。当你学会了这些对链表就会有了基本的掌握,不再害怕它。
在链表的实现前,我们先把链表所需要用到的先准备好,这样实现的时候会省心很多。
在对链表一系列的操作之前,我们首要需要的就是头节点,有了头节点后续数据的插入删除都会变得简单。
List* ListCreate()
{
List* newnode = (List*)malloc(sizeof(List));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->prev = newnode;
newnode->next = newnode;
newnode->val = 0;
return newnode;
}
因为是循环的双向链表,所以头结点初始化的时候,两个指针都是指向自己。
在插入数据中,必不可少的就是节点的创建,然后再链接到表中
List* BuyListNode(ListDatatype x)
{
List* newnode = (List*)malloc(sizeof(List));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->prev = NULL;
newnode->next = NULL;
return newnode;
}
当链表有了数据以后,为了直观的方便我们对数据增删查改的观察,打印就起到了作用
void ListPrint(List* pead)
{
assert(pead);
List* cur = pead->next;
printf("pead:");
while (cur != pead)
{
printf("《=》%d", cur->val);
cur = cur->next;
}
printf("\n");
}
在链表的头部和尾部分别进行对数据插入和删除
void ListPushFront(List* pead, ListDatatype x)
{
assert(pead);
List* newnode = BuyListNode(x);
List* cur =pead->next;
pead->next = newnode;
newnode->prev = pead;
newnode->next = cur;
cur->prev = newnode;
//ListInsert(pead->next, x);这是在pos位置前插入数据,这里可进行复用,后面会有实现
}
新结点的前指针指向前一个节点,后指针指向后一个节点,就进行了链接。
void ListPushBack(List* pead, ListDatatype x)
{
assert(pead);
List* newnode = BuyListNode(x);
List* cur = pead->prev;
pead->prev = newnode;
newnode->next = pead;
cur->next = newnode;
newnode->prev = cur;
//ListInsert(pead, x);这是在pos位置前插入数据,这里可进行复用,后面会有实现
}
在尾部插入和头插同理,需要改变的只有相邻节点间的指针
void ListPopFront(List* pead)
{
assert(pead);
assert(pead->next !=pead);
List* cur = pead->next;
List* second = cur->next;
free(cur);
pead->next = second;
second->prev = pead;
}
删除首节点,然后使头部指针指向后一个节点
void ListPopBack(List* pead)
{
assert(pead);
assert(pead->prev);
List* cur = pead->prev;
List* second = cur->prev;
free(cur);
second->next = pead;
pead->prev = second;
}
我们可以通过对链表的遍历,然后找到数据的位置,进行插入、删除、更改等操作
List* ListFind(List* pead, ListDatatype x)
{
assert(pead);
List* cur = pead->next;
while (cur != pead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
给定一个元素的数据,然后在链表中进行遍历,找到后返回元素地址
void ListInsert(List* pos, ListDatatype x)
{
assert(pos);
List* cur = pos->prev;
List* newnode = BuyListNode(x);
newnode->prev = cur;
cur->next = newnode;
newnode->next = pos;
pos->prev = newnode;
}
void ListErase(List* pos)
{
assert(pos);
List* cur = pos->prev;
List* second = pos->next;
cur->next = second;
second->prev = cur;
free(pos);
}
以上在pos位置进行的操作,只要我们有了地址,就可以进行操作,这些都可以对前面的头插头删尾插尾删进行复用。
当链表我们不再需要使用的时候,就需要将其进行销毁,因为这些空间都是在堆上进行开辟的
void ListDestroy(List* head)
{
assert(head);
List* cur = head->next;
while (cur != head)
{
List* tmp = cur;
cur = cur->next;
free(tmp);
}
free(head);
}
今天内容就到这里啦,时间不知不觉就8月了,距离暑假的结束也已经步入了倒计时,不知道大家这段时间有没有沉下心来好好学习呢。送给大家一句话:今天你做别人不愿意做的事,明天你就可以做到别人做不到的事。坚持的人只有少数,你只要静下心来,该有的都有得到,我与诸君共勉。当然也十分希望大家动动小手指,给博主一键三连,大家的支持是我前行的最大动力。