//头插
//让新节点指向原来的头指针(节点),即新节点位于开头
newnode->next = plist;
//再让头指针(节点)指向新节点,新节点就成为了头节点
plist = newnode;
此操作在链表为空的情况下也能正常运行。
// 单链表尾插
//第一个参数为头指针的拷贝(形参)
void SListPushBack(SLTNode* phead, SLTDataType x)
{
SLTNode* tail = phead;
//创建要插入的新节点
SLTNode* newnode = BuySListNode(x);
//遍历下一个节点指向为空的节点
while (tail->next != NULL)
{
tail = tail->next;
}
//将该节点与新节点链接起来
tail->next = newnode;
}
phead,tail,newnode为局部变量,出了作用域就会自动销毁,而链表的节点存在于堆上,不会自动销毁。
需要让新节点充当头节点,也就是要让 plist(结构体指针)(头指针) 指向新节点,因此我们尝试将新创建的节点赋给头指针
if (phead == NULL)
{
phead = newnode;
}
但这个做法对吗,显然不对,形参是实参的一份临时拷贝,改变形参不影响实参,出了这个作用域,这两个指针就销毁了,plist也没有改变。
plist 是结构体类型的指针,要改变它的值,在函数中就需要传它的地址,也就是指针的地址。
//第一个参数为头指针的拷贝(形参)
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
//如果链表为空
//*pphead==plist
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
//创建要插入的新节点
//遍历下一个节点指向为空的节点
while (tail->next != NULL)
{
tail = tail->next;
}//将该节点与新节点链接起来
tail->next = newnode;
}
}
总结:
改变结构体用结构体指针;
改变结构体指针用结构体二级指针;
3.3.3头插
本篇开头已经在函数外实现过了,现在在函数中实现一次。
每一次头插都要改变 plist 头指针,因此也需要传二重指针
// 单链表的头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
3.3.4尾删
根据剩余节点的不同,分3种情况
1.链表为空
这是一种异常的情况,我们需要使用断言对参数加以限制,以防传空指针情况的出现。
assert(*pphead);
2.链表只剩一个节点
再删掉就为空,这时候就需要释放节点的空间,并将头指针置空,就涉及到了头指针的改变,需要引用二级指针。
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
3.链表中包含>1个节点
用 tail 找到末尾节点并将其删除,将倒数第二个节点置空,该情况下不需要二级指针。
原理图:
SLTNode* tailPre = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tailPre = tail;
tail = tail->next;
}
free(tail);
tailPre->next = NULL;
3.3.5头删
让头指针指向第二个节点,将第一个节点释放。
// 单链表头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
//第二个节点
SLTNode* newhead = (*pphead)->next;
//释放第一个节点
free(*pphead);
//让第二个节点成为新的头节点
*pphead = newhead;
}
完整代码:
头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//打印链表
void SLTPrint(SLTNode* pahead);
//开辟一个节点并赋值
SLTNode* BuySLTNode(SLTDataType X);
// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
// 单链表的尾删
void SLTPopBack(SLTNode** pphead);
// 单链表头删
void SLTPopFront(SLTNode** pphead);
测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void TestSList1()
{
int n = 0;
printf("请输入链表的长度\n");
scanf("%d", &n);
printf("请依次输入每个节点的值\n");
//创建头指针
SLTNode* plist = NULL;
for (int i = 0; i < n; i++)
{
int val = 0;
scanf("%d", &val);
//开辟新节点
SLTNode* newnode = BuySLTNode(val);
//头插
//让新节点指向原来的头指针(节点),即新节点位于开头
newnode->next = plist;
//再让头指针(节点)指向新节点,新节点就成为了头节点
plist = newnode;
}
SLTPushBack(&plist, 100);
SLTPrint(plist);
}
void TestSList2()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
SLTPushFront(&plist, 10);
SLTPushFront(&plist, 20);
SLTPushFront(&plist, 30);
SLTPushFront(&plist, 40);
SLTPrint(plist);
}
void TestSList3()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
// SLTPopBack(&plist);
// SLTPrint(plist);
}
void TestSList4()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
//SLTPopFront(&plist);
SLTPrint(plist);
}
//void TestSList5()
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
// SLTPushBack(&plist, 5);
// SLTPrint(plist);
//
// SLTNode* pos = SLTFind(plist, 3);
// SLTInsert(&plist, pos, 30);
//}
int main()
{
//TestSList1();
TestSList2();
/*TestSList3();
TestSList4();
TestSList5();*/
return 0;
}
实现文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
//结束,打印空
printf("NULL\n");
}
//开辟节点并赋值
SLTNode* BuySLTNode(SLTDataType X)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
newnode->data = X;
newnode->next = NULL;
return newnode;
}
// 单链表尾插
//第一个参数为头指针的拷贝(形参)
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
//如果链表为空
//*pphead==plist
if (*pphead == NULL)
{
//改变结构体指针,用结构体二级指针
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
//创建要插入的新节点
//遍历下一个节点指向为空的节点
while (tail->next != NULL)
{
tail = tail->next;
}
//改变结构体用结构体指针,将该节点与新节点链接起来
tail->next = newnode;
}
}
// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
//限制参数不为空
assert(*pphead);
//仅有一个节点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//有两个及以上节点的情况
else
{
//尾节点的前一个节点
SLTNode* tailPre = NULL;
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tailPre = tail;
//tail往后走之前赋给前一个指针
tail = tail->next;
}
free(tail);
tailPre->next = NULL;
}
}
// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
//第二个节点
SLTNode* newhead = (*pphead)->next;
//释放第一个节点
free(*pphead);
//让第二个节点成为新的头节点
*pphead = newhead;
}