前言
一、链表的含义
1.概念
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素+指针,元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
二、单链表的的存储结构及各种操作
1.单链表的存储结构
typedef int SLTDateType;//方便修改数据结构中元素的修改
typedef struct SListNode {
SLTDateType data;//数据域
struct SListNode* next;//指针域
}SListNode;
2.单链表节点的创建
SListNode* BuySListNode(SLTDateType x) {
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));//向内存申请空间
if (newnode == NULL) {//判断申请的空间是否成功
perror("malloc fail");
return;
}
newnode->data = x;//对数据域进行赋值
newnode->next = NULL;
return newnode;//返回这个节点
}
2.单链表大的尾部插入
void SListPushBack(SListNode** pplist, SLTDateType x) {
SListNode * newnode = BuySListNode(x);//申请空间
if (*pplist == NULL) {//如果链表为空直接将新的节点赋给头节点
*pplist = newnode;
}
else {
SListNode* cur = *pplist;//定义一个中间节点将头结点赋值给cur
while (cur->next != NULL) {//利用while找到最后一个节点
cur = cur->next;
}
cur->next = newnode; //将最后一个节点直接指向新的节点
}
}
3.单链表的头部插入
void SListPushFront(SListNode** pplist, SLTDateType x) {
SListNode* newnode = BuySListNode(x);//申请一新的节点
SListNode* cur = *pplist;//定义新的节点将头结点赋值给它记录
newnode->next = cur;//将新的节点的next指向cur
*pplist = newnode;//然后将头结点更新为newnode
}
4.单链表的尾部删除
void SListPopBack(SListNode** pplist) {
assert(*pplist!=NULL);//断言链表是否为空
if ((*pplist)->next == NULL) {//只有一个节点,直接free,然后置为空
free(*pplist);
*pplist == NULL;
}
else {
SListNode* tail = *pplist;//定义一个中间节点找到最后一个节点的前一个,将前一个free掉并置空
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
5.单链表的头部删除
void SListPopFront(SListNode** pplist) {
assert(*pplist);//断言链表是否为空
SListNode* cur = (*pplist)->next;//记录住头结点的下一个,防止找不到
free(*pplist);//释放掉空间
*pplist = cur;//将cur赋给头结点
}
6.单链表的查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
assert(plist);//对链表断言
SListNode* cur = plist;//定义中间节点cur,将头结点赋给cur
while (cur != NULL) {//遍历查找
if (cur->data == x) //找到返回这个节点
return cur;
cur = cur->next;
}
return NULL;//没找到返回NULL
}
7.单链表在任意位置的后面插入
void SListInsertAfter(SListNode* pos, SLTDateType x) {
assert(pos);
SListNode * newnode = BuySListNode(x);//申请节点
newnode->next=pos->next;//将新节点的next指向pos位置以前的节点
pos->next = newnode;//将pos节点的next指向新的节点
}
8.单链表在任意位置的后面删除
void SListEraseAfter(SListNode* pos) {
assert(pos);
SListNode* cur = pos->next->next;//记录住pos下一个的下一个的元素地址
free(pos->next);//释放pos的next元素
pos->next = cur;//将pos的next更新为cur
}
9.单链表在任意位置的前面插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x) {
assert(*pphead);
assert(pos);
SListNode* newnode = BuySListNode(x);
if (*pphead == pos) {//pos是否是链表的头(*pphead==pos)。如果是,该函数只需调用SListPushFront(pphead,x),它会将值为x的新节点推送到链表的前面。
SListPushFront(pphead, x);
}
else {
SListNode* prev = NULL;
SListNode* cur = *pphead;
while (cur->next!= NULL) {//指定的位置不是链表的头,则代码将使用循环遍历链表,以查找指定位置之前的节点。它跟踪上一个节点(prev)和当前节点(cur)。一旦找到指定的位置pos,就会将新节点插入前一个节点和当前节点之间。prev->next被设置为指向新节点,newnode->next被设定为指向当前节点。
if (cur == pos) {
break;
}
prev = cur;
cur = cur->next;
}
prev->next = newnode;
newnode->next = cur;
}
}
10.单链表在任意位置的前面删除
void SLTErase(SListNode** pphead, SListNode* pos) {
assert(*pphead);
assert(pos);
if (pos == *pphead) {//是头结点直接调用头删函数
SListPopFront(pphead);
}
else
{
SListNode* prev = NULL;//定义节点方便记录当前节点的上一个
SListNode* cur = *pphead;//定义节点记录头结点
while (cur->next!=NULL)//遍历查找节点
{
if (cur == pos) {//找到跳出循环
break;
}
prev = cur; //记录上一节点
cur = cur->next;
}
prev->next = cur->next;//指向下一节点的下一节点
free(cur);//释放空间
}
}
11.单链表的销毁
void SLTDestroy(SListNode** pphead) {
assert(*pphead);
SListNode* cur = *pphead;
while (cur != NULL) {
SListNode* node = cur->next;//创建一个临时指针节点,并为其分配cur->next的值,释放当前节点之前保留对下一个节点的引用。
free(cur);//为当前节点(cur)分配的内存使用free函数释放。
cur = node;
}
*pphead = NULL;//循环之后,头指针(*pphead)被设置为NULL,表示链表现在为空。
}
三、总结
单链表单链表是一种动态数据结构,可以方便地进行插入和删除操作,而无需事先知道数据的数量或分配固定大小的内存。可以更灵活地利用内存,因为它可以动态分配内存空间,避免了数组需要预分配固定大小的内存的限制。在单链表中,插入和删除一个节点的操作效率很高,只需要修改相邻节点的指针即可,而不需要移动大量元素。单链表不需要在开始时知道存储元素的数量,可以根据需要动态增长。虽然有如此多的优点,但是也有许多缺点,如单链表不支持随机访问,只能通过从头节点开始逐个遍历来找到特定位置的节点。这导致了在访问第N个元素时需要O(N)的时间复杂度。单链表每个节点都需要额外的指针域来存储下一个节点的地址,这导致相比数组更多的存储空间开销。单链表在逆向遍历时效率较低,因为只有指向前一个节点的指针,需要从头开始遍历。由于节点在内存中可能不是顺序存储的,这可能导致缓存性能较差。