上一篇介绍了单链表,这一片介绍双向链表。
单链表的缺点:
1.尾插时需要遍历到最后结点才能尾插。
2.插入功能比较繁琐,分前后,前插入需要找到前一个结点。
3.需要考虑空链表。
所以双向链表又弥补了此缺点。
一、介绍双向链表
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
此head可以叫guard,哨兵位,里面的数据没有什么意义,就是一个带头结点方便增删查改。
二、代码实现
头文件:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
//void ListInit(LTNode** pphead);
LTNode* ListInit();
void ListDestory(LTNode* phead);
void ListPrint(LTNode* phead);
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);
void ListInsert(LTNode* pos, LTDataType x);
void ListErase(LTNode* pos);
1.结构体和定义
双向链表,所以肯定有两个指针,一个指向前结点(prev),一个指向后结点(next)。
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
2.链表初始化
guard是哨兵位结点,一开始没有结点,因为双链表,所以它的prev和next指针都是指向自己的。就是创建一个哨兵位结点。也可以说是头结点。
LTNode* ListInit()
{
LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
3.创建结点
插入结点函数都得调用此函数,向系统申请一个空间来存放要插入的数据,后面指针指向NULL,前面指针也是指向NULL,因为后面要改变它前后指针,先指向NULL初始化而已。
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.链表的销毁
有创建就要有销毁,二者不能分开。一个一个拿出来销毁,最后销毁哨兵位结点(头节点)。
// 可以传二级,内部置空头结点
// 建议:也可以考虑用一级指针,让调用ListDestory的人置空 (保持接口一致性)
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
//phead = NULL;
}
5.打印链表
void ListPrint(LTNode* phead)
{
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
6.bool ListEmpty 函数
bool值为真和假,插入元素时需要先判断双向链表是否为空,bool函数,如果不为空,返回真值,否则为假,assert就是要看它不为空。
bool ListEmpty(LTNode* phead)
{
assert(phead);
/*if (phead->next == phead)
return true;
else
return false;*/
return phead->next == phead;
}
7.尾插
初始化之后(前面有写),只有一个结点时,和多个结点时,都是一样的,phead就是guard哨兵位,它是指向它自己的。和创建结点函数挂钩。
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;//当只有一个结点时,就是它自己phead
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;*/
ListInsert(phead, x);
}
8.尾删
尾删和头插一样,指针指向改变了,free掉最后结点就可以了。
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);
}
9.头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
// 先链接newnode 和 phead->next节点之间的关系
/*LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;*/
// 不关心顺序
//LTNode* newnode = BuyListNode(x);
//LTNode* first = phead->next;
//phead->next = newnode;
//newnode->prev = phead;
//newnode->next = first;
//first->prev = newnode;
ListInsert(phead->next, x);
}
10.头删
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);
}
11.查找
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
12.pos位置之前插入
// 在pos之前插入
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
// prev newnode pos;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
13.pos位置删除
// 删除pos位置
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
//pos = NULL;
}
14.链表的结点个数
size_t ListSize(LTNode* phead)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++n;
cur = cur->next;
}
return n;
}
总结:
和单链表相比,双向链表的功能比较强大。但是都是按需所取。
三、链表和顺序表的区别:
如有不正之处,欢迎大家私信我纠正,我也十分愿意与大家一起共同探讨学习。