目录:
1.链表的分类
2.双向链表(带头双向循环链表) 结构
3.链表的实现
4.常见问题解答
1.链表的分类
链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
带头:指的是链表中有哨兵位节点,该 哨兵位节点 即 头节点
哨兵位节点不存储任何有效元素,只是站在这里"放哨的"
"哨兵位"存在的意义: 遍历循环链表避免死循环。
单向或者双向:
循环或者不循环:
最常用的两种:
单链表:单向不带头不循环链表
双向链表:双向带头循环链表
2.双向链表
带头:头节点(哨兵位)
双向:后继指针(next) 前驱指针(prev)
循环:尾节点的next指针指向头节点
定义双向链表节点的结构:
typedef int LTDataType;
typedef struct ListNode
{
//指针保存下⼀个节点的地址
struct ListNode* next;
//指针保存上⼀个节点的地址
struct ListNode* prev;
LTDataType data;
}LTNode;
3.链表的实现
3.1 List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDatatype;
typedef struct ListNode
{
LTDatatype data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//声明双向链表中提供的方法
//
//void LTInit(LTNode** pphead);//初始化、、以参数形式返回
LTNode* LTInit();//、、以返回值形式返回
//插入数据之前,链表必须初始化到只有一个头节点的情况
//不改变哨兵位的地址,因此传一级 即可
void LTPushBack(LTNode* phead, LTDatatype x);//尾插
void LTPushFront(LTNode* phead, LTDatatype x);//头插
void LTPopBack(LTNode* phead);//尾删
void LTPopFront(LTNode* phead);//头删
void LTPrint(LTNode* phead);//打印
LTNode* LTFind(LTNode* phead, LTDatatype x);//查找
void LTInsert(LTNode* pos, LTDatatype x);//在pos位置之后插入数据
void LTErase(LTNode* pos);//删除pos节点
void LTDestory(LTNode*phead);//销毁链表
List.c
#include"List.h"
LTNode* LTBuyNode(LTDatatype x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("mallov fail");
exit(1);
}
node->data = x;
node->next = node->prev = node;
return node;
}
//void LTInit(LTNode** pphead)//初始化
//{
// //创建一个哨兵位
// *pphead = LTBuyNode(-1);
//}
LTNode* LTInit()//初始化
{
LTNode*phead = LTBuyNode(-1);
return phead;
}
void LTPrint(LTNode* phead)//打印
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur!=phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
}
void LTPushBack(LTNode* phead, LTDatatype x)//尾插
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
void LTPushFront(LTNode* phead, LTDatatype x)//头插
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
void LTPopBack(LTNode* phead)//尾删
{
assert(phead&&phead->next!=phead);
LTNode* del = phead->prev;
phead->prev = del->prev;
del->prev->next = phead;
free(del);
del = NULL;
}
void LTPopFront(LTNode* phead)//头删
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
LTNode* pcur = phead->next;
while (pcur!=NULL)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, LTDatatype x)//在pos位置之后插入数据
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->prev = pos;
newnode->next = pos->next;
pos->next->prev = newnode;
pos->next = newnode;
}
//为什么不传二级? --- 保持接口一致性
void LTErase(LTNode* pos)//删除pos节点
{
//pos!=phead 但是没有参数phead,无增加校验
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
void LTDestory(LTNode* phead)//销毁链表
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur!=phead )
{
LTNode* next = pcur->next;
free(pcur);
pcur = NULL;
pcur = next;
}
free(phead);
phead = NULL;
}
Test.c
以上是我的测试,大家也可进行多种不同测试。
4.常见问题解答
?LTDestory和LTErase参数为什么不传二级指针?
理论上是要传二级指针的,因为我们需要让形参的改变影响到实参。但是为了保持接口一致性 才传的一级---
#传一级存在的问题是,当phead置为NULL后,实参plist不会被修改为NULL,
因此解决办法是:调用完方法后,手动将实参置为空。
有更多问题,可以在评论区提出^.^