【数据结构】C语言实现单链表基本操作
实现单链表的基本操作并测试
1、定义单链表的存储结构
//单链表的存储结构
typedef struct LNode
{
ElemType data;
struct LNode *next; //next指向自身类型struct LNode *的指针
}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型
2、初始化单链表
建立一个只有头结点的空链表。
//初始化单链表
/*生成新结点作为头结点,头指针L指向头结点
*头结点的指针域设置为空NULL
*/
Status InitList(LinkList *L)
{
*L=(LinkList)malloc(sizeof(LNode)); //生成新结点作为头结点并指向头指针L
(*L)->next = NULL; //头结点的指针域设置空
return OK;
}
3、头插法建立链表
由于前期卡在了这,这里是另一篇记录文章,简单描述了一下整个头插法过程。
/*头插法(前插法)
*/
void CreateList_H(LinkList *L,int n) //形参L 是指向inkList类型的指针变量指针(用于指向新开辟储存单元),逆序储存n个元素值
{
*L = (LinkList)malloc(sizeof(LNode)); //为头结点开辟储存空间,并使头指针指向头节点
(*L)->next = NULL; //含头结点的空链表
int i; //元素数量计数
printf("请输入%d个链表元素:",n);
for(i=0;i<n;i++) //循环输入元素
{
LNode *p=(LinkList)malloc(sizeof(LNode)); //为新结点开辟空间并令指针指向该地址
scanf("%d",&p->data); //向新结点数据域输入数据
p->next = (*L)->next; //头结点指针域的值(即新结点后继结点地址)赋给新结点指针域进行连接
(*L)->next = p; //指针域指针连接顺序不可颠倒 新结点地址赋值给头结点指针域进行连接
}
}
4、尾插法建立单链表。
/*尾插法(后插法)
*/
void CreateList_R(LinkList *L,int n)
{
LNode *r;
*L = (LinkList)malloc(sizeof(LNode)); //头指针指向新开辟的头结点
(*L)->next = NULL; //创建空链表,指针域设置为NULL
r = (*L); //尾指针指向头结点
int i;
printf("请输入%d个链表元素:",n);
for(i=0;i<n;i++)
{
LNode *p = (LinkList)malloc(sizeof(LNode)); //为新结点p开辟储存空间
scanf("%d",&p->data); //为新结点输入数据
p->next = NULL; //新结点作为尾结点,其指针域设置为NULL
r->next = p; //尾指针指向的结点的指针域设置为p结点的地址进行连接
r = p; //尾指针向前移动,指向新的尾结点
}
}
5、判断单链表是否为空。
/*判断链表是否为空
* 思路:判断头结点指针域是否为空
*/
Status ListEmpty(LinkList L)
{
if(L->next) return FALSE; //非空 0
return TRUE; //空 1
}
6、输出单链表长度。
/*求链表长度
* 思路:从首元结点开始,依次计数所有结点,长度不包括头结点
*/
Status ListLength(LinkList L)
{
LNode *p;
p = L->next; //从首元结点开始
int len = 0;
while(p) //循环条件设置为非空
{
++len; //第一次循环开始,首元结点非空则长度为1,之后循环都是指向下一个结点再判断是否为空再+1
p = p->next; //指向下一结点
}
return len; //返回长度
}
7、取值
按结点位置进行取值
//取值
Status GetElem(LinkList L,int i,ElemType *e) //形参L为指向头结点的指针
{
int j=1;
LNode *p; //定义指向LNode类型的指针变量
p=L->next; //p指向首元结点
while(p&&j<i) //循环条件为p非空和取值位置i合法(大于1),i等于1相当于取首元结点的data值
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR; //i不合法即返回ERROR,p为空(可能空表或是i值超过表长即i>n) i<=0也是不合法
*e = p->data;
return OK;
}
8、按值查找
即输入结点所储存数据值进而输出地址。
/*按值查找,返回元素位置
* 1、从首元结点开始找
* 2、直到找到结点data值等于e或者超过表长指向空值
* 3、最后返回p的指针地址值(数据域等于data的指针值或者NULL)
*/
LNode *LocateElem(LinkList L,ElemType e)
{
LNode *p;
p = L->next;
while(p && p->data!=e)
p=p->next;
return p;
}
9、单链表插入结点
/*链表插入元素
* 1、寻找i位置前驱结点
* 2、判断前驱结点地址以及插入位置是否合理
* 3、开辟新结点储存单元
* 4、新结点数据域赋值插入元素值,指针域指向前驱结点下一个结点地址
* 5、将前驱结点指针域指向新结点地址
*/
Status ListInsert(LinkList L,int i,ElemType e) //输入插入位置i、同类型ElemType元素值
{
LNode *p,*s; //定义LNode类型指针变量
p = L; //p指向L头结点
int j = 0; //定义计数
while(p && j<i-1) //循环条件 p非空和j小于i-1(i的前驱结点位置)一般情况下:j=i-1结束循环
{
p = p->next; //p继续指向下一个结点
j++; //位置数累计
}
if(!p || j>i-1) return ERROR; //p为前驱结点的指针值(地址),为NULL则超链表长;j>i-1表示位置小于0
s = (LinkList)malloc(sizeof(LNode)); //为新插入结点开辟空间并指针s指向该储存单元
s->data = e; //将插入数据赋值给储存单元数据域
s->next = p->next; //新结点指针域指向下一个结点ai
p->next = s; //前去结点指针域指向新结点
return OK;
}
10、删除结点
/*链表删除元素
* 1、找到前驱结点
* 2、判断前驱结点指针域和前驱结点位置是否合理
* 3、将前驱结点指针域指向的下一个结点的指针域赋值给前驱结点的指针域,形成跨元素连接
* 4、最后释放内存
*/
Status ListDelete(LinkList L,int i)
{
LNode *p,*q;
p = L; //指针p指向头结点
int j=0;
while(p->next && j<i-1) //判断前驱结点指针域是否为空和前驱位置值
{
p = p->next;
j++;
}
if(!(p->next) || j>i-1) return ERROR; //判断删除位置合理与否:前驱结点指针域是否为空,为空证明超链长了
q = p->next;
p->next = q->next; //前驱结点指针域指向的指针域的值 赋给 前驱结点的指针域 有点向二重指针的感觉
free(q); //释放空间
return OK;
}
11、清空链表
该链表仍存在,只是头结点的指针域的值为NULL
/*清空链表
*依次释放所有结点,将头结点指针域设置为空
*/
Status ClearList(LinkList L)
{
LNode *p,*q; //指针p所指结点释放内存,指针q则是记录下一个结点(辅助作用)
p = L->next; //一开始将p指向首元结点
while(p) //循环条件是p非空
{
q=p->next; //将q指向下一个结点
free(p); //释放p
p=q; //将p和q同步
}
L->next = NULL; //最后将头结点指针域设置为空
return OK;
}
12、销毁链表
该链表彻底删除,最后头指针指向NULL。
/*销毁单链表
* 思路:从头结点开始,依次释放所有结点
*/
Status DestroyList(LinkList L)
{
LNode *p; //定义辅助结点p
while(L) //循环条件为非空
{
p = L; //p指向当前的L
L = L->next; //L指向下一个结点,相当于L++
free(p); //释放辅助结点p所指的结点
}
return OK;
}
13、遍历链表。
//遍历链表
void print(LinkList L)
{
LNode *p;
p = L->next; //p的值为首元结点地址值,进而一开始就指向首元结点
while(p) //循环条件是下一个结点地址为空结束
{
printf("%d ",p->data);
p = p->next; //继续指向下一个结点
}
}
最后,附上所有代码(含main函数测试代码)
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef int ElemType;
//单链表的存储结构
typedef struct LNode
{
ElemType data;
struct LNode *next; //next指向自身类型struct LNode *的指针
}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型
//初始化单链表
/*生成新结点作为头结点,头指针L指向头结点
*头结点的指针域设置为空NULL
*/
Status InitList(LinkList *L)
{
*L=(LinkList)malloc(sizeof(LNode)); //生成新结点作为头结点并指向头指针L
(*L)->next = NULL; //头结点的指针域设置空
return OK;
}
//取值
Status GetElem(LinkList L,int i,ElemType *e) //形参L为指向头结点的指针
{
int j=1;
LNode *p; //定义指向LNode类型的指针变量
p=L->next; //p指向首元结点
while(p&&j<i) //循环条件为p非空和取值位置i合法(大于1),i等于1相当于取首元结点的data值
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR; //i不合法即返回ERROR,p为空(可能空表或是i值超过表长即i>n) i<=0也是不合法
*e = p->data;
return OK;
}
//遍历链表
void print(LinkList L)
{
LNode *p;
p = L->next; //p的值为首元结点地址值,进而一开始就指向首元结点
while(p) //循环条件是下一个结点地址为空结束
{
printf("%d ",p->data);
p = p->next; //继续指向下一个结点
}
}
/*判断链表是否为空
* 思路:判断头结点指针域是否为空
*/
Status ListEmpty(LinkList L)
{
if(L->next) return FALSE; //非空 0
return TRUE; //空 1
}
/*销毁单链表
* 思路:从头结点开始,依次释放所有结点
*/
Status DestroyList(LinkList L)
{
LNode *p; //定义辅助结点p
while(L) //循环条件为非空
{
p = L; //p指向当前的L
L = L->next; //L指向下一个结点,相当于L++
free(p); //释放辅助结点p所指的结点
}
return OK;
}
/*清空链表
*依次释放所有结点,将头结点指针域设置为空
*/
Status ClearList(LinkList L)
{
LNode *p,*q; //指针p所指结点释放内存,指针q则是记录下一个结点(辅助作用)
p = L->next; //一开始将p指向首元结点
while(p) //循环条件是p非空
{
q=p->next; //将q指向下一个结点
free(p); //释放p
p=q; //将p和q同步
}
L->next = NULL; //最后将头结点指针域设置为空
return OK;
}
/*求链表长度
* 思路:从首元结点开始,依次计数所有结点,长度不包括头结点
*/
Status ListLength(LinkList L)
{
LNode *p;
p = L->next; //从首元结点开始
int len = 0;
while(p) //循环条件设置为非空
{
++len; //第一次循环开始,首元结点非空则长度为1,之后循环都是指向下一个结点再判断是否为空再+1
p = p->next; //指向下一结点
}
return len; //返回长度
}
/*按值查找,返回元素位置
* 1、从首元结点开始找
* 2、直到找到结点data值等于e或者超过表长指向空值
* 3、最后返回p的指针地址值(数据域等于data的指针值或者NULL)
*/
LNode *LocateElem(LinkList L,ElemType e)
{
LNode *p;
p = L->next;
while(p && p->data!=e)
p=p->next;
return p;
}
/*链表插入元素
* 1、寻找i位置前驱结点
* 2、判断前驱结点地址以及插入位置是否合理
* 3、开辟新结点储存单元
* 4、新结点数据域赋值插入元素值,指针域指向前驱结点下一个结点地址
* 5、将前驱结点指针域指向新结点地址
*/
Status ListInsert(LinkList L,int i,ElemType e) //输入插入位置i、同类型ElemType元素值
{
LNode *p,*s; //定义LNode类型指针变量
p = L; //p指向L头结点
int j = 0; //定义计数
while(p && j<i-1) //循环条件 p非空和j小于i-1(i的前驱结点位置)一般情况下:j=i-1结束循环
{
p = p->next; //p继续指向下一个结点
j++; //位置数累计
}
if(!p || j>i-1) return ERROR; //p为前驱结点的指针值(地址),为NULL则超链表长;j>i-1表示位置小于0
s = (LinkList)malloc(sizeof(LNode)); //为新插入结点开辟空间并指针s指向该储存单元
s->data = e; //将插入数据赋值给储存单元数据域
s->next = p->next; //新结点指针域指向下一个结点ai
p->next = s; //前去结点指针域指向新结点
return OK;
}
/*链表删除元素
* 1、找到前驱结点
* 2、判断前驱结点指针域和前驱结点位置是否合理
* 3、将前驱结点指针域指向的下一个结点的指针域赋值给前驱结点的指针域,形成跨元素连接
* 4、最后释放内存
*/
Status ListDelete(LinkList L,int i)
{
LNode *p,*q;
p = L; //指针p指向头结点
int j=0;
while(p->next && j<i-1) //判断前驱结点指针域是否为空和前驱位置值
{
p = p->next;
j++;
}
if(!(p->next) || j>i-1) return ERROR; //判断删除位置合理与否:前驱结点指针域是否为空,为空证明超链长了
q = p->next;
p->next = q->next; //前驱结点指针域指向的指针域的值 赋给 前驱结点的指针域 有点向二重指针的感觉
free(q); //释放空间
return OK;
}
/*头插法(前插法)
*/
void CreateList_H(LinkList *L,int n) //形参L 是指向inkList类型的指针变量指针(用于指向新开辟储存单元),逆序储存n个元素值
{
*L = (LinkList)malloc(sizeof(LNode)); //为头结点开辟储存空间,并使头指针指向头节点
(*L)->next = NULL; //含头结点的空链表
int i; //元素数量计数
printf("请输入%d个链表元素:",n);
for(i=0;i<n;i++) //循环输入元素
{
LNode *p=(LinkList)malloc(sizeof(LNode)); //为新结点开辟空间并令指针指向该地址
scanf("%d",&p->data); //向新结点数据域输入数据
p->next = (*L)->next; //头结点指针域的值(即新结点后继结点地址)赋给新结点指针域进行连接
(*L)->next = p; //指针域指针连接顺序不可颠倒 新结点地址赋值给头结点指针域进行连接
}
}
/*尾插法(后插法)
*/
void CreateList_R(LinkList *L,int n)
{
LNode *r;
*L = (LinkList)malloc(sizeof(LNode)); //头指针指向新开辟的头结点
(*L)->next = NULL; //创建空链表,指针域设置为NULL
r = (*L); //尾指针指向头结点
int i;
printf("请输入%d个链表元素:",n);
for(i=0;i<n;i++)
{
LNode *p = (LinkList)malloc(sizeof(LNode)); //为新结点p开辟储存空间
scanf("%d",&p->data); //为新结点输入数据
p->next = NULL; //新结点作为尾结点,其指针域设置为NULL
r->next = p; //尾指针指向的结点的指针域设置为p结点的地址进行连接
r = p; //尾指针向前移动,指向新的尾结点
}
}
//测试函数
int main()
{
LinkList L; //定义指向LNode类型的指针变量L
ElemType *e;
int num,status,num_1,n,i; //num:操作序号 status:函数状态值 num_1:赋值操作序号 n:插入的元素 i:插入链表位置序号
printf("****************单链表基本操作测试*******************\n");
printf(" 1-初始化空链表 2-建立链表\n");
printf(" 3-链表是否空 4-输出链表长度\n");
printf(" 5-取值 6-按值查找\n");
printf(" 7-插入元素 8-删除元素\n");
printf(" 9-清空链表 10-销毁链表\n");
printf(" 11-遍历链表 12退出测试系统\n");
printf("***************单链表基本操作测试********************\n");
while(1)
{
printf("\n请输入操作序号:");
scanf("%d",&num);
switch (num)
{
case 1:
{
status = InitList(&L);
if(status)printf("链表初始化已完成~\n");
else printf("链表初始化失败~\n");
};
break;
case 2:
{
printf("\n***********选择赋值方式***************\n");
printf(" 1-头插法 2-尾插法\n");
printf("选择赋值方式:");
scanf("%d",&num_1);
printf("为多少个元素赋值:");
scanf("%d",&n);
if(num_1==1) CreateList_H(&L,n);
else if(num_1==2) CreateList_R(&L,n);
else printf("方式选择有误~\n");
}; break;
case 3:
{
if(ListEmpty(L)) printf("链表为空~\n");
else printf("链表非空~\n");
};break;
case 4:
{
status = ListLength(L);
printf("链表长度为%d\n",status);
};break;
case 5:
{
printf("请输入取出元素位置:");
scanf("%d",&i);
GetElem(L,i,e);
printf("位置%d元素值为%d\n",i,*e);
};break;
case 6:
{
LNode *p;
printf("请输入目标元素值:");
scanf("%d",&n);
p=LocateElem(L,n);
printf("该值指针地址为:%d\n",p);
};break;
case 7:
{
printf("请输入需插入的目标元素:");
scanf("%d",&n);
printf("请输入插入目标位置:");
scanf("%d",&i);
ListInsert(L,i,n);
};break;
case 8:
{
printf("请输入删除结点位置:");
scanf("%d",&i);
if(ListDelete(L,i)) printf("删除操作已完成~\n");
};break;
case 9:
{
if(ClearList(L)) printf("链表已清空~\n");
else printf("清空操作失败~\n");
};break;
case 10:
{
if(DestroyList(L)) printf("链表已销毁~\n");
else printf("销毁操作失败~\n");
};break;
case 11:
{
printf("当前链表:");
print(L);
printf("\n");
};break;
case 12:
{
printf("成功退出测试系统~\n");
exit(1);
};break;
default: printf("输入操作序号有误~\n");
}
}
}