链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接实现。在链表的所有结构中,双向带头循环链表是链表中存储数据的最优结构。在以下代码中,实现了链表创建,销毁,打印,头插,尾插,头删,尾删,插入,删除,查找的接口。通过节点中的前驱指针和后驱指针,使数据的访问比单链表更加方便,在代码实现上也更加简洁。
目录
链表的头文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDatatype;
typedef struct Listnode
{
LTDatatype data;
struct Listnode* next;
struct Listnode* prev;
}Listnode;
Listnode* ListCreat();
void ListDestory(Listnode* phead);
void ListPrint(Listnode* phead);
void ListPushFont(Listnode* phead, LTDatatype x);
void ListPushBack(Listnode* phead, LTDatatype x);
void ListPopFront(Listnode* phead);
void ListPopBack(Listnode* phead);
void ListInsert(Listnode* pos, LTDatatype x);
void ListErase(Listnode* pos);
Listnode* ListFind(Listnode* phead, LTDatatype x);
1.链表的创建
创建一个哨兵位的头节点,该节点不存储有效数据,前驱指针和后驱指针均指向自己,由于增加节点在很多接口中都会用到,增加节点被封装成一个函数。
Listnode* BuyListNode(LTDatatype x)
{
Listnode* newnode = (Listnode*)malloc(sizeof(Listnode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->prev = NULL;
newnode->prev = NULL;
}
Listnode* ListCreat()
{
Listnode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
2.链表的销毁
从头遍历链表并释放每一个节点,最后释放头节点
void ListDestory(Listnode* phead)
{
assert(phead);
assert(phead->next);
Listnode* cur = phead->next;
while (cur != phead)
{
Listnode* next = cur->next;
cur->next = NULL;
cur->prev = NULL;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
3.链表的打印
从哨兵位的下一个节点开始遍历并打印,直到回到哨兵位
void ListPrint(Listnode* phead)
{
assert(phead);
Listnode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
4.链表的插入
默认在指定位置的前一个位置插入,注意在更改指针指向时,先记录前一个节点的位置。如果不想记录,也可以选择最后更改pos的前驱指针的指向。
void ListInsert(Listnode* pos, LTDatatype x)
{
assert(pos);
Listnode* prev = pos->prev;
Listnode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
5.链表的删除
记录指定位置的节点的前后节点,释放指定位置的节点,更改前后两个节点的指针指向
void ListErase(Listnode* pos)
{
assert(pos);
Listnode* prev = pos->prev;
Listnode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
6.链表头插
在实现了插入接口的前提下,可以对插入接口进行复用,哨兵位的下一个节点就是有效数据的头节点。
void ListPushFont(Listnode* phead, LTDatatype x)
{
assert(phead);
ListInsert(phead->next, x);
}
7.链表尾插
对插入接口复用,哨兵位的节点的前驱指针指向链表的尾节点。插入接口默认插在指定节点的前一个位置,因此对插入接口传入哨兵位节点的地址。
void ListPushBack(Listnode* phead, LTDatatype x)
{
assert(phead);
ListInsert(phead, x);
}
8.链表头删
对删除接口进行复用
void ListPopFront(Listnode* phead)
{
assert(phead);
assert(phead->next);
ListErase(phead->next);
}
9.链表尾删
void ListPopBack(Listnode* phead)
{
assert(phead);
ListErase(phead->prev);
}
10.链表查找
遍历寻找与参数值拥有相同值的节点
Listnode* ListFind(Listnode* phead, LTDatatype x)
{
assert(phead);
assert(phead->next);
Listnode* cur = phead->next;
while (cur != phead)
{
if (x == cur->data)
{
return cur;
}
cur = cur->next;
}
return NULL;
}