前言
在各种信息管理系统的程序设计中,常常需要到大量的数据记录表格,如果采用结构体数组存储这些数据,会出现一些问题。其一是数组必须定义固定的长度,程序运行时数组元素数目也是固定的,若元素数比数组长度长,则会造成越界问题。所以当我们使用结构体数组时,必须尽可能的定义最大数组长度,这样可能会造成内存浪费。其二是在数组中删除与插入元素都需要移动数组中的很多元素,效率很低。
为了更好的解决这些问题,我们可以采用动态存储分配的数据结构——链表。它的特点是用则申请,不用则释放,插入与删除也较为方便。能大大提高空间利用率与时间效率。
在学习链表之前我们来了解一下存储空间的分配和释放
存储空间的分配和释放
C语言标准函数库中提供了四个函数:malloc()、calloc()、realloc()、free(),用来实现内存的动态分配与释放。前三个用来分配内存,第四个用来释放。链表中最常用的是malloc()与free(),下面来简单介绍一下。
- 申请存储空间函数malloc()
malloc()函数原型为
void *malloc(unsigned int size);
其功能是在内存的动态存储区申请一个长度为size字节的连续存储空间。并返回该存储空间的起始地址。如果没有足够空间可分配,则返回空指针NULL。 - 释放存储空间函数free()
free()函数原型为
void free(void *p);
其功能是将指针变量p指向的存储空间释放。free函数无返回值。需要说明的是,p只能是程序中此前最后一次调用malloc或calloc函数所返回的地址。
链表的定义
链表是一种最常见的采用动态存储分配方式的数据结构。在链表中,每个元素称为结点。结点通常由数据域与指针域构成。
数据域存储数据信息,指针域用来连接链表。
如果每个结点只有一个指针域,除了末尾结点指向NULL外其余都指向下一个结点,一个连接一个,称为单向链表或单链表。本次我们主要来讲解一下单链表。
创建链表前的准备工作
创建链表前我们需要定义一个结构体,其中包括数据域与指针域
typedef struct Node
{
int data; //数据域
struct Node *next; //指针域
}Node;
创建链表
链表分为有头结点链表与无头结点链表
头结点为单链表的第一个元素结点之前设置的一个结点,该结点内不存放数据,只用来指向单链表的第一个元素。对于链表来说,头结点可有可无,但是为了方便操作,通常使用有头结点链表。(本篇使用的是有头结点链表)
Node* CraetList()
{
Node* head = (Node*)malloc(sizeof(Node)); //为头结点分配内存空间
if (head == NULL) //如果没有分配成功
{
printf("malloc error!");
return NULL;
}
head->next = NULL; //初始化头结点的指针
return head;
}
链表的增加
- 头插法(在链表第一个元素之前插入一个新元素)
Node *Headadd(Node * head) //头插法
{
int a;
printf("输入你想添加到链表里的信息个数");
scanf("%d",&a); //可以利用头插法插入a个元素
while(a--)
{
Node * n = (Node *)malloc(sizeof(Node)); //为新节点分配内存空间
int b;
printf("输入元素信息\n");
scanf("%d",&b); //输入你要添加的元素值
n->data = b; //将你要输入的值赋给新结点的数据域
n->next = head->next; //将新结点的指针指向第一个元素结点
head->next = n; //更新head
}
return head;
}
- 尾插法(在链表最后一个元素后添加元素)
Node * InsertListEnd(Node * head,int x) //尾插法
{
Node * n= (Node *)malloc(sizeof(Node));
n->data=x;
n->next=NULL; //初始化指针
Node * p=head; //定义一个指针指向head
while (p->next) //遍历链表,使p指向最后一个元素
{
p=p->next;
}
p->next=n; //将最后一个结点的指针指向新结点
return head;
}
删除链表中某个结点
删除结点需要找到该结点的前一个元素,令前一个结点的指针指向该结点的后一个结点即可。
Node * DelList(Node * head,int x) //删除结点(数据域为x的结点)
{
Node *p=head;
int n=0; //计数器(记录x的出现次数),用于后面判断是否找到值为x的结点
while(p->next) //遍历链表,找到数据域为x的结点的前结点
{
if(p->next->data == x)
{
p->next=p->next->next; //改变前结点的指向
n++;
}
else
p=p->next;
}
if(p->data==x) //判断最后一个结点的值与x是否相等
{
p=NULL;
n++;
}
if(n==0)
printf("未找到该结点");
return head;
}
修改链表某个结点的值
修改结点的值首先需要找到该结点,再进行修改
Node* Change(Node* head,int a,int b) //寻找数据为a的结点,将其数据改为b
{
Node* p = head->next;
while(p) //遍历链表寻找数据为a的结点
{
if(p->data == a)
{
p->data = b; //改变结点的值
break;
}
p=p->next;
}
return head;
}
链表内结点的查找
寻找元素为x的结点,若找到返回该结点,找不到返回空指针
Node* Find(Node* head,int x)
{
Node* p = head->next;
while(p)
{
if(p->data == x)
return p;
p=p->next;
}
printf("未找到该元素");
return NULL;
}
打印链表
void Print(Node *head) //打印
{
Node*p=head->next;
if(p!=NULL)
{
printf("%d",p->data);
p=p->next;
}
while(p)
{
printf("->%d",p->data);
p=p->next;
}
printf("\n");
return;
}
小结
- 此篇博客主要介绍了一些单链表的简单操作,链表在c语言中有着重要的作用,关于链表的其他应用与双向链表会在后期介绍。
- 以上代码为测试样例,若有错误欢迎指正!!!
- 部分图片来源于