文章目录
前言
单链表创建、单链表打印、一级和二级指针做参数、尾插、头插、尾删、头删、查找指定数值位置、指定位置之前插入、指定位置删除、指定位置之后插入、指定位置之后删除等的介绍
链表的图示
一、 单链表节点创建
单链表节点的创建
typedef int SLTDataType;
// 链表节点类型的创建
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
- data的类型使用typedef进行重命名,方便修改data的类型
- 节点的第二个成员为 下一个节点的指针(地址)。
二、 单链表的打印
单链表的打印定义
// 单链表的打印
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
- phead是一个指针变量,他存放的是单链表第一个节点的地址,通过 -> 可以访问第一个节点的成员。
- 将phead存放到变量cur中,防止可能再次使用无法找到phead。
- 判断若cur不为NULL,则打印当前cur的data。并且将cur置为cur->next(下一个节点的地址)。
- 判断若cur为NULL,跳出循环。打印NULL。
三、 一级和二级指针做参数
- 对于一个整形变量,我们若要通过函数来改变这个变量的值。
- 直接参数是无法实现的,因为形参是实参的一份临时拷贝。如下:
void func(int n)
{
n = 1;
}
int main()
{
int a = 0;
func(a);
printf("%d ", a);// 0
}
- 若通过函数改变一个整形变量的数值,可以通过传指针(地址),在函数中解引用的方式来实现。如下:
void func1(int* n)
{
*n = 1;
}
int main()
{
int a = 0;
func1(&a);
printf("%d ", a);// 1
}
- 同理可得,若通过函数改变一个一级指针变量,传入一个一级指针是无法实现的,如下:
void func2(int* n)
{
n = (int*)malloc(sizeof(int));
}
int main()
{
int* pa = NULL;
func2(pa);
printf("%p", pa);// 0000000000000000
}
- 同理可得,若要通过函数改变一个一级指针变量,需要传入一个二级指针。
- 在函数中对二级指针进行解引用来改变一级指针变量。如下:
void func3(int** n)
{
*n = (int*)malloc(sizeof(int));
}
int main()
{
int* pa = NULL;
func3(&pa);
printf("%p", pa);// 000002733CFA7BC0
}
- 图解
四、单链表尾插
单链表尾插定义
- 由上述一二级指针的关系,可得
- 若初始状态链表为空(NULL)则尾插需要改变传入的第一个节点的地址。所以需要传入二级指针。
- 若初始状态链表不为空,则找尾,并进行尾插。
// 创建新节点并初始化的函数
SLTNode* BuyNewNode(SLTDataType x)
{
// 创建新的节点
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("BuyNewNode()::malloc()");
return;
}
// 初始新节点的数据
newnode->data = x;
newnode->next = NULL;
return newnode;
}
// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyNewNode(x);
// 判断是否单链表头节点地址为空
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
// 找尾
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
单链表尾插测试
void SLTest01()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
}
效果如下:
五、单链表头插
单链表头插定义
- 头插同理也需要传入二级指针。
- 新节点的next指针指向原来的头节点
- 头节点又重新指向新的节点。
// 创建新节点并初始化的函数
SLTNode* BuyNewNode(SLTDataType x)
{
// 创建新的节点
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("BuyNewNode()::malloc()");
return;
}
// 初始新节点的数据
newnode->data = x;
newnode->next = NULL;
return newnode;
}
// 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
// 创建新的节点
SLTNode* newnode = BuyNewNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
单链表头插测试
void SLTest02()
{
SLTNode* plist = NULL;
// 头插
SLTPushFront(&plist, 1);
SLTPrint(plist);
SLTPushFront(&plist, 2);
SLTPrint(plist);
SLTPushFront(&plist, 3);
SLTPrint(plist);
SLTPushFront(&plist, 4);
SLTPrint(plist);
}
效果如下:
六、单链表尾删
单链表尾删定义
- 找到单链表最后一个节点以及最后一个节点的前一个节点
- free释放最后一个节点。
- 并将最后一个节点的前一个节点置为空(NULL)。
// 尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead != NULL);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
SLTNode* prev = NULL;
// 找尾
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
单链表尾删测试
void SLTest03()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 尾删
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
}
效果如下:
五、单链表头删
单链表头删定义
- 先将链表第二个节点的地址存储
- 然后释放第一个节点。
- 最后将链表第一个节点的指着指向存储的第二个节点地址。
// 头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead != NULL);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tmp = (*pphead)->next;
free(*pphead);
*pphead = tmp;
}
}
单链表头删测试
void SLTest04()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 头删
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
/*SLTPopFront(&plist);
SLTPrint(plist);*/
}
效果如下:
六、单链表查找指定数值位置
查找指定数值位置函数定义
- 遍历链表,判断每一个节点的data数据是否等于指定数值x
- 若存在相等,则返回当前节点的地址。
- 若没有相等,则返回空指针NULL。
// 查找指定数值位置
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
查找指定数值位置函数测试
void SLTest05()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 查找指定数值2的位置
SLTNode* ret = SLTFind(plist, 2);
if (ret != NULL)
{
// 将数值为2的节点数据乘2
ret->data *= 2;
SLTPrint(plist);
}
else
{
printf("没找到!!!\n");
}
}
效果如下:
七、指定位置之前插入
指定位置之前插入函数定义
- 若pos位置为第一个节点的地址,则直接调用头插
- 若不是
- 创建新的节点,找到pos节点之前的节点。
- 将pos节点之前的节点的next指向新节点。
- 新节点的next指向pos节点。
// 指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
// 创建新的节点
SLTNode* newnode = BuyNewNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
指定位置之前插入函数测试
void SLTest06()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 查找指定数值2的位置
SLTNode* ret = SLTFind(plist, 2);
SLTInsert(&plist, ret, 30);
SLTPrint(plist);
}
效果如下:
八、指定位置删除
指定位置删除函数定义
- 找到指定位置之前的节点(prev)。
- 将prev的next指向指定位置(pos)的next节点。
/ 指定位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
指定位置删除函数测试
void SLTest07()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 查找指定数值2的位置
SLTNode* ret = SLTFind(plist, 2);
SLTErase(&plist, ret);
SLTPrint(plist);
}
效果如下:
九、指定位置之后插入
指定位置之后插入函数定义
- 若指定位置pos为最后一个节点,则直接调用尾插
- 若pos不是最后一个节点
- 新建一个节点
- 将新节点的next指向pos的next节点。
- 将pos的next指向新节点。
// 指定位置之后插入
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushBack(pphead, x);
}
else
{
// 创建新的节点
SLTNode* newnode = BuyNewNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
}
指定位置之后插入函数测试
void SLTest08()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 查找指定数值2的位置
SLTNode* ret = SLTFind(plist, 2);
SLTInsertAfter(&plist, ret, 60);
SLTPrint(plist);
}
效果如下:
十、指定位置之后删除
指定位置之后删除函数定义
- 创建临时变量del存储pos的下一个节点。
- 将pos的next指向del的next节点。
- free释放del节点。
- 将del置为NULL。
// 指定位置之后删除
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
指定位置之后删除函数测试
void SLTest09()
{
SLTNode* plist = NULL;
// 尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
// 查找指定数值2的位置
SLTNode* ret = SLTFind(plist, 2);
SLTEraseAfter(&plist, ret);
SLTPrint(plist);
}
效果如下:
总结
单链表创建、单链表打印、一级和二级指针做参数、尾插、头插、尾删、头删、查找指定数值位置、指定位置之前插入、指定位置删除、指定位置之后插入、指定位置之后删除等的介绍