目录
一、双向循环链表的介绍
带头双向循环链表:结构最复杂,但是实现反而简单。一般用来单独存储数据,实际中使用的链表数据结构都是带头双向链表。另外,这个结构虽然结构复杂,但是使用代码实现后会发现结构会带来很多优势。双向链表严格来说只需要快速的实现两个接口,insert 和 earse 头尾的插入和删除就可以搞定了,这就是结构的优势!
二、双链表的实现
0x01. 双链表的定义
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
0x10.接口函数
void ListDestory(LTNode* phead);
void ListPrint(LTNode* phead);
LTNode* ListInit();
LTNode* BuyListNode(LTDataType x);
void ListPushBack(LTNode* phead, LTDataType x);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
bool ListEmpty(LTNode* phead);
size_t ListSize(LTNode* phead);
LTNode* ListFind(LTNode* phead, LTDataType x);
//pos前插入
void ListInsert(LTNode* pos, LTDataType x);
//删除pos位置;
void ListErase(LTNode* pos);
三、接口函数的实现
1) 初始化双链表
LTNode* ListInit()
{
LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
这里我们使用 malloc 函数开辟一块空间作为 "哨兵位" guard ,最后将其进行一个初始化。最后再将 guard 作为结果返回回去,外面就可以接收到了。这就是返回值的方法,当然这里也可以采用二级指针的方法来完成。
2) 双向链表的打印
phead表示哨兵位节点
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("phead<=>");
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
创建遍历指针 cur,因为 pHead 是哨兵位所以存的不是有效数据,我们想要遍历链表就需要从 pHead->next 开始(即第一个有效数据节点),当 cur 等于 pHead 就相当于全部走了一遍了,这时就结束。
3) 创建新节点
这里基本和单链表相同
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
4) 尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->next = phead;
newnode->prev = tail;
phead->prev = newnode;
//
//ListInsert(phead, x);
}
1)这里的phead没有改变,所以不用传二级指针
2)双链表的尾插节点可以直接从phead->prev那里获得,不用像单链表那样遍历,时间复杂度只需要O(1),但这里创建出来的新节点要将头和尾都要连上
3)如果以及写出ListInsert(任意位置前插入),可以直接调用该函数实现尾插。
5) 头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
newnode->prev = phead;
phead->next = newnode;*/
//不关心顺序
LTNode* newnode = BuyListNode(x);
LTNode* first = phead->next;
newnode->next = first;
first->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
//ListInsert(phead->next, x);
}
无论是头插还是尾插,都要注意改变next和prev修改时的顺序,这里将第一个节点位置保存下来,修改时即不用关心顺序,可以让代码的错误率更低。
同理,可以直接调用ListInsert函数完成头插
6) 判断链表是否为空
bool ListEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
虽然代码比较简洁,但后面删除节点要调用,所以先写出来。
7) 尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* tail = phead->prev;
LTNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
//ListErase(phead->prev);
}
避免把哨兵节点删除,需要先断言一下,链表是否为空。
删尾部节点的时候,要将它的前一个节点和phead链接上,画出草图即可以轻松写出代码。
8) 头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
//ListErase(phead->next);
}
头删有哨兵节点时十分简单,直接保存头部节点,将phead链接到它的next即可,在把second的prev连到phead上即可。
9) 链表大小
直接遍历
size_t ListSize(LTNode* phead)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur!=phead)
{
n++;
cur = cur->next;
}
return n;
}
10 )查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
11) pos前位置插入
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
newnode->prev = prev;
newnode->next = pos;
prev->next = newnode;
pos->prev = newnode;
}
防止pos位置为空,断言一下。
写出该函数后,前面的头插和尾插就可以直接调用该函数完成。
12)删除pos位置
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
较为简单,pos前和pos后建立链接即可
13)销毁链表
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
在程序的最后,一定要销毁链表,链表需遍历销毁,避免内存泄露
三、完整代码
List.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
void ListDestory(LTNode* phead);
void ListPrint(LTNode* phead);
LTNode* ListInit();
LTNode* BuyListNode(LTDataType x);
void ListPushBack(LTNode* phead, LTDataType x);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
bool ListEmpty(LTNode* phead);
size_t ListSize(LTNode* phead);
LTNode* ListFind(LTNode* phead, LTDataType x);
//pos前插入
void ListInsert(LTNode* pos, LTDataType x);
//删除pos位置;
void ListErase(LTNode* pos);
List.c
#define _CRT_SECURE_NO_WARNINGS
#include "List.h"
LTNode* ListInit()
{
LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("phead<=>");
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->next = phead;
newnode->prev = tail;
phead->prev = newnode;*/
ListInsert(phead, x);
}
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
newnode->prev = phead;
phead->next = newnode;*/
//不关心顺序
/*LTNode* newnode = BuyListNode(x);
LTNode* first = phead->next;
newnode->next = first;
first->prev = newnode;
phead->next = newnode;
newnode->prev = phead;*/
ListInsert(phead->next, x);
}
bool ListEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
/*LTNode* tail = phead->prev;
LTNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;*/
ListErase(phead->prev);
}
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
/*LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;*/
ListErase(phead->next);
}
size_t ListSize(LTNode* phead)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur!=phead)
{
n++;
cur = cur->next;
}
return n;
}
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
newnode->prev = prev;
newnode->next = pos;
prev->next = newnode;
pos->prev = newnode;
}
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "List.h"
void TestList1()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListPushFront(plist, 0);
ListPushFront(plist, -1);
ListPrint(plist);
ListPushFront(plist, 10);
ListPushFront(plist, 20);
ListPushFront(plist, 30);
ListPrint(plist);
ListInsert(ListFind(plist, 3), 0);
ListPrint(plist);
ListErase(ListFind(plist, 2));
ListPrint(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPrint(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPrint(plist);
ListDestory(plist);
plist = NULL;
}
int main()
{
TestList1();
return 0;
}
test.c是用来测试代码的,建议每写出一个函数,就测试一下,如果出现错误,这时通过调试很容易发现。