C语言链表
概述
当每一个数据元素都和它下一个数据元素用指针链接在一起时,就形成了一个链,这个链子的头就位于第一个数据元素,这样的存储方式就是链式存储。它是动态的进行存储分配的一种结构。
数组和链表
数组的优势,在于可以方便的遍历查找需要的数据。但是,这种时间上的便利性,是因为数组在内存中占用了连续的空间,在进行类似的查找或者遍历时,本质是指针在内存中的定向偏移。然而,当需要对数组成员进行添加和删除的操作时,数组内完成这类操作的时间复杂度则变成了O(n)。
链表的特性,使其在某些操作上比数组更加高效。例如当进行插入和删除操作时,链表操作的时间复杂度仅为O(1)。另外,因为链表在内存中不是连续存储的,所以可以充分利用内存中的碎片空间。除此之外,链表还是很多算法的基础,最常见的哈希表就是基于链表来实现的。
链表节点元素的构成
在链表中有一个头指针变量,图中head表示的就是头指针,这个指针变量保存一个地址。从图中的箭头可以看到该地址为一个变量的地址,也就是说头指针指向一个变量。这个变量称为元素,在链表中每一个元素包括两个部分:数据部分和指针部分。数据部分用来存放元素所包含的数据,而指针部分用来指向下一个元素。最后一个元素指针指向NULL,表示指向的地址为空
每个元素由两部分组成:数据域(本身的信息)和指针域(指向后继的指针)。
typedef struct student
{
char name[10]; //数据元素
float score; //数据元素
struct student *next; //指向下一个结点的位置
} pstudent;
通过一个简单的程序来熟悉对链表的基本操作
void main()
{
int a = 0;
pstudent* head;
head = creat(3);
change(head,2);
head = Insert(head, 4);
head = Delete(head,4);
Traversal(head);//遍历的是执行Delete()函数后的链表
a = Length(head);
printf("%d\n",a);
}
- 调用函数时,当head(头指针)的指向发生改变时,我们应该返回一个新的指针(head),避免别的函数传参时发生错误。
静态链表(简单链表)
静态链表有助于很好的理解动态链表:
struct student
{
long num;
float score;
struct student* next;
};
void main()
{
struct student a,b,c,*head,*p;
a.num = 10101; a.score = 89;
b.num = 10101; b.score = 90;
c.num = 10101; c.score = 99;
head = &a;
a.next = &b;
b.next = &c;
c.next = NULL;
p = head;
do
{
printf("%ld %5.1f\n",p->num,p->score);
p = p->next;
}while(p != NULL);
}
动态链表的创建
头指针:只有指针的元素,并没有数据元素。
头节点:头节点的数据类型与首节点的数据类型相同,并且头节点是首节点前面的那个节点,并不存放有效数据;头节点的存在只是为了方便链表的操作。
创建链表的方法有好多种,可以参考下文:创建链表的四种方法
以下代码是自己对链表的理解所写,理解起来更容易
pstudent* creat(int n)
{
pstudent* head;//头指针
pstudent* node;//头节点(数据域和指针域)
pstudent* next;//下一节点(数据域和指针域)
node = (pstudent*)malloc(sizeof(pstudent));
head = NULL;//n=0时为空链表
if(n != 0)
{
head = node;//头指针指向头节点
for (int i = 0; i < n; i++)
{
next = (pstudent*)malloc(sizeof(pstudent));
printf("please input %d node value:\n",i+1);
printf("please input name:\n");
fflush(stdin);//情除缓存区
gets(node->name);
printf("please input score:\n");
fflush(stdin);//(注1)
scanf("%f", &node->score);
system("cls");//清屏
node->next = next;//头节点指向下一个节点
node = next;//指向下一个元素(注2)
}
node->next = NULL;//结束创建(注3)
}
return head;
}
- 第一次输入的回车可能会被第二次输入操作所捕捉,这个的作用就是清空缓冲,这样就不会出现这种情况了。
- node = next,头节点输入完成,该next节点输入,由于使用for循环,第二次循环仍为node节点(node->name),而我们需要的是第二个节点,故在第一次循环结束时,将node指向了第二个节点next,即node = next,此时的node不再是头节点。注意不能写成next = node,node节点是我们每次录入的节点,该地址应该在本次录入完成后赋予下一个节点的地址,故node为左值。
- node->next = NULL,循环结束后,最后一个节点之后不再有节点,故下一个节点为空。
链表之遍历输出
void Traversal(pstudent* head)
{
printf("Output Traversal Message:\n");
while (head->next != NULL)
{
printf("%s\n", head->name);
printf("%f\n\n", head->score);
head = head->next;
}
}
链表之长度
int Length(pstudent* head)
{
int length = 0;
while(head->next != NULL)
{
++length;
head = head->next;
}
return length;
}
链表之修改节点
void change(pstudent* head,int n)
{
int i = 1;
pstudent* node = head;
if (n == 1)
{
printf("please input change name:\n");
fflush(stdin);
scanf("%s", &node->name);
printf("please input change score:\n");
fflush(stdin);
scanf("%f", &node->score);
}
else
{
while (i < n && node != NULL)
{
node = node->next;
i++;
}
if (node != NULL)
{
printf("please input change name:\n");
fflush(stdin);
scanf("%s", &node->name);
printf("please input change score:\n");
fflush(stdin);
scanf("%f", &node->score);
}
else
{
printf("not exit node");
}
}
}
链表之插入节点
pstudent* Insert(pstudent* head, int position)
{
int i = 2;
pstudent* node = head;//定义一个指针node,用来移动指向链表节点的位置
pstudent* insert = (pstudent*)malloc(sizeof(pstudent));
printf("please input insert name:\n");
fflush(stdin);
gets(insert->name);
printf("please input insert score:\n");
fflush(stdin);
scanf("%f", &insert->score);
if(position == 1)
{
insert->next = head;
head = insert;
}
else if(position == 2)
{
insert->next = node->next;
node->next = insert;
}
else
{
while (i < position && node != NULL)
{
node = node->next;
i++;
}
if (node != NULL)
{
insert->next = node->next;
node->next = insert;
}
else if(node == NULL)
{
node->next = insert;
insert->next = NULL;
}
else
{
printf("not exit node");
}
}
return head;
}
链表之删除节点
pstudent* Delete(pstudent* head, int n)
{
int i = 1;
pstudent* node = head;
pstudent* front;
if(n == 1)
{
head = node->next;
free(node);
}
else
{
while (i < n && node != NULL)
{
front = node;
node = node->next;
i++;
}
if (node != NULL)
{
front->next = node->next;
free(node);
}
else
{
puts("not exit node");
}
}
return head;
}