单链表的增删查改功能的函数定义包括:
1.动态申请结点
2.打印单链表
3.单链表头插
4.单链表头删
5.单链表尾插
6.单链表尾删
7.单链表查找
8.单链表pos之前插入
9.单链表pos之后插入
10.单链表pos位置删除
11.单链表销毁
文件定义:
1.头文件:SList.h
2.源文件1:SList.c
3.源文件2:源.c
//SList.h
#pragma once
#include
#include
#include
#include
typedef int SListDataType;
typedef struct SListNode
{
int data;//val
struct SListNode* next;//存储下一个结点的地址
}SListNode;
struct SListNode* BuyListNode(SListDataType x);//动态申请一个结点
void SListPrint(struct SListNode* phead);//单链表打印
void SListPushBack(struct SListNode** pphead, SListDataType x);//单链表尾插
void SListPushFront(struct SListNode** pphead, SListDataType x);//单链表头插
void SListPopBack(struct SListNode** pphead);//单链表尾删
void SListPopFront(struct SListNode** pphead);//单链表头删
struct SListNode* SListFind(struct SListNode* phead, SListDataType x);//单链表查找
void SListInsert(struct SListNode** pphead,struct SlistNode* pos, SListDataType x);//单链表pos之前插入
void SListErase(struct SListNode** pphead,struct SListNode* pos);//单链表删除pos位置
void SListInsertAfter(struct SListNode* pos, SListDataType x);//单链表pos之后插入
void SListDestory(struct SListNode** pphead);//单链表销毁
//SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SListPrint(struct SListNode* phead)//打印链表 这里不需要改变值 所以一级指针*phead就可以 不需要二级指针
{
struct SListNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
};
struct SListNode* BuyListNode(SListDataType x)//新结点创建
{
struct SListNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));
if (newnode == NULL)
{
printf("malloc失败!\n");
exit(-1);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
return newnode;
};
void SListPushBack(struct SListNode** pphead, SListDataType x)//尾插
{
//要改变*phead 就要用**pphead 传数据进去要用&*phead
struct SListNode* newnode = BuyListNode(x);
if (*pphead == NULL)
{
pphead = newnode;
}
else
{
//找尾
struct SListNode* tail = *pphead;//phead的地址赋给tail指针
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;//新插入的newnode newnode->next=NULL;
}
};
void SListPushFront(struct SListNode** pphead, SListDataType x)//头插
{
struct SListNode* newnode = BuyListNode(x);
newnode->next = *pphead;//*pphead就是*phead 头指针地址
*pphead = newnode;//头指针指向新结点
}
void SListPopBack(struct SListNode** pphead)//尾删
{
//尾删最后一个next必须为NULL 直接tail->next=NULL 不行 需要多一个结构体让tail指向其next指向NULL
assert(pphead);
//可能情况
//1.空
//2.一个结点
//3.多个结点
if (*pphead == NULL)//暴力方式:assert(*pphead!=NULL);
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else {
struct SListNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;//tail指向prev
}
};
void SListPopFront(struct SListNode** pphead)//头删
{
assert(pphead);
//1.空
//2.非空
if (*pphead == NULL)
{
return;
}
else
{
struct SListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
};
struct SListNode* SListFind(struct SListNode* phead, SListDataType x)//单链表查找
{
struct SListNode* cur = phead;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;//没找到就返回NULL
};
void SListInsert(struct SListNode** pphead, struct SlistNode* pos, SListDataType x)//在pos位置之前插入
{
assert(pphead);
assert(pos);
//1.pos是第一个结点
//2.pos不是第一个结点
if (pos == *pphead)
{
SListPushFront(pphead, x);//头插一个结点
}
else
{
struct SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
struct SListNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->next = pos;
}
};
void SListInsertAfter(struct SListNode* pos, SListDataType x)//在pos位置之后插入
{
assert(pos);
struct SListNode* next = pos->next;
struct SListNode* newnode = BuyListNode(x);
pos->next = newnode;
newnode->next = next;
};
void SListErase(struct SListNode** pphead, struct SListNode* pos)//单链表删除pos位置
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
struct SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
};
void SListDestory(struct SListNode** pphead)//单链表销毁
{
assert(pphead);
struct SListNode* cur = *pphead;
while (cur != NULL)
{
struct SListNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
};
//源.c
#define _CRT_SECURE_NO_WARNINGS 1
//链表
#include
#include
#include
#include"Slist.h"
int main()
{
SListNode* slist=NULL;//空链表
printf("1:测试头插功能!\n");
printf("头插入4,3,2,1\n");
SListPushFront(&slist, 4);
SListPushFront(&slist, 3);
SListPushFront(&slist, 2);
SListPushFront(&slist, 1);
SListPrint(slist);
printf("\n");
printf("2:测试头删功能!\n");
printf("头删1\n");
SListPopFront(&slist, 1);
SListPrint(slist);
printf("\n");
printf("3:测试尾插功能!\n");
printf("尾插7\n");
SListPushBack(&slist, 7);
SListPrint(slist);
printf("\n");
printf("4:测试尾删功能!\n");
printf("尾删7\n");
SListPopBack(&slist);
SListPrint(slist);
printf("\n");
printf("5:测试查找功能!\n");
printf("查找3\n");
if (SListFind(slist, 3) == NULL)
printf("没有找到数据3\n");
else
printf("找到了数据3\n");
printf("\n");
printf("6:测试随机位置前插入功能!\n");
printf("在第3个位置前插入5\n");
SListInsert(&slist, slist->next->next, 5);
SListPrint(slist);
printf("\n");
printf("7:测试随机位置后插入功能!\n");
printf("在第3个位置后插入8\n");
SListInsertAfter(slist->next->next, 8);
SListPrint(slist);
printf("\n");
printf("8:测试pos位置删除数据!\n");
printf("删除第3个位置的数据\n");
printf("原链表:");
SListPrint(slist);
printf("删除后:");
SListErase(&slist, slist->next->next);
SListPrint(slist);
printf("\n");
printf("9:测试销毁链表!\n");
printf("销毁前链表:\n");
SListPrint(slist);
printf("销毁后链表:\n");
SListDestory(&slist);
SListPrint(slist);
printf("\n");
return 0;
}
效果展示:
理解指针插入删除细节:我们举例下面两个代码来描述
单链表的插入:
代码一(插入正确操作):
S->next=P->next
P->next=S
代码二(插入错误操作):
P->next=S
S->next=p->next
那么为什么第二个代码错误,第一个代码正确呢?
下面我们图解两个代码给你阐述原因:
代码一:
阐述:首先我们假定P结点的地址为0X1000,P的下一个结点P->next地址是0X1004,S结点的地址是0x1100。原来P的后继指针地址存的是P->next的地址也就是0X1004,S的后继指针地址为NULL,执行操作①后S的后继指针地址变为了&P->next。执行代码②后,P的后继指针地址指向S结点,也就是&S,那么P就不再指向p->next了,而p->next地址又没变,所以实现了在p和p->next之间插入了S结点。最终变成了我们图上的最终结果图!
代码二:
阐述:前面和上面一样的,但是操作①将P的后继指针地址变成了S的地址,然后执行操作②的时候,它让S的后继指针地址变成了P的后继节点地址,也就是S->next=S了,S的后继指针地址变成了自己,就成为了自己循环了,因此错误。这是没有深入理解指针的朋友们经常犯的错误之一!
提醒:
1.我们之所以难以理解是因为我们把p->next看成了一定是p的后一个结点,其实p->next只是一个名称,指针我们看的是地址,p->next只是因为内存是连在一起的才这样写的,但链表存储数据并不是连续存储的。
2.结点分为数据域和指针域,数据域存储数据比如P->data=1,p->next是指针域,指向下一个结点,因此后继指针地址是指向下一个结点而不是什么指针的数据域。
3.研究指针问题真的建议大家画图,不然有些问题真的不好理解。
//总结
单链表:
1.适合头插头删
2.尾部或者中间某个位置插入删除不适合
3.如果要使用链表单独存储数据,那我们后面学的双链表更适合
顺序表
优点:连续物理空间方便下标随机访问
缺点:①插入数据,空间不足时要扩容,扩容有性能消耗
②头部或者中间位置插入数据,需要挪动数据,效率较低
③可能存在一定空间占用,浪费空间
推荐此书的原因:
1.此书很好的用彩色画图的方式为大家展示了基本的增删查改问题
2.此书代码简洁,逻辑清晰
3.此书的虽然还是达不到企业水平,但是基本入门了,适合用来深入学习数据结构
4.学校里学习的数据结构非常的死板,而且非常不实用,比如从来没考虑插入、删除那些位置导致的各种问题和判断他们