单向链表
1.前置知识(部分最好记忆)
1.1 链表组成
无论链表是否为空,均可通过 newcode->next = phead
将 newcode
连接到 phead
。
注意:通常先处理 newcode->next
和 data
,可先保存下一个节点的指向。
最后,phead = newcode
。
phead 为指针变量,其保存着节点 1 的值,也就是说 phead 指向节点 1。
节点 1 中的 next 保存着节点 2 的地址,这意味着节点 1 的 next 指向节点 2,也可以表示节点 1 指向节点 2。(注:在 C 语言中,准确的表达应该是 node1->next = node2
,其中 node1 和 node2 都是结构体变量的地址。)
在定义结构体类型后,可以通过
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
这段代码用于创建一个结构体变量(空间),并返回该结构体变量的地址,即节点的地址。
每个节点具有两个属性(在定义结构体类型时设定):一个是 data
,用于存储数据;另一个是 next
,用于存储下一个节点的地址。
尾节点是最后一个节点,其后无节点(即 ptail->next
为 NULL
)。在前一个节点连接后一个节点,并为尾节点连接空指针后,我们只需知晓头节点的地址,便可依据连接关系,访问整个链表。
因此,一般在进行增删查找操作时,都需要传递参数 ptail
(头节点指针),它是链表的入口(链表的关键参数)。
1.2 链表插入分二种情况
总结来说:就说改变原链表phead指向的的为一类,其他的为另一类
头插 链表为空的尾插和头插 都需要改变phead值
1.2.1 头插 或 链表为空的插入 都需要重新给phead赋值
无论链表是否为空,均可通过 newcode->next = phead
将 newcode
连接到 phead
。若链表为空,则 newcode->next
为 NULL
;若链表不为空,则 newcode
指向原链表的第一个节点。
注意:通常应先处理 newcode->next
和 data
,可先保存下一个节点的指向。最后,令 phead = newcode
。
1.2.2 尾插或者中间插
插入的节点,需连接其前面和后面。
此处为何未考虑尾插且链表为空的情况呢?在上面 1.2.1 中“链表为空时的插入”已涵盖此情况。
1.3 链表的删除 注意:当无节点时,不可删除
总结来说:就说改变原链表phead指向的的为一类,其他的为另一类
即:头删为一类 其他的为另外一类
2.链表各种接口的实现
2.1 链表的打印
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;//相当于指针的后移一位
}
printf("NULL\n");
}
2.1 链表的节点的申请
// 动态申请一个结点
SLTNode* BuySLTNode(SLTDataType x)
{
//同样不需要断言
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
if (newnode == NULL)//如果malloc失败
{
perror("malloc fail");
return NULL;
}
//如果malloc成功
newnode->data = x;//插入的数据
newnode->next = NULL;//初始化为空
return newnode;//返回newnode 节点地址
}
2.2 单链表节点增加
2.2.1 单链表指定位置插入
方法1:
// 在pos的前面插入x
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
if (pos == *pphead||*pphead == NULL)
//头插 和 phead为空的情况下的头插和尾插
{
//发现plist不管是否为空,头插的方法都一样
//why 头插都需要改变phead指向
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
else
{ //不包含空链表的尾插和中间插
SLTNode* newnode = BuySListNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//pos中已经保存着prev中的指向的下一个节点地址
prev->next = newnode;
newnode->next = pos;
}
}
方法2:
/ 在链表中插入节点 positon
void insert(struct Node** headRef, int position, int value) {
// 创建新节点并为其分配内存
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败。\n");
return;
}
newNode->data = value;
// 如果要插入的位置是链表的头部或链表为空
if (*headRef == NULL || position == 0) {
newNode->next = *headRef;
*headRef = newNode;
return;
}
struct Node* current = *headRef;
int count = 1;
// 找到要插入位置的前一个节点
while (current->next != NULL && count < position) {
current = current->next;
count++;
}
// 在指定位置插入节点
newNode->next = current->next;
current->next = newNode;
}
2.2.2 单链表尾插
tail为空指针的前一个节点 尾节点指向NULL 即 tail->next
// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);//pphead是phead的地址,不能为空
SLTNode* newnode = BuySLTNode(x);
//找尾(尾插之前先找到尾)
if (*pphead == NULL)//若链表为空
{
*pphead = newnode;//把新节点作为头结点
}
else//若链表不为空
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
//tail为空指针的前一个节点 //尾节点指向NULL 即tail->next
//对于不为空的链表:尾插的本质是原尾结点要存新尾结点的地址
{
tail = tail->next;
}
tail->next = newnode;//挪动的指针指向新节点 也就是挪动的指针等于新节点的指针
}
}
2.2.3 单链表头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
//发现plist不管是否为空,头插的方法都一样
//why 头插都需要改变phead指向
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.3 单链表节点删除
2.3.1 单链表指定位置删除
方法一
// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(*pphead);//防止链表为空,还删除节点
if (pos == *pphead)
{
SLTNode* next = (*pphead)->next;
//free(节点之前,需要先找到下一个节点,之后才能删除)
free(*pphead);
*pphead = next;
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
方法二
// 在链表中删除节点
void delete(struct Node** headRef, int value) {
struct Node* current = *headRef;
struct Node* prev = NULL;
// 处理头节点为目标节点的情况
if (current != NULL && current->data == value) {
*headRef = current->next;
free(current);
return;
}
// 遍历链表找到要删除的节点
while (current != NULL && current->data != value) {
prev = current;
current = current->next;
}
// 如果找到了目标节点,则删除它
if (current != NULL) {
prev->next = current->next;
free(current);
}
}
2.3.1 单链表头删
void SListPopFront(SLTNode** pphead)
{
SLTNode* next = (*pphead)->next;
//free(节点之前,需要先找到下一个节点,之后才能删除)
free(*pphead);
*pphead = next;
}
2.3.1 单链表尾删
void SListPopBack(SLTNode** pphead)
{
// 1、空
// 2、一个节点
// 3、一个以上的节点
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;//尾删之后,前一个节点要置空
}
}
##2.4 查找某个值在链表中位置,返回该地址,找不到返回NULL
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SListNode* cur = phead;
//while (cur != NULL)
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
2.4 链表节点的释放
void SListDestroy(SLTNode** pphead)
{
SLTNode* current = *pphead;
SLTNode* cur_next;
while (current != NULL)//等价于while(current)
{
cur_next = current->next;
free(current);
current = cur_next;
}
经典单链表例题:
1.逆序 1>2>3>4>5 变成 5>4>3>2>1
需要定义三个指针变量,记录当前节点,前一个节点,和后移节点的地址
struct ListNOde* reverselist(struct ListNode* head)
{
struct ListNode* n1,*n2,*n3;
n1=NULL;
n3=head->next;
while(n2==NULL) //循环终止条件
{
n2->next=n1;
n2=n3;
n3=n3->next;
}