数据结构与算法-单链表专题
一. 链表的概念和结构
概念:链表是物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表也是线性表中的一种
结构:链表是由一个一个的节点组成
节点有时由数据和指向下一个节点的指针组成
二. 单链表的实现
2.1 定义链表的节点的结构
SList.h:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLDataType;//链表节点的创建
typedef struct SListNode
{SLDataType data;struct SListNode* next;
}SLTNode;
2.2 链表的初始化
add.c:
//初始化
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;//节点的链接
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
首先要先开辟空间,由于单链表中节点的空间无需再减少或者扩容,所以不需要使用realloc,用malloc进行创建就好
这里的节点都需要单独创建
然后节点结构体空间创建好后,放入初始化的数据(1,2,3,4)
之后就需要将各自创建的节点通过指针来进行链接,达到逻辑结构上的顺序结构,这样就形成了单链表
别忘了最后的节点指向下一个节点的指针需要变成空指针
2.3 单链表使用需要的函数
2.3.1 打印
//打印
void SLTPrint(SLTNode* phead)
{SLTNode* pcur = phead;while (pcur){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}
通过next指针进行循环判断条件
2.3.2 创建新的节点
SLTNode* SLTBuyNode(SLDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->next = NULL;newnode->data = x;return newnode;
}
这是为了插入新元素而开辟空间形成新节点的函数
2.3.3 尾插
//尾插
void SLTPushBack(SLTNode** pphead, SLDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* ptail = *pphead;while (ptail->next == NULL){ptail = ptail->next;}ptail->next = newnode;}
}
首先注意此处形参和实参都是二级指针。
因为尾插操作其中需要改变一级指针,这样的话传一级指针就是传值调用,所以指针就不会改变,于是需要传地址调用,这样才能对原来的指针进行更改。
assert是为了防止传过来的是个空指针,这样ptail就无法使用了,因为空指针无法进行解引用
然后对单链表是否为空进行验证,为空就直接将新创建的节点为单链表的首节点继续使用
后面就是对尾节点的寻找以及将新创建的节点接连到后面
2.3.4 头插
//头插
void SLTPushFront(SLTNode** pphead, SLDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}
跟尾插差不多,只不过没必要寻找尾节点,直接将传过来的首节点前进行插入新节点就可以
2.3.5 尾删
//尾删
void SLTPopBack(SLTNode** pphead)
{//链表不能为空assert(pphead && *pphead);//链表只有一个节点if ((*pphead)->next == NULL) //-> 优先级高于*{free(*pphead);*pphead = NULL;}else {//链表有多个节点SLTNode* prev = *pphead;SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}//prev ptailfree(ptail);ptail = NULL;prev->next = NULL;}}
分为三种情况
第一种:链表为空。无需进行尾删,直接退出
第二种:链表只有一个节点。直接解放节点malloc创建的空间就好,使用free,然后指针变成NULL
第三种:链表有多个节点。先通过循环寻找尾节点和尾节点前一个节点,然后释放最后的节点,将尾节点前的节点的next指针变成NULL就可以
2.3.6 头删
//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
头删跟尾删刚开始差不多,但更为简单
2.3.7 查找
函数实现:
//查找
SLTNode* SLTFind(SLTNode* phead, SLDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
add.c文件中的函数使用:
SLTNode* p = SLTFind(node1, 3);if (p==NULL){printf("没找到\n");}else{printf("找到了\n");}//打印SLTPrint(node1);
2.3.8 在指定位置之前插入数据
函数实现:
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* newnode = SLTBuyNode(x);//若pos==*pphead//头插if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}
}
函数运用:
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTNode* find = SLTFind(plist, 3);
SLTInsert(& plist, find, 11);
SLTPrint(plist)
2.3.9 在指定位置之后插入数据
函数实现:
//在指定位置之后插?数据
void SLTInsertAfter(SLTNode* pos, SLDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
2.3.10. 删除pos节点
函数实现:
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);if (pos == *pphead)//pos等于*pphead,也就是说pos是头节点{//头删SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
分两种情况:头删和正常删除
2.3.11. 删除pos之后的节点
函数实现:
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos&&pos->next);SLTNode* del = pos->next;pos->next = del->next;free(pos->next);del = NULL;
}
2.3.12 链表的销毁
函数实现:
//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur =next;}*pphead = NULL;
}