C语言中单链表的基本操作(创建、销毁、增删查改等)
作者:安河桥畔
这篇文章主要介绍了C语言中单链表的基本操作(创建、销毁、增删查改等),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
链表分类
链表主要有下面三种分类方法:
- 单向或者双向
- 带头或者不带头
- 循环或者非循环综合来看链表有八种类型,本文主要针对的是不带头节点的非循环单链表。
单链表的介绍
typedef struct SListNode { DataType data;//数据域 struct SListNode *next;//结构体指针,指向下一个节点 }SListNode;//类型别名
如图
链表的每一个节点由数据域和指针域构成,数据域存放数据,指针域中的指针指向下一个节点。
plist表示链表的指针,指向链表的第一个节点,最后一个节点的指针为空。
单链表的基本操作
创建
创建单链表有几点需注意:
- 链表与顺序表的区别是,顺序表是物理空间上连续的,而链表只在逻辑上连续,所以链表申请空间时是使用一个申请一个,顺序表则是一次申请一段空间,空间不足时进行扩容。
- 如果在栈上申请空间,在函数调用结束后会释放,所以需要在堆区申请空间。
- 每次申请一个节点都要存入数据,所以链表总是满的,而顺序表则可能有一段空间没有利用。
- 函数的返回值是指向节点的结构体类型的指针
SListNode* BuySListNode(DataType x) { SListNode* plist = (SListNode*)malloc(sizeof(SListNode)); if (plist == NULL) { return NULL;//判断是否申请成功 } plist->data = x; plist->next = NULL; return plist; }
打印
遍历链表,进行打印
void SListPrint(SListNode* plist) { assert(plist); SListNode* cur = plist; while (cur) { printf("%d-->", cur->data); cur = cur->next; } printf("NULL\n"); }
尾插
尾插的操作步骤:
- 若链表为空,*pplist指向新插入的节点
- 链表不为空则遍历链表,找到最后一个节点
- 令最后一个节点的next指向新插入的节点
- 新插入的节点next指向NULL
注意事项:
- 因为插入元素要改变原链表中指针的指向,故传参时要传入二级指针。
- assert(pplist)是判断链表是否存在,因为pplist是指向链表的指针的地址,若pplist为空,说明链表的地址不存在,即链表不存在;而如果(*pplist)为空,表示的是该链表是空链表。
void SListPushBack(SListNode** pplist, DataType x) { //改变指针指向,参数传二级指针 assert(pplist);//判断链表是否存在,与链表是否为空不同 //1.若链表为空,*pplist指向插入的节点 if (*pplist == NULL) { *pplist = BuySListNode(x); } else { //2.链表不为空,指针移动到链表最后一个节点,其next指向插入的节点 SListNode* cur = *pplist; while (cur->next) { cur = cur->next;//cur的next为空时,cur指向最后一个节点 } cur->next = BuySListNode(x); } }
头插
头插操作顺序:
- 申请一个新节点
- 新节点的next指向原来的第一个节点,即*pplist
- 改变*pplist指向,使其指向新增的节点
进行头插时,要注意各个步骤的顺序,如果直接令*pplist指向了新增的的节点,会导致原有的第一个节点无法找;另外,链表为空时的操作方法与链表非空时代码可以合并,不用再分开写各种情况。
void SListPushFront(SListNode** pplist, DataType x) { assert(pplist); //if (NULL == *pplist) //{ // //链表为空 // *pplist = BuySListNode(x); //} //else //{ // SListNode* temp = *pplist;//temp指向链表原来的第一个节点 // *pplist = BuySListNode(x);//plist指向新增的节点 // (*pplist)->next = temp;//新增的节点指向原来的第一个节点 //} //上面两种情况代码可以合并 SListNode* node = BuySListNode(x);//申请一个新节点 node->next = *pplist;//新增的节点的next指向原来的第一个节点 *pplist = node;//*pplist指向新增的节点 }
尾删
尾删步骤:
- 判断链表是否为空或只有一个结点
- 遍历找到最后一个节点的前驱结点prev
- 令prev的next指向NULL
- 释放原来最后一个节点申请的空间
注意事项:
- 区分链表为空、单个结点、多个结点各种情况
- 不能找到最后一个节点并将其置空,而是要找到其前驱节点,断开与最后一个节点的连接
- 删除节点后要释放空间,避免内存泄漏
void SListPopBack(SListNode** pplist) { assert(pplist); //1.链表为空 if (NULL== *pplist) { return; } //2.链表只有一个元素 else if (NULL == (*pplist)->next) { free(*pplist); *pplist = NULL; } //3.链表有多个元素 else { SListNode* prev = NULL; SListNode* cur = *pplist; while (cur->next) { prev = cur; cur = cur->next;//循环结束时cur指向最后一个节点 } //cur= NULL;//这里不能写cur=NULL,需要找到cur的前一个节点,将其next置空\ 否则前一个结点的next依然指向原来的最后一个节点 prev->next = NULL;//prev成为最后一个节点 free(cur);//释放原来最后一个节点的空间 }
头删
头删的操作步骤:
- 保存第一个节点的指针信息
- 令*pplist指向第二个节点
- 释放原来的第一个节点的空间
同样的,头删也要注意保存原来第一个节点的位置,否则*pplist指向改变后,原来的第一个节点就找不到了,会无法释放空间造成内存泄漏。
void SListPopFront(SListNode** pplist) { assert(pplist); //1.单链表为空 if (NULL == *pplist) { return; } 2.单链表有一个节点 //else if (NULL == (*pplist)->next) //{ // *pplist = NULL;//删除后链表为空 //} 3.单链表有多个节点 //else //{ //*pplist= (*pplist)->next; //} //两种情况可以合并,只有一个节点时,*pplist的next为空 else { SListNode* delNode = *pplist; *pplist = delNode->next; free(delNode);//释放删除节点的空间 } }
查找
用于查找某一元素是否存在于链表中,若存在则返回其第一次出现在链表中的位置,不存在则返回NULL。
遍历时注意循环条件。
SListNode* SListFind(SListNode* plist, DataType x) { SListNode* cur = plist; while (cur) { if (cur->data == x) { return cur; } else { cur = cur->next; } } return NULL; }
任意位置插入
pos节点后插入的步骤:
- 申请一个新的节点
- 新增节点的next指向原pos的next
- pos的next指向新增的节点
注意事项
- 任意位置的插入操作只能在给定节点的后面插入,前面的节点无法同通过给出的节点找到。
- 要注意插入的操作顺序,否则原来链表pos后的节点可能会找不到
void SListInsertAfter(SListNode* pos, DataType x) { assert(pos);//指针合法性校验 SListNode* newNode = BuySListNode(x); newNode->next = pos->next; pos->next = newNode; }
任意位置删除
与任意位置的插入相同,只能删除给定节点pos后面的节点
void SListDeleteAfter(SListNode* pos) { assert(pos); //1.链表有一个节点 if (NULL == pos->next) { return; } //2.链表有多个节点 else { SListNode* temp = pos->next; pos->next = temp->next; free(temp); } }
销毁
链表的销毁,遍历一遍,逐个释放空间
void SListDestroy(SListNode** pplist) { assert(pplist);//链表是否存在 //1.链表为空 if (NULL == *pplist) { return; } else { SListNode* cur = NULL; while (*pplist) { cur = *pplist; *pplist = (*pplist)->next; free(cur); } } }
完整代码
work.h
头文件包含,函数声明,定义结构体
#pragma once #include<stdio.h> #include<Windows.h> #include<assert.h> #include<assert.h> typedef int DataType; typedef struct SListNode { DataType data;//数据域 struct SListNode *next;//结构体指针,指向下一个节点 }SListNode;//类型别名 //函数声明 SListNode* BuySListNode(DataType x);//节点申请 void SListPrint(SListNode* pst);//单链表遍历打印 void SListPushBack(SListNode** pplist, DataType x);//单链表尾插 void SListPushFront(SListNode** pplist, DataType x);//单链表头插 void SListPopBack(SListNode** pplist);//单链表尾删 void SListPopFront(SListNode** pplist);//单链表头删 SListNode* SListFind(SListNode* plist, DataType x);//单链表查找 void SListInsertAfter(SListNode* pos, DataType x);//pos后位置的插入 void SListDeleteAfter(SListNode* pos);//pos后位置的删除 void SListDestroy(SListNode** pplist);//释放链表空间
work.c
各操作函数的具体实现
#include"work.h" //链表与顺序表的区别是,顺序表是物理空间上连续的 //而链表只在逻辑上连续,所以链表申请空间时是使用一个申请一个 //顺序表则是一次申请一段空间 SListNode* BuySListNode(DataType x) { //若在栈申请内存函数调用结束后会释放,所以使用动态申请 SListNode* plist = (SListNode*)malloc(sizeof(SListNode)); if (plist == NULL) { return NULL;//判断是否申请成功 } plist->data = x; plist->next = NULL; return plist; } void SListPrint(SListNode* plist) { assert(plist); SListNode* cur = plist; while (cur) { printf("%d-->", cur->data); cur = cur->next; } printf("NULL\n"); } //尾插法 void SListPushBack(SListNode** pplist, DataType x) { //改变指针指向,参数传二级指针 assert(pplist);//判断链表是否存在,与链表是否为空不同 //1.若链表为空,*pplist指向插入的节点 if (*pplist == NULL) { *pplist = BuySListNode(x); } else { //2.链表不为空,指针移动到链表最后一个节点,其next指向插入的节点 SListNode* cur = *pplist; while (cur->next) { cur = cur->next;//cur的next为空时,cur指向最后一个节点 } cur->next = BuySListNode(x); } } //头插法 void SListPushFront(SListNode** pplist, DataType x) { assert(pplist); //if (NULL == *pplist) //{ // //链表为空 // *pplist = BuySListNode(x); //} //else //{ // SListNode* temp = *pplist;//temp指向链表原来的第一个节点 // *pplist = BuySListNode(x);//plist指向新增的节点 // (*pplist)->next = temp;//新增的节点指向原来的第一个节点 //} //链表为空的情况可以和不为空合并 SListNode* node = BuySListNode(x);//申请一个新节点 node->next = *pplist;//新增的节点的next指向原来的第一个节点 *pplist = node;//*pplist指向新增的节点 } //尾删法? void SListPopBack(SListNode** pplist) { assert(pplist); //1.链表为空 if (NULL== *pplist) { return; } //2.链表只有一个元素 else if (NULL == (*pplist)->next) { free(*pplist); *pplist = NULL; } //3.链表有多个元素 else { SListNode* prev = NULL; SListNode* cur = *pplist; while (cur->next) { prev = cur; cur = cur->next;//循环结束时cur指向最后一个节点 } //cur= NULL;//这里不能写cur=NULL,需要找到cur的前一个节点,将其next置空\ 否则前一个结点的next依然指向原来的最后一个节点 prev->next = NULL;//prev最后一个节点 free(cur);//释放原来最后一个节点的空间 } } //头删 void SListPopFront(SListNode** pplist) { assert(pplist); //1.单链表为空 if (NULL == *pplist) { return; } 2.单链表有一个节点 //else if (NULL == (*pplist)->next) //{ // *pplist = NULL;//删除后链表为空 //} 3.单链表有多个节点 //else //{ //*pplist= (*pplist)->next; //} //两种情况可以合并,只有一个节点时,*pplist的next为空 else { SListNode* delNode = *pplist; *pplist = delNode->next; free(delNode);//释放删除节点的空间 } } //单链表查找 SListNode* SListFind(SListNode* plist, DataType x) { SListNode* cur = plist; while (cur) { if (cur->data == x) { return cur; } else { cur = cur->next; } } return NULL; } //任意位置的插入 //只能在pos的后面插入 void SListInsertAfter(SListNode* pos, DataType x) { assert(pos);//指针合法性校验 SListNode* newNode = BuySListNode(x); newNode->next = pos->next; pos->next = newNode; } //任意位置的删除 //只能删除给定的pos后面的节点 void SListDeleteAfter(SListNode* pos) { assert(pos); //1.链表有一个节点 if (NULL == pos->next) { return; } //2.链表有多个节点 else { SListNode* temp = pos->next; pos->next = temp->next; free(temp); } } // 链表空间释放 void SListDestroy(SListNode** pplist) { assert(pplist);//链表是否存在 //1.链表为空 if (NULL == *pplist) { return; } else { SListNode* cur = NULL; while (*pplist) { cur = *pplist; *pplist = (*pplist)->next; free(cur); } } }
main.c
程序入口,测试用例
#include"work.h" void Test() { SListNode* node = NULL;//定义一个结构体指针 //尾插法插入五个节点 SListPushBack(&node, 1); SListPushBack(&node, 2); SListPushBack(&node, 3); SListPushBack(&node, 4); SListPushBack(&node, 5); SListPrint(node);//遍历打印 SListPushFront(&node, 0);//头插一个节点 SListPrint(node);//遍历打印 SListPopBack(&node);//尾删最后一个节点 SListPrint(node);//遍历打印 SListPopFront(&node);//头删第一个节点 SListPrint(node);//遍历打印 printf("%p\n", SListFind(node, 4));//查找3在链表中的位置 printf("%p\n", SListFind(node, 99));//查找99在链表中的位置 SListInsertAfter(SListFind(node, 4), 99);//4后面插入一个节点99 SListPrint(node);//遍历打印 SListDeleteAfter(SListFind(node, 4));//删除4的下一个节点 SListPrint(node);//遍历打印 } int main() { Test(); system("pause"); return 0; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。