有头单向链表操作的分文件封装函数实现。
1.创建空表
malloc开辟一块结构体大小的空间作为头结点,通过if判断空间四否开辟成功,然后对头节点进行初始化,刚开始让头节点指向null;
步骤:
(1)malloc开辟一块结构体大小的空间作为头节点
(2)对空间进行初始化,让头节点指向null
// 1.创建一个空的单向链表(有头单向链表)
link_node_t *CreateEpLinkList()
{
// 开辟空间作为头节点
link_node_t *head = (link_node_t *)malloc(sizeof(link_node_t));
if (head == NULL)
{
perror("creat malloc error");
return NULL;
}
// 初始化头节点
head->next = NULL;
return head;
}
2.向指定位置插入数据
步骤:
(1)将指针移动到参入位置的前一个节点
(2)用指针pnew保存新建的节点地址,pnew = malloc();
(3)将数据存入新节点,也就是初始化新节点,
(4)将新节点插入链表,先连后边再连前边
插入数据时,需要先判断一下插入位置是否合理,不能越界。
向指定位置插入数据,需要新建节点来存放需要插入的数据,需要malloc开辟一块空间。
指定位置插入数据,只需要把新建的节点跟插入位置前后的两个节点通过指针域连接到一块。
但是电脑不知道需要插入的位置所在的存储空间地址,就需要定义一个结构体类型的指针,让头指针移动到插入位置前边的节点,这样通过该节点的指针域就可以获得插入位置的地址。
在连接时,只能先把新建节点与后面的节点连接,再把新建与前边的节点相连,因为前面节点的指针域里存放的是后面一个节点的地址,如果先连前面,当前面的指针域指向新建节点时,后面节点的地址就找不到了,也就无法连接到后面的节点了。
// 2.向单向链表的指定位置插入数据
// p保存链表的头指针 post 插入的位置 data插入的数据
int InsertIntoPostLinkList(link_node_t *p, int post, datatype data)
{
// 容错判断,如果post越界,插入失败
if (post < 0 || post > LengthLinkList(p))
{
perror(" InsertIntoPostLinkList error 插入失败");
return -1;
}
// 指针移动到插入位置
for (int i = 0; i < post; i++)
{
p = p->next;
}
// 新建节点用来保存插入的数据
link_node_t *pnew = (link_node_t *)malloc(sizeof(link_node_t));
// 初始化新建节点,存入数据,
pnew->data = data;
pnew->next = NULL;
// 连接 先连后边,再连前边
pnew->next = p->next;
p->next = pnew;
return 0;
}
3.遍历单向链表
遍历有头单向链表,指针先跨过头节点,然后输出每个节点的数据域,当指针与为空时,链表到尾,停止遍历。
// 3.遍历单向链表
void ShowLinkList(link_node_t *p)
{
while (p->next != NULL)
{
p = p->next;
printf("%d ", p->data);
}
printf("\n");
}
4.求单向链表的长度
求单向链表的长度,通过while循环来移动指针,只要指针域不为空,就代表该节点有数据,每循环一次,指针向后移动一位,长度加一。
// 4.求单向链表长度的函数
int LengthLinkList(link_node_t *p)
{
int length = 0;
while (p->next != NULL)
{
p = p->next;
length++;
}
return length;
}
5.删除指定位置的数据
步骤:
(1)将指针指向被删除的节点的前一个
(2)指针pdel指向要被删除的节点: pdel = p->next
(3)跨过要删除的节点:p->next = pdel ->next
(4)释放要删除的节点:free(pdel)
删除指定位置数据,也需要先判断一下,如果链表为空,或者山粗的位置越界,那么就无法删除。
删除指定位置数据,只需要把删除的节点跨过去就行了,但是需要先找到删除节点的地址,删除节点的地址存放在前边的节点指针域中,通过指针移动到前边那个节点。
删除节点后,该节点的空间需要释放,如果直接诶把该节点跳过,那么就找不到该节点的地址了,也就无法释放该节点了。所以需要定义一个中间指针,指向删除的节点,用来暂存删除的节点。
等把删除的节点跨过之后,通过中间指针释放删除节点的空间。由于链表中的每个结点都是malloc
开辟出来的,故需要一个PDel
指针指向结点,将其释放掉。
// 5.删除单向链表中指定位置的数据 post 代表的是删除的位置
int DeletePostLinkList(link_node_t *p, int post)
{
if (IsEpLinkList(p) || post < 0 || post > LengthLinkList(p))
{
printf("无法删除 \n");
return -1;
}
// 移动指针到删除位置的前面
for (int i = 0; i < post; i++)
{
p = p->next;
}
// 定义指针指向需要删除的节点,暂存
link_node_t *pdel = p->next;
// 跨过需要删除的节点
p->next = pdel->next;
// p->next = p->next->next;
// 释放删除节点的空间
free(pdel);
pdel = NULL;
return 0;
}
6.修改指定位置的数据
在链表中插入或删除某个结点,不需要移动元素,仅修改指针即可。
步骤:
(1)移动指针到修改位置的节点
(2)对要修改的节点的数据域进行赋值
// 7.修改指定位置的数据 post 被修改的位置 data修改成的数据
int ChangePostLinkList(link_node_t *p, int post, datatype data)
{
// 容错判断,如果为空,或者越界,那么修改失败
if (IsEpLinkList(p) || post < 0 || post > LengthLinkList(p))
{
printf("无法删除 \n");
return -1;
}
// 移动指针到需要修改的节点
for (int i = 0; i <= post; i++)
{
p = p->next;
}
// 修改数据域,重新赋值
p->data = data;
return 0;
}
7.查找指定数据出现的位置
步骤:
(1)循环遍历节点,移动指针
(2)每个节点的数据域跟查找的数据进行比较,相等就输出下标,不相等就继续移动指针,同时下标+1
// 8.查找指定数据出现的位置 data被查找的数据 //search 查找
int SearchDataLinkList(link_node_t *p, datatype data)
{
int n = 0;
while (p->next != NULL)
{
p = p->next;
if (p->data == data)
{
printf("%d 出现在下标为 %d 的位置\n", data, n);
return 0;
}
n++;
}
printf("%d 未找到\n", data);
return 0;
}
8.删除链表中出现的指定数据
步骤:
(1)跨过头节点,从首元节点开始遍历单链表,相当于用pdel指针遍历无头链表
(2)判断节点中的数据是否为要删除的数据
(3)如果节点中为要删除的数据,那么就跨过被删除节点,然后释放被删除节点,然后pdel继续向后遍历
(4)如果节点不是需要删除的数据,那么p和pdel都向后移动一位
通过遍历链表,循环对比节点的数据,可以删除链表中出现的指定元素。
// 9.删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
int DeleteDataLinkList(link_node_t *p, datatype data)
// 写法一
{
if (IsEpLinkList(p))
{
printf("表为空,无法删除 \n");
return -1;
}
// 跨过头节点
link_node_t *pdel = p->next;
while (pdel != NULL)
{
// 判断节点的值是否为删除的数据
if (pdel->data == data)
{
// 跨过被删除的节点
p->next = pdel->next;
// 释放被删除的节点
free(pdel);
pdel = NULL;
// 此时p指针已经指向后一个节点,只需要移动pdel即可
// 指针后移
pdel = p->next;
}
else
{
// 指针后移
p = p->next;
pdel = p->next;
}
}
return 0;
}
9.链表逆置
步骤:
(1)断头:将头节点与链表断开,断开之前保存下一个节点的位置,避免断开之后找不到后续链表,定义一个q保存头节点的下一个节点,断开后的链表相当于q为首元节点的一个无头链表。
(2)遍历:遍历无头链表的所有节点,将每一个节点当新节点插入空链表(头插法),头插之前先用temp保存一下q的下一个节点,避免插入q之后找不到后续的链表了。
(3)头插:先将q节点与后边的节点相连,在把头节点与q节点相连。注意必须先连后边再连前边,否则后面的链表节点会丢失。
(4) 循环移动指针:将q指针移动到无头链表的第一个节点,temp指向q的下一个节点,循环进行头插。
// 10.转置链表
// 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面的链表能够找到
// 定义一个q保存头节点的下一个节点,断开后相当于无头节点
// 遍历无头链表的所有节点,将每一个节点当做新节点插入空链表的下一个节点(头插),
// 头插之前先用temp保存一下q的下一个节点以防插入完之后找不到后续节点
void ReverseLinkList(link_node_t *p)
{
// 用于暂存头指针后的节点
link_node_t *q = p->next;
link_node_t *temp = NULL;
// 断开头节点与链表
p->next = NULL;
while (q != NULL)
{
// 让temp暂存q的下一个节点,以防头插q之后找不到原来的链表
temp = q->next;
// 将q插入到头节点之后,先连后边,再连前边
q->next = p->next;
p->next = q;
// 移动q到temp,继续遍历链表
q = temp;
}
}
10.清空单向链表
清空单向链表,其实通过循环,把链表中每一个节点都删除,只需要在原先的删除指定位置节点的基础上,加入一个循环,通过循环遍历删除链表中的所有节点。
// 11.清空单向链表
void ClearLinkList(link_node_t *p)
{
link_node_t *pdel = NULL;
while (p->next != NULL)
{
// pdel暂存要删除的节点
pdel = p->next;
// 跨过删除节点
p->next = pdel->next;
// 释放删除的节点
free(pdel);
pdel = NULL;
}
if (IsEpLinkList(p))
{
printf("表已清空\n");
}
}
11.头文件
封装结构体,进行函数声明
#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include <stdio.h>
#include <stdlib.h>
typedef int datatype; // int 重命名为 datatype
typedef struct node_t
{
datatype data; // 数据域
struct node_t *next; // 指针域,指向自身结构体的指针
} link_node_t, *link_list_t;
// 1.创建一个空的单向链表(有头单向链表)
link_node_t *CreateEpLinkList();
// 2.向单向链表的指定位置插入数据
// p保存链表的头指针 post 插入的位置 data插入的数据
int InsertIntoPostLinkList(link_node_t *p, int post, datatype data);
// 3.遍历单向链表
void ShowLinkList(link_node_t *p);
// 4.求单向链表长度的函数
int LengthLinkList(link_node_t *p);
// 5.删除单向链表中指定位置的数据 post 代表的是删除的位置
int DeletePostLinkList(link_node_t *p, int post);
// 6.判断单向链表是否为空 1代表空 0代表非空
int IsEpLinkList(link_node_t *p);
// 7.修改指定位置的数据 post 被修改的位置 data修改成的数据
int ChangePostLinkList(link_node_t *p, int post, datatype data);
// 8.查找指定数据出现的位置 data被查找的数据 //search 查找
int SearchDataLinkList(link_node_t *p, datatype data);
// 9.删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
int DeleteDataLinkList(link_node_t *p, datatype data);
// 10.转置链表
void ReverseLinkList(link_node_t *p);
// 11.清空单向链表
void ClearLinkList(link_node_t *p);
#endif
12.完整函数体
#include "linklist.h"
// 1.创建一个空的单向链表(有头单向链表)
link_node_t *CreateEpLinkList()
{
// 开辟空间作为头节点
link_node_t *head = (link_node_t *)malloc(sizeof(link_node_t));
if (head == NULL)
{
perror("creat malloc error");
return NULL;
}
// 初始化头节点
head->next = NULL;
return head;
}
// 2.向单向链表的指定位置插入数据
// p保存链表的头指针 post 插入的位置 data插入的数据
int InsertIntoPostLinkList(link_node_t *p, int post, datatype data)
{
// 容错判断,如果post越界,插入失败
if (post < 0 || post > LengthLinkList(p))
{
perror(" InsertIntoPostLinkList error 插入失败");
return -1;
}
// 指针移动到插入位置
for (int i = 0; i < post; i++)
{
p = p->next;
}
// 新建节点用来保存插入的数据
link_node_t *pnew = (link_node_t *)malloc(sizeof(link_node_t));
// 初始化新建节点,存入数据,
pnew->data = data;
pnew->next = NULL;
// 连接 先连后边,再连前边
pnew->next = p->next;
p->next = pnew;
return 0;
}
// 3.遍历单向链表
int ShowLinkList(link_node_t *p)
{
if (IsEpLinkList(p))
{
printf("表为空 \n");
return -1;
}
while (p->next != NULL)
{
p = p->next;
printf("%d ", p->data);
}
printf("\n");
return 0;
}
// 4.求单向链表长度的函数
int LengthLinkList(link_node_t *p)
{
int length = 0;
while (p->next != NULL)
{
p = p->next;
length++;
}
return length;
}
// 5.删除单向链表中指定位置的数据 post 代表的是删除的位置
int DeletePostLinkList(link_node_t *p, int post)
{
if (IsEpLinkList(p) || post < 0 || post > LengthLinkList(p))
{
printf("无法删除 \n");
return -1;
}
// 移动指针到删除位置的前面
for (int i = 0; i < post; i++)
{
p = p->next;
}
// 定义指针指向需要删除的节点,暂存
link_node_t *pdel = p->next;
// 跨过需要删除的节点
p->next = pdel->next;
// p->next = p->next->next;
// 释放删除节点的空间
free(pdel);
pdel = NULL;
return 0;
}
// 6.判断单向链表是否为空 1代表空 0代表非空
int IsEpLinkList(link_node_t *p)
{
return p->next == NULL;
}
// 7.修改指定位置的数据 post 被修改的位置 data修改成的数据
int ChangePostLinkList(link_node_t *p, int post, datatype data)
{
// 容错判断,如果为空,或者越界,那么修改失败
if (IsEpLinkList(p) || post < 0 || post > LengthLinkList(p))
{
printf("无法删除 \n");
return -1;
}
// 移动指针到需要修改的节点
for (int i = 0; i <= post; i++)
{
p = p->next;
}
// 修改数据域,重新赋值
p->data = data;
return 0;
}
// 8.查找指定数据出现的位置 data被查找的数据 //search 查找
int SearchDataLinkList(link_node_t *p, datatype data)
{
int n = 0;
while (p->next != NULL)
{
p = p->next;
if (p->data == data)
{
printf("%d 出现在下标为 %d 的位置\n", data, n);
return 0;
}
n++;
}
printf("%d 未找到\n", data);
return 0;
}
// 9.删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
int DeleteDataLinkList(link_node_t *p, datatype data)
// 写法一
{
if (IsEpLinkList(p))
{
printf("表为空,无法删除 \n");
return -1;
}
// 跨过头节点
link_node_t *pdel = p->next;
while (pdel != NULL)
{
// 判断节点的值是否为删除的数据
if (pdel->data == data)
{
// 跨过被删除的节点
p->next = pdel->next;
// 释放被删除的节点
free(pdel);
pdel = NULL;
// 此时p指针已经指向后一个节点,只需要移动pdel即可
// 指针后移
pdel = p->next;
}
else
{
// 指针后移
p = p->next;
pdel = p->next;
}
}
return 0;
}
// 10.转置链表
// 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面的链表能够找到
// 定义一个q保存头节点的下一个节点,断开后相当于
// 遍历无头链表的所有节点,将每一个节点当做新节点插入空链表的下一个节点(头插),
// 头插之前先用temp保存一下q的下一个节点以防插入完之后找不到后续节点
void ReverseLinkList(link_node_t *p)
{
// 用于暂存头指针后的节点
link_node_t *q = p->next;
link_node_t *temp = NULL;
// 断开头节点与链表
p->next = NULL;
while (q != NULL)
{
// 让temp暂存q的下一个节点,以防头插q之后找不到原来的链表
temp = q->next;
// 将q插入到头节点之后,先连后边,再连前边
q->next = p->next;
p->next = q;
// 移动q到temp,继续遍历链表
q = temp;
}
}
// 11.清空单向链表
void ClearLinkList(link_node_t *p)
{
link_node_t *pdel = NULL;
while (p->next != NULL)
{
// pdel暂存要删除的节点
pdel = p->next;
// 跨过删除节点
p->next = pdel->next;
// 释放删除的节点
free(pdel);
pdel = NULL;
}
if (IsEpLinkList(p))
{
printf("表已清空\n");
}
}