一,头文件
在了解了链表的定义等基础知识之后,我们需要将定义转化为程序;
首先我们需要将概念的思想转化为程序的思路:
①单向有头链表,存在一个空的头节点;
②链表中节点逻辑上按顺序排列,存储时节点之间通过指针连接,存储空间上不一定连续;
③节点需要存放数据,还需要存放下一个节点的地址,由此我们可知链表结构体中的成员变量包括一个数据域"datatype data;",和一个指针域"结构体类型 *p;"
④头节点的数据域为空,没有数据,指针域指向链表中的第一个有数据的节点;当链表为空时,头节点的指针域指向空;
了解了上述内容后,就容易理解程序;
程序主要掌握空链表的创建,数据的插入和删除,以及链表的转置。
头文件程序代码如下:
#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct link_node
{
datatype data;//数据域
struct link_node *next;//指针域
}link_t;
//1.创建一个有头单向链表
link_t *CreateEpLinklist(void);
//2.向指定位置插入数据
int InsertIntoLinklist(link_t *p,int post,datatype data);
//3.计算链表长度
int LengthLinklist(link_t *p);
//4.在指定位置删除数据
int DeleteLinklist(link_t *p,int post);
//5.遍历链表
void ShowLinklist(link_t *p);
//6.判断链表是否为空
int isEpLinklist(link_t *p);
//7.查询链表指定位置的数据
int SearchPostLinklist(link_t *p,int post);
//8.查询指定数据在链表的位置
int SearchDataLinklist(link_t *p,datatype data);
//9.修改链表中指定的数据
int ChangeDataLinklist(link_t *p,datatype old,datatype new);
//10.修改链表中指定位置的数据
int ChangePostLinklist(link_t *p,int post,datatype new);
//11.删除链表中指定的数据
void DeleteDataLinklist(link_t *p,datatype data);
//12.链表的转置
void ReserveLinklist(link_t *p);
//13.清空链表
void ClearLinklist(link_t *p);
//14.销毁链表
void DestoryLinklist(link_t **p);
#endif
二,函数代码
1.创建一个空的单向有头链表
先使用malloc开辟一个结构体空间,然后将指针域指向空,数据域不赋值,即无数据,就创建好了一个单向链表的空的头结点。
//创建一个有头单向链表
link_t *CreateEpLinklist(void)
{
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
printf("CreateEpLinklist fail\n");
return NULL;
}
p->next = NULL;//数据域无效,指针域有效
return p;
}
2.向链表中指定位置插入数据
插入数据时,需要判断插入的位置是否合理;
将指向空的头节点的指针指向要插入位置的前一个节点;
开辟一个新的结构体空间;
给结构体中的数据域赋值,指针域指向该位置的后一位的节点,也就是头节点指向的当前节点的下一个节点(因为新节点还没插入,新节点要插入到两个节点之间);
将头节点当前指向的节点,其指针域指向新节点,新节点才算插入成功;
//向指定位置插入数据
int InsertIntoLinklist(link_t *p,int post,datatype data)
{
int i;
link_t *pnew = NULL;
//容错判断
if(post < 0 || post > LengthLinklist(p))//插入数据时,可能会插入最后一位的后一位
{
printf("InsertIntoLinklist fail\n");
return -1;
}
//将头指针指向post-1位置的节点
for(i = 0;i < post;i++)
{
p = p->next;
}
//创建新节点
pnew = (link_t *)malloc(sizeof(link_t));
if(NULL == pnew)
{
printf("pnew create fail\n");
return -1;
}
pnew->data = data;
pnew->next = NULL;
//新节点先指向post-1指向的节点,post-1再指向新节点
pnew->next = p->next;
p->next = pnew;
return 0;
}
3.在指定位置删除数据
删除数据时也需要判断删除位置的合理性;
头节点也需要指向要删除节点的前一个节点;
然后用一个临时指针指向要删除的节点;
让头节点当前指向的节点,其指针域指向要删除结点的下一个节点;
将要删除的节点释放,并指向NULL。
//在指定位置删除数据
int DeleteLinklist(link_t *p,int post)
{
link_t *pdel = NULL;
//容错判断
//后面的所有相关指定位置post的容错判断,因为都表示的是下标,指定的post不能超出下标,所以需要减1
if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
{
printf("DeleteLinklist fail\n");
return -1;
}
//将节点指向要删除的节点的前一个节点post-1
for(int i = 0;i < post;i++)
{
p = p->next;
}
//新建一个临时指针指向要删除的post节点
pdel = p->next;
//要删除的节点的前一个节点post-1指向要删除的节点的后一个节点post+1
p->next = pdel->next;
//释放要删除的节点
free(pdel);
pdel = NULL;
return 0;
}
4.链表的转置
转置,对于链表来说,可以理解为除去空的头节点,将其他的原来的节点倒过来排序;
先将空的头节点和第一个有数据的节点断开,生成一个空的头节点和一个没有无头链表;
然后将无头链表中的所有节点循环插入到空的头节点的后面,而且每次插入节点都要插入到空的头节点的后一位;
void ReserveLinklist(link_t *p)
{
link_t *temp = NULL;//接收无头节点的插入节点的下一个节点
link_t *q = NULL;//接收无头节点的第一个节点
q = p->next; //让q接收断开的无头节点的第一个节点
p->next = NULL;//让原本的头节点后面指向空,也就是断开链表
while(q != NULL)//所有的节点都要插入,所以应该是对q判断,不应该是对q->next判断;q所在的节点为空了,才停止插入
{
temp = q->next;//先保存下来无头节点中要插入节点的下一个节点
q->next = p->next;//插入的节点后面要链接之前插入的节点;一开始后面链接的是空,下一次插入后面链接的就是无头节点的第一个节点
p->next = q;//循环插入空的头节点后面
q = temp;//让q移动到无头节点的下一个节点
}
}
//或
void ReserveLinklist(link_t *p)
{
link_t *temp = NULL;
link_t *pa = p->next;
p->next = NULL;
while(pa != NULL)
{
temp = pa; //保存下来要插入的节点
pa = pa->next; //无头链表向后移动
temp->next = NULL; //要插入的节点后面指向空,也就是把它和无头链表断开
temp->next = p->next; //插入的节点后面接上已经插入的节点;
p->next = temp; //节点插入;
}
}
三,详细代码
#include "linklist.h"
//1.创建一个有头单向链表
link_t *CreateEpLinklist(void)
{
link_t *p = (link_t *)malloc(sizeof(link_t));
if(NULL == p)
{
printf("CreateEpLinklist fail\n");
return NULL;
}
p->next = NULL;//数据域无效,指针域有效
return p;
}
//2.向指定位置插入数据
int InsertIntoLinklist(link_t *p,int post,datatype data)
{
int i;
link_t *pnew = NULL;
//容错判断
if(post < 0 || post > LengthLinklist(p))//插入数据时,可能会插入最后一位的后一位
{
printf("InsertIntoLinklist fail\n");
return -1;
}
//将头指针指向post-1位置的节点
for(i = 0;i < post;i++)
{
p = p->next;
}
//创建新节点
pnew = (link_t *)malloc(sizeof(link_t));
if(NULL == pnew)
{
printf("pnew create fail\n");
return -1;
}
pnew->data = data;
pnew->next = NULL;
//新节点先指向post-1指向的节点,post-1再指向新节点
pnew->next = p->next;
p->next = pnew;
return 0;
}
//3.计算链表长度,len是长度,从1开始;post是下标,从0开始;
int LengthLinklist(link_t *p)
{
int len = 0;
while(p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
//4.在指定位置删除数据
int DeleteLinklist(link_t *p,int post)
{
link_t *pdel = NULL;
//容错判断
//后面的所有相关指定位置post的容错判断,因为都表示的是下标,指定的post不能超出下标,所以需要减1
if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
{
printf("DeleteLinklist fail\n");
return -1;
}
//将节点指向要删除的节点的前一个节点post-1
for(int i = 0;i < post;i++)
{
p = p->next;
}
//新建一个临时指针指向要删除的post节点
pdel = p->next;
//要删除的节点的前一个节点post-1指向要删除的节点的后一个节点post+1
p->next = pdel->next;
//释放要删除的节点
free(pdel);
pdel = NULL;
return 0;
}
//5.遍历链表
void ShowLinklist(link_t *p)
{
while(p->next != NULL)
{
p = p->next;
printf("%d ",p->data);
}
putchar(10);
printf("---------------------\n");
}
//6.判断链表是否为空
int isEpLinklist(link_t *p)
{
return p->next == NULL;
}
//7.查询链表指定位置的数据
datatype SearchPostLinklist(link_t *p,int post)
{
//容错判断
if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
{
printf("SearchPostLinklist fail\n");
return -1;
}
int i;
for(i = 0;i <= post;i++)
{
p = p->next;
}
return p->data;
}
//8.查询指定数据在链表的位置
int SearchDataLinklist(link_t *p,datatype data)
{
if(isEpLinklist(p))
{
printf("isEmptyLinklist\n");
return -1;
}
int post = 0;
while(p->next != NULL)
{
p = p->next;
if(p->data == data)
{
return post; //return之后函数结束
}
post++;
}
return -1;
}
//9.修改链表中指定的数据
int ChangeDataLinklist(link_t *p,datatype old,datatype new)
{
while(p->next != NULL)
{
p = p->next;
if(p->data == old)
{
p->data = new;
}
}
return 0;
}
//10.修改链表中指定位置的数据
int ChangePostLinklist(link_t *p,int post,datatype new)
{
//容错判断
if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
{
printf("ChangePostLinklist fail\n");
return -1;
}
int i;
for(i = 0;i <= post;i++)
{
p = p->next;
}
p->data = new;
return 0;
}
//11.删除链表中指定的数据
void DeleteDataLinklist(link_t *p,datatype data)
{
while(p->next != NULL)
{
if(p->next->data == data)
{
link_t *pdel = p->next; //必须放这,每次free后,pdel就被释放了
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
else
{
p = p->next;
}
}
}
#if 0
//12.链表的转置
void ReserveLinklist(link_t *p)
{
link_t *temp = NULL;//接收无头节点的插入节点的下一个节点
link_t *q = NULL;//接收无头节点的第一个节点
q = p->next; //让q接收断开的无头节点的第一个节点
p->next = NULL;//让原本的头节点后面指向空,也就是断开链表
while(q != NULL)//所有的节点都要插入,所以应该是对q判断,不应该是对q->next判断;q所在的节点为空了,才停止插入
{
temp = q->next;//先保存下来无头节点中要插入节点的下一个节点
q->next = p->next;//插入的节点后面要链接之前插入的节点;一开始后面链接的是空,下一次插入后面链接的就是无头节点的第一个节点
p->next = q;//循环插入空的头节点后面
q = temp;//让q移动到无头节点的下一个节点
}
}
#endif
#if 1
//12.链表的转置
void ReserveLinklist(link_t *p)
{
link_t *temp = NULL;
link_t *pa = p->next;
p->next = NULL;
while(pa != NULL)
{
temp = pa; //保存下来要插入的节点
pa = pa->next; //无头链表向后移动
temp->next = NULL; //要插入的节点后面指向空,也就是把它和无头链表断开
temp->next = p->next; //插入的节点后面接上已经插入的节点;
p->next = temp; //节点插入;
}
}
#endif
//13.清空链表
void ClearLinklist(link_t *p)
{
link_t *pdel = NULL;
while(p->next != NULL)
{
pdel = p->next;
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
//14.销毁链表
void DestoryLinklist(link_t **p) //要把main函数中创建的指针也销毁,也就是更改它的值,所以需要地址传递
{
if(!isEpLinklist(*p))
{
ClearLinklist(*p);
}
free(*p);
*p = NULL;
}