目录
前言
概念
链表的分类
1.单向或者双向
2.带头或者不带头(哨兵位)
3.循环或者非循环
实际中最常用的两种结构分别是
无头单向非循环链表(代码实现)
常用接口预览
接口具体代码实现
测试
带头双向链表循环(代码实现)
常用接口预览
接口具体代码实现
在顺序表的实现中,我们发现顺序表的存在以下缺点:
1.中间头部插入删除数据,需要挪动数据,效率低下O(N)
2.空间不够,扩容。扩容需要申请新空间,拷贝数据,释放旧空间,会有一定的消耗,其次还可能会有一定的空间浪费。
而链表就很好的弥补了这些缺点。
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
上述情况组合起来就有8种链表结构
上图所示带头的链表结构组合有4种,不带头的同样有4种;
1.无头单向非循环链表:结构简单,一般不会单独用来存储数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等。
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。
#include
#include
#include
typedef int SLTDataType;
//定义链表结点结构
typedef struct SListNode
{
SLTDataType data;
//c语言中 struct SListNode 才能表示类型
struct SListNode* next;
}SLTNode;
//遍历链表输出
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找 --- 修改与其一致
SLTNode* SListFind(SLTNode* pphead, SLTDataType x);
//pos位置删除
void SListErase(SLTNode** pphead, SLTNode* pos);
//pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x);
//pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//pos位置后面删除
void SLTEraseAfter(SLTNode* pos);
1.定义链表结点结构
//定义链表结点存储的数据的数据类型
typedef int SLTDataType;
//定义链表结点结构
typedef struct SListNode
{
SLTDataType data;
//c语言中 struct SListNode 才能表示类型
struct SListNode* next;
}SLTNode;
2.创建新结点
//创建新结点
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //动态申请空间
if (newnode == NULL) //判断空间是否申请失败
{
perror("malloc fail");
return NULL;
}
//初始化新结点
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3..遍历链表,输出链表元素。
//遍历链表
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead; //令cur指向链表的头结点
while (cur!=NULL) //当cur等于NULL时,代表链表已经遍历完成,结束循环
{
printf("%d->", cur->data); //输出cur指向的结点 的数据
cur = cur->next; //next存储的是cur的下一结点
}
printf("NULL\n");
}
4.尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else {
//找尾
//尾插的本质:原尾结点中要存储新的尾结点的地址
SLTNode* tail = *pphead;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = newnode;
}
}
5.尾删
void SLTPopBack(SLTNode** pphead)
{
//处理链表本身就为空,链表为空便不再进行尾删
assert(pphead);
assert(*pphead);
//处理只有一个结点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
//查找尾结点的前一个结点
while (tail->next->next != NULL) {
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
6.头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
//新建一个结点
SLTNode* newnode = BuySLTNode(x);
//让新结点的next指向原链表的头结点
newnode->next = *pphead;
//让原本指向头结点的指针指向新节点,新节点变成链表的头结点
*pphead = newnode;
}
7.头删
void SLTPopFront(SLTNode** pphead)
{
//处理链表本身就为空
assert(pphead);
assert(*pphead);
//存储原链表的头节点,方便后续释放空间
SLTNode* cur = *pphead;
//让原本指向链表头节点的指针,指向头节点的下一结点
*pphead = (*pphead)->next;
free(cur);
}
8.查找i链表中的某个元素
SLTNode* SListFind(SLTNode* pphead, SLTDataType x) {
SLTNode* cur = pphead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
//这里处理未找到的情况
return NULL;
}
9.在pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x)
{
assert(pos);
assert(pphead);
//当pos为头节点的位置时,使用头插即可
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else {
SLTNode* prev = *pphead;
//遍历查找pos的前一结点
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
10. pos位置后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
11.删除pos位置的结点
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pphead);
//删除的结点是头结点时,使用头删即可
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
//外面置空pos指针
}
}
12.删除pos位置后面结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
void TestSList1()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPushFront(&plist, 0);
SLTNode* ret=SListFind(plist, 2);
SLTInsert(&plist, ret, 10);
SLTPrint(plist);
}
int main() {
TestSList1();
return 0;
}
#include
#include
#include
typedef int LTDataType;
//双向链表结点定义
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
//创建一个新的结点
LTNode* BuyListNode(LTDataType x);
//初始化
LTNode* LTInit();
void LTDestory(LTNode* phead);
//尾插尾删
void LTPushback(LTNode* phead, LTDataType x);
void LTPopback(LTNode* phead);
//头插头删
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
//在pos位置 之前插入一个值
void LTInsert(LTNode* pos, LTDataType x);
//查找某个结点
LTNode* LTFind(LTNode* phead, LTDataType x);
//删除某个结点
void LTErase(LTNode* pos);
//判断链表是否为空
bool LTEmpty(LTNode* phead);
//输出链表
void LTPrint(LTNode* phead);
1.创建一个新节点
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL) {
perror("malloc fail");
return NULL;
}
//初始化新结点
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
2.初始化链表
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1);
//默认无结点,只有一个哨兵位,prev指向它的前一结点,next指向他的后一结点
//这里因为链表中没有结点,让它都指向自己
phead->next = phead;
phead->prev = phead;
return phead;
}
3.链表销毁
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
LTErase(cur);
cur = next;
}
free(phead);
//phead置空因为这里传的是一级指针,改变不了实参,由调用方自行置空
}
4.在指定结点前插入一个值-即新结点
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
5.删除指定位置结点
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
6.尾插
void LTPushback(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;*/
//这里使用代码复用更加方便
LTInsert(phead, x);
}
7.尾删
void LTPopback(LTNode* phead)
{
/*assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
del = NULL;*/
//这里使用代码复用更加方便
LTErase(phead->prev);
phead->prev = NULL;
}
8.头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
/*LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;*/
//这里使用代码复用更加方便
LTInsert(phead->next, x);
}
9.头删
void LTPopFront(LTNode* phead) {
/*LTNode* del = phead->next;
phead->next = del->next;
del->prev = phead;
free(del);*/
//这里使用代码复用更加方便
LTErase(phead->next);
phead->next = NULL;
}
10.打印链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("<=>head<=>");
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
11.查找链表中的某个值
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}