链表的分类
链表有8种结构,由以下特征组合而成:
单向或双向
单向:一个节点只包含数据和一个next后继指针,指向下一个节点
双向:一个节点包含数据,next后继指针,还多了prev前驱指针,指向前一个节点
带头或不带头
注意:这里的"头"特指哨兵位头节点 哨兵位头节点不存储或记录有效数据,
但它的next指针和prev指针仍然有效.
循环或不循环
带头双向循环链表的实现
定义节点
双向链表的节点包含数据,后继指针next指向下一个节点位置,前驱指针prev指向前一个节点位置
typedef int LTDataType;//暂定int为链表存储的数据
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType x;
}LTNode;
初始化链表
带头的链表,在对链表进行增删查改前要创建一个哨兵位头节点,
并且该节点的next和prev刚开始指向自己。
LTNode* ListInit()//链表初始化
{
//malloc一个哨兵位头节点,该节点不存储有效数据
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
assert(phead);
phead->next = phead;
phead->prev = phead;
}
链表的增删查改
尾插
void ListPushBack(LTNode* phead, LTDataType x)//尾插
{
assert(phead);//哨兵位不可能为空
LTNode* tail = phead->prev;
LTNode* newNode = BuyListNode(x);//申请新节点的函数
tail->next = newNode;
newNode->prev = tail;
newNode->next = phead;
phead->prev = newNode;
}
头插
void ListPushFront(LTNode* phead, LTDataType x)//头插
{
LTNode* newNode = BuyListNode(x);
LTNode* head = phead->next;//记录头节点(非哨兵位)
newNode->next = head;
head->prev = newNode;
newNode->prev = phead;
phead->next = newNode;
}
尾删
void ListPopBack(LTNode* phead)//尾删
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* tailpre = tail->prev;
free(tail);
tailpre->next = phead;
phead->prev = tailpre;
}
头删
void ListPopFront(LTNode* phead)//头删
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* head = phead->next;//记录头节点(不特指哨兵位)
LTNode* headnext = head->next;
phead->next = headnext;
headnext->prev = phead;
free(head);
}
在任意位置之前插入一个节点
注意:若pos == phead,即在哨兵位之前插入一个节点,就相当于尾插,
因为phead->prev指向尾节点;
若pos == phead->next, 相当于头插,
因为phead->next就是链表的头结点(不特指哨兵位).
void ListInsert(LTNode* pos, LTDataType x)//在pos位置前插入
{
assert(pos);
LTNode* newNode = BuyListNode(x);
LTNode* prev = pos->prev;
prev->next = newNode;
newNode->prev = prev;
newNode->next = pos;
pos->prev = newNode;
}
删除任意位置的节点
void ListErase(LTNode* pos, LTNode* phead)//删除pos位置的节点
{
assert(pos);
assert(!ListEmpty(phead));//保证链表一定不为空,可能会错传哨兵位来删除
LTNode* pre = pos->prev;//链接【pos前一个节点】与【pos后一个节点】
LTNode* next = pos->next;
pre->next = next;
next->prev = pre;
free(pos);
}
其它函数
获取链表长度
int ListSize(LTNode* phead)
{
assert(phead);
//有头循环链表的遍历结束条件不能为NULL,否则死循环
LTNode* cur = phead->next;//从哨兵位的下一个节点开始遍历,直到回到哨兵位后遍历结束
int num = 0;
while (cur != phead)
{
cur = cur->next;
num++;
}
return num;
}
申请新节点
LTNode* BuyListNode(LTDataType x)//申请新节点
{
LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
assert(newNode);
newNode->x = x;
}
链表判空
bool ListEmpty(LTNode* phead)//判断链表是否为空
{
assert(phead);
return phead->next == phead;//或者phead->prev == phead;
}
链表打印
void ListPrint(LTNode* phead)//打印链表
{
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->x);
cur = cur->next;
}
printf("\n");
}
链表的销毁
void ListDestory(LTNode* phead)//销毁链表
{
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;//先记录下一个节点,再free掉当前节点,就能找到后面的节点
free(cur);
cur = next;
}
free(phead);
}