文章目录
1.带头双向循环链表
带头双向循环链表:结构最复杂,一般用来单独存储数据。实际中使用的链表结构都是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现时反而简单。
因为在上一个链表篇,我们已经把链表的大部分性质都已经讲了,这里我们就直接实现带头双向循环链表吧。
2. 模拟实现
2.1 准备工作
把程序需要的头文件都写上,然后我们在创建一个结构体。于单链表不同的地方就已经显示出来了。单链表只有指向下一个节点的指针,而现在我们要实现的双向链表,所以还需要添加一个指向上一个节点的指针。
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef struct ListNode
{
struct ListNode* prev;
int data;
struct ListNode* next;
}LNode;
2.2 初始化与节点创建
不同于上一篇文章的单链表,这个链表是一个带头的链表。我们需要先初始化一个头节点出来.
这个头节点是不能删的,头节点的值可以随便填,后续打印和查找都不会经过头节点。
利用二级指针的目的是为了成功修改phead的值,如果我们不用二级指针的话就只是传值调用,不会对参数进行实质的修改。
在初始化的函数中,因为我们需要malloc空间,而后续大量的插入函数也需要malloc空间,为了方便就写成了一个创建节点的函数,体现了函数简化的好处。
//test.c
LNode* phead ;
InitList(&phead);
//初始化
void InitList(LNode** phead)
{
*phead = BuyListNode(0);
(*phead)->next = *phead;
(*phead)->prev = *phead;
}
//创建节点
LNode* BuyListNode(int x)
{
LNode* tmp = (LNode*)malloc(sizeof(LNode));
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
tmp->data = x;
tmp->next = NULL;
tmp->prev = NULL;
return tmp;
}
2.3 链表尾插
在单链表中我们尾插需要遍历链表找到最后一个节点然后插入新的节点,但是现在因为我们是双向循环链表,我们可以直接找到最后一个节点,就是头节点的前一个节点。
提问:为什么这里可以使用一级指针
回答:因为这里我们不再需要修改phead指针的值,我们要修改的是phead指向地址的内容,该内容和phead的值没有关系,即使修改了phead指向地址的内容,phead的值还是不会改变的。
//链表尾插
void PushBackList(LNode* phead, int x)
{
assert(phead);
LNode* newnode = BuyListNode(x);
LNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
2.4 链表尾删
注意:头节点是不能被删除的
和尾插类似的情况,我们不在需要遍历寻找最后一个节点。直接拿到最后一个节点改动指向就可以的。
//链表尾删
void PopBackList(LNode* phead)
{
assert(phead->next != phead);//防止头节点被删除
assert(phead);
LNode* tail = phead->prev;
LNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
tail->next = NULL;
tail->prev = NULL;
free(tail);
tail = NULL;
}
2.5 链表打印
注意不要打印头节点,当节点指向头节点时就停止打印
//打印链表
void PrintList(LNode* phead)
{
assert(phead);
LNode* next = phead->next;
while (next != phead)
{
printf("%d ", next->data);
next = next->next;
}
printf("\n");
}
2.6 链表头删
大体逻辑和尾删相似
//链表头删
void PopFrontList(LNode* phead)
{
assert(phead->next != phead);//防止头节点被删除
assert(phead);
LNode* next = phead->next;
LNode* tmp = next->next;
phead->next = tmp;
tmp->prev = phead;
next->next = NULL;
next->prev = NULL;
free(next);
next = NULL;
}
2.7 链表头插
找到头节点后的第一个节点,把新节点插入其中就可以了。
//链表头插
void PushFrontList(LNode* phead,int x)
{
assert(phead);
LNode* newnode = BuyListNode(x);
LNode* next = phead->next;
next->prev = newnode;
newnode->next = next;
phead->next = newnode;
newnode->prev = phead;
}
2.8 链表的查找
遍历除了头节点外的节点,如果找到了就返回该节点,没找到就返回NULL
//链表的查找
LNode* FindList(LNode* phead, int x)
{
assert(phead);
LNode* cur = phead->next;
while(cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
2.9 在链表的pos位置前插入数据
配合查找函数使用,如果pos为头节点的下一个节点,我们可以复用前面实现的链表头插。这就是函数的复用。
//在链表的pos位置前插入数据
void InsertList(LNode* phead, LNode* pos, int x)
{
assert(phead);
assert(pos);
//如果pos是phead后的第一个节点,相当于头插
if (pos == phead->next)
PushFrontList(phead, x);
//else if (pos == phead->prev)//如果pos是phead前的第一个节点,相当于尾插
// PushBackList(phead, x); error
else
{
LNode* prev = pos->prev;
LNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
}
2.10 删除pos位置的节点
同样也是配合查找函数使用,该函数有两次复用。如果pos是phead后的第一个节点,相当于头删,如果pos是phead前的第一个节点,相当于尾删。
再一般情况下,删除也是挺简单的,提前找到pos前和pos后的节点就是了
//删除pos位置的节点
void EraseList(LNode* phead, LNode* pos)
{
assert(pos);
assert(phead);
//如果pos是phead后的第一个节点,相当于头删
if (pos == phead->next)
PopFrontList(phead);
else if (pos == phead->prev)//如果pos是phead前的第一个节点,相当于尾删
PopBackList(phead);
else
{
LNode* prev = pos->prev;
LNode* next = pos->next;
prev->next = next;
next->prev = prev;
pos->next = NULL;
pos->prev = NULL;
free(pos);
pos = NULL;
}
}
2.11 链表的销毁
使用了动态内存开辟,一定要记得释放,这是一个好习惯,可以有效的防止内存泄漏。
可能有人说:当程序结束时,系统会自动回收内存的。
是这样没错,但是如果程序要一直跑呢,许多大型的程序都是要一直跑的,可不能每次调用一个功能就泄漏一点内存,这样程序迟早会崩溃的。
//链表销毁
void DestoryList(LNode* phead)
{
LNode* cur = phead->next;
while (cur != phead);
{
LNode* prev = cur;
cur = cur->next;
free(prev);
prev = NULL;
}
free(phead);
phead = NULL;
}
3.代码整合
//dlist.h
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef struct ListNode
{
struct ListNode* prev;
int data;
struct ListNode* next;
}LNode;
//初始化
void InitList(LNode** phead);
//创建节点
LNode* BuyListNode(int x);
//链表尾插
void PushBackList(LNode* phead, int x);
//链表尾删
void PopBackList(LNode* phead);
//打印链表
void PrintList(LNode* phead);
//链表头删
void PopFrontList(LNode* phead);
//链表头插
void PushFrontList(LNode* phead,int x);
//链表的查找
LNode* FindList(LNode* phead, int x);
//在链表的pos位置前插入数据
void InsertList( LNode* phead,LNode* pos, int x);
//删除pos位置的节点
void EraseList(LNode* phead, LNode* pos);
//链表销毁
void DestoryList(LNode* phead);
///
//dlist.c
#include "dlist.h"
//初始化
void InitList(LNode** phead)
{
*phead = BuyListNode(0);
(*phead)->next = *phead;
(*phead)->prev = *phead;
}
//创建节点
LNode* BuyListNode(int x)
{
LNode* tmp = (LNode*)malloc(sizeof(LNode));
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
tmp->data = x;
tmp->next = NULL;
tmp->prev = NULL;
return tmp;
}
//链表尾插
void PushBackList(LNode* phead, int x)
{
assert(phead);
LNode* newnode = BuyListNode(x);
LNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//链表尾删
void PopBackList(LNode* phead)
{
assert(phead->next != phead);//防止头节点被删除
assert(phead);
LNode* tail = phead->prev;
LNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
tail->next = NULL;
tail->prev = NULL;
free(tail);
tail = NULL;
}
//打印链表
void PrintList(LNode* phead)
{
assert(phead);
LNode* next = phead->next;
while (next != phead)
{
printf("%d ", next->data);
next = next->next;
}
printf("\n");
}
//链表头删
void PopFrontList(LNode* phead)
{
assert(phead->next != phead);//防止头节点被删除
assert(phead);
LNode* next = phead->next;
LNode* tmp = next->next;
phead->next = tmp;
tmp->prev = phead;
next->next = NULL;
next->prev = NULL;
free(next);
next = NULL;
}
//链表头插
void PushFrontList(LNode* phead,int x)
{
assert(phead);
LNode* newnode = BuyListNode(x);
LNode* next = phead->next;
next->prev = newnode;
newnode->next = next;
phead->next = newnode;
newnode->prev = phead;
}
//链表的查找
LNode* FindList(LNode* phead, int x)
{
assert(phead);
LNode* cur = phead->next;
while(cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
//在链表的pos位置前插入数据
void InsertList(LNode* phead, LNode* pos, int x)
{
assert(phead);
assert(pos);
//如果pos是phead后的第一个节点,相当于头插
if (pos == phead->next)
PushFrontList(phead, x);
//else if (pos == phead->prev)//如果pos是phead前的第一个节点,相当于尾插
// PushBackList(phead, x);
else
{
LNode* prev = pos->prev;
LNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
}
//删除pos位置的节点
void EraseList(LNode* phead, LNode* pos)
{
assert(pos);
assert(phead);
//如果pos是phead后的第一个节点,相当于头删
if (pos == phead->next)
PopFrontList(phead);
else if (pos == phead->prev)//如果pos是phead前的第一个节点,相当于尾删
PopBackList(phead);
else
{
LNode* prev = pos->prev;
LNode* next = pos->next;
prev->next = next;
next->prev = prev;
pos->next = NULL;
pos->prev = NULL;
free(pos);
pos = NULL;
}
}
//链表销毁
void DestoryList(LNode* phead)
{
LNode* cur = phead->next;
while (cur != phead);
{
LNode* prev = cur;
cur = cur->next;
free(prev);
prev = NULL;
}
free(phead);
phead = NULL;
}
/
//test.c
#include "dlist.h"
int main()
{
/*LNode* phead ;
InitList(&phead);
PushBackList(phead, 1);
PushBackList(phead, 2);
PushBackList(phead, 3);
PushBackList(phead, 4);
PushBackList(phead, 5);
PushFrontList(phead, 100);
PushFrontList(phead, 110);
PrintList(phead);
LNode* tmp = FindList(phead, 5);
InsertList(phead, tmp, 1000);
PrintList(phead);
EraseList(phead, tmp);
PrintList(phead);
//PopFrontList(phead);
DestoryList(phead);
*/
return 0;
}
完