链表是一种线性存储结构。
链表的是别名链式存储结构或者单链表,用于存储逻辑关系为“一对一”的数据,其物理存储位置是随机的。 为了保证体现各数据之间的逻辑关系,链表的解决方案是:每个数据元素在存储时都配备一个指针,用于指向自己的后继元素。
链表的节点
从下图可以看出,链表每个数据的存储都由两部分组成。
1、数据元素本身,其区域称为数据域;
2、指向后继元素的指针,其区域称为指针域。
上图所示的结构在链表中称为节点。也就是说,链表实际存储的是一个个节点,而真正的数据元素包含在这些节点中,如下图(注:不完整的链表)。
那么,链表中每个节点怎么实现的呢?要用C语言的结构体,代码如下:
//声明节点结构
typedef struct Link{
int data; //数据域,用来存数据
struct Link *next; //指针域,用来指向下一元素
}link;//关键字 typedef 使用后可以为结构体命名别名
名词介绍
1、头指针:用来指向链表第一个节点的位置。指明链表的位置,便于链表的遍历。
2、头结点:未存数据的空节点,通常作为链表的第一个节点(若存在,称为有头链表,若不存在,是无头链表)。
3、首元结点:因为存在头结点的原因,链表种第一个存有数据的节点称为首元结点。
一个完整的链表包含头指针、头结点(可有可无)、首元结点和其他节点。如果头节点存在,头指针指向头结点,如果不存在,头指针指向首元结点。如下图。
创建链表
创建一个有头链表(个人感觉有头链表操作简单些),C语言实现代码如下:
//创建链表的函数,n为节点个数
link * initLink(int n)
{
int i, x;
link *head, *p, *q, *t;
//创建头节点
head = (link*)malloc(sizeof(link));
t = head;// 声明一个指针指向头结点,用于遍历链表
printf("请输入%d个数据\n",n);
for(i=1;i<=n;i++)
{
scanf("%d",&x);//创建首元结点和其他节点
p=(link*)malloc(sizeof(link));
p->data = x;
p->next = NULL;
// 建立新节点于上一节点的关系
t->next = p;//t指向它的下一个
t = t->next;//t变成它的下一个
}
return head; //返回头指针
}
链表插入元素
因为我上边创建的是有头链表,所以在首元结点前插入元素也是比较简单的。
插入元素的操作步骤:
1、将新节点的next指针指向待插入位置的后的节点;
2、将插入位置前节点的next指针指向要插入的节点。
例如在A和C之间要插入B节点:
首先要:B->next = C->next;
然后要:A->next = B;
步骤不能换,换了会造成部分链表丢失。
C语言实现代码如下:
//插入,遍历链表,如果当前节点是最后一个节点或者下一个节点大于待插入的数时插入
int insertData(link* &head, int x)
{
link *p, *t;
t = head->next;
//如果是在第一位
if(t->data >x )
{
p = (link*) malloc (sizeof(link));
p->data = x;
p->next = head->next;
head->next = p;
return 1;//返回1表示插入成功
}
//不是在第一个位置
while(t != NULL)
{
if(t->next == NULL || t->next->data >x)
{
p = (link*) malloc (sizeof(link));
p->data = x;
p->next = t->next;
t->next = p;
break;
}
t = t->next;
}
return 1;//返回1表示插入成功
}
链表删除元素
这个操作也不太难,找到指定元素后直接把它删除就行了,但是,为了对存储空间负责,我们需要手动释放删除元素的空间,只是用一下close()。
操作步骤:
1、在链表中找到想要删除的元素,直接摘除;
2、手动释放掉节点。
例如有A、B、C节点,要删除B节点
A->next = A->next->next
有的同学就想了,为什么不A->next = B->next,不是更方便吗?这样想是有问题的,因为我们只用一个指针遍历链表,不可能指向A节点,又指向B节点。要是申请两个头指针用来遍历的话,感觉也可以…。不过应该会更麻烦吧,但是可以实现上边的喲。
C语言实现代码如下:
//删除
int deleteData(link* &head, int y)
{
link *t2, *freeData;
t2 = head;
while(t2->next != NULL)
{
if(t2->next->data == y )
{
freeData = t2->next;// freeData指向被删除的节点,防止丢失
t2->next = t2->next->next;
free(freeData);//手动申请地址后,要记得释放,不过,当关闭进程是也会自动释放
return 1;
}
t2=t2->next;
}
return -1;//删除失败
}
链表查找元素
链表查找元素很慢,因为要从头遍历链表,这也是它的缺点。
没有什么办法,就是从头开始比较,直到遍历成功或者遍历到链表的尾端NULL(代表查找失败)
C语言实现代码如下:
//查询一个数
int selectData(link* &head, int m)
{
link *t = head;
while(t->next != NULL)
{
if(t->next->data == m)
{
return 1;//返回1,代表找到了
}
t=t->next;
}
return -1;//返回1,代表查找失败
}
有兴趣的朋友也可以在while循环外边定义一个flag变量,把它放到while循环里面统计步数,确定该数在链表的哪一个位置。
链表修改元素
思路:就是在链表元素查找的基础上修改一下元素,是不是很好想,嘿嘿。
//修改一个数
int updata(link* &head, int a, int b)
{
link *t = head;
while(t->next != NULL)
{
if(t->next->data == a)
{
t->next->data = b;
return 1;//返回1,代表修改成功
}
t=t->next;
}
return -1; //返回1,代表修改成失败
}
给出运行结果图:
最后附上完整C语言代码:
#include<stdio.h>
#include<stdlib.h>
//声明节点结构
typedef struct Link{
int data; //数据域,用来存数据
struct Link *next; //指针域,用来指向下一元素
}link;//关键字 typedef 使用后可以为结构体命名别名
//创建链表的函数,n为节点个数
link * initLink(int n)
{
int i, x;
link *head, *p, *q, *t;
//创建头节点
head = (link*)malloc(sizeof(link));
t = head;// 声明一个指针指向头结点,用于遍历链表
printf("请输入%d个数据\n",n);
for(i=1;i<=n;i++)
{
scanf("%d",&x);//创建首元结点和其他节点
p=(link*)malloc(sizeof(link));
p->data = x;
p->next = NULL;
// 建立新节点于上一节点的关系
t->next = p;//t指向它的下一个
t = t->next;//t变成它的下一个
}
return head;
}
//插入,遍历链表,如果当前节点是最后一个节点或者下一个节点大于待插入的数时插入
int insertData(link* &head, int x)
{
link *p, *t;
t = head->next;
//如果是在第一位
if(t->data >x )
{
p = (link*) malloc (sizeof(link));
p->data = x;
p->next = head->next;
head->next = p;
return 1;//返回1表示插入成功
}
//不是在第一个位置
while(t != NULL)
{
if(t->next == NULL || t->next->data >x)
{
p = (link*) malloc (sizeof(link));
p->data = x;
p->next = t->next;
t->next = p;
break;
}
t = t->next;
}
return 1;//返回1表示插入成功
}
//删除
int deleteData(link* &head, int y)
{
link *t2, *freeData;
t2 = head;
while(t2->next != NULL)
{
if(t2->next->data == y )
{
freeData = t2->next;// freeData指向被删除的节点,防止丢失
t2->next = t2->next->next;
free(freeData);//手动申请地址后,要记得释放,不过,当关闭进程是也会自动释放
return 1;
}
t2=t2->next;
}
return -1;//删除失败
}
//查询一个数
int selectData(link* &head, int m)
{
link *t = head;
while(t->next != NULL)
{
if(t->next->data == m)
{
return 1;//返回1,代表找到了
}
t=t->next;
}
return -1;//返回1,代表查找失败
}
//修改一个数
int updata(link* &head, int a, int b)
{
link *t = head;
while(t->next != NULL)
{
if(t->next->data == a)
{
t->next->data = b;
return 1;//返回1,代表修改成功
}
t=t->next;
}
return -1; //返回1,代表修改成失败
}
//打印链表
void display(link* &head)
{
link* t = head->next;
while(t!=NULL)
{
printf("%d ",t->data);
t = t->next;
}
}
int main(void)
{
int i, n;
int result1, result2, result3, result4, resultCheck;
printf("请输入个数n: \n");
scanf("%d",&n);
//调用创建链表函数,返回生成链表的头指针
link *head = initLink(n);
printf("此时的链表是: \n");
display(head);
printf("\n");
//插入一个数
int x;
printf("请输入要插入的数:\n");
scanf("%d",&x);
result1 = insertData(head,x);
if(result1 == 1)
printf("插入成功\n");
else
printf("插入失败\n");
printf("插入后的链表: \n");
display(head);
printf("\n");
//删除一个数
int y;
printf("请输入要删除的数:\n");
scanf("%d",&y);
result2 = deleteData(head,y);
if(result2 == 1)
printf("删除成功\n");
else
printf("删除失败,链表中不存在该数\n");
printf("删除后的链表:\n");
display(head);
printf("\n");
//查询一个数
int m;
printf("请输入要查询的数:\n");
scanf("%d",&m);
result3 = selectData(head,m);
if(result3 == 1)
printf("链表中存在该数\n");
else
printf("链表中不存在该数\n");
//修改一个数
int a, b;
printf("请输入原始数和修改后的:\n");
scanf("%d %d",&a,&b);
resultCheck = selectData(head,a);
if(resultCheck == 1)
{
result4 = updata(head,a,b);
}
else
printf("链表中不存在该数,请输入正确的数\n");
printf("修改后的链表:\n");
display(head);
printf("\n");
return 0;
}