-
链表是由许多相同数据类型的数据按照特定顺序排列而成的线性表。其优点是数据的插入和删除都相当方便,缺点是设计数据结构十分麻烦。
一、单链表
(一)动态分配内存
c语言中可使用头文件下stdlib.h中的malloc()和free()函数在程序执行期间动态分配与释放内存。
- 动态分配内存方式如下:
数据类型*指针名称 = (数据类型*)malloc (sizeof(数据类型)*n);
n=1即表示一个变量
变量或对象在使用动态内存分配后,必须进行内存的释放。否则会造成“内存漏失”现象。
- 释放动态分配变量方式如下:
free(指针名称)
- 例如:
#include<stdio.h>
#include<stdlib.h>
int main(void){
float *piVal=(float*)malloc(sizeof(float));//动态分配一块浮点数内存空间
*piVal=3.14;
int *piCal=(int*)malloc(sizeof(int));//动态分配一块整数内存空间
*piCal=1000;
printf("piVal所指向的地址内容为%f\n\n",*piVal);
printf("piCal所指向的地址内容为%d\n\n",*piCal);
free(piVal);//释放piVal所指向的内存空间
free(piCal);//释放piCal所指向的内存空间
}
/*piVal所指向的地址内容为3.140000
piCal所指向的地址内容为1000*/
- 实例一
#include<stdio.h>
#include<stdlib.h>
#include<cstring>
int main(void) {
char* s1 = (char*)malloc(sizeof(char) * 10);
strcpy(s1, "Hello,C!");
printf("%s ", s1);
printf("s1=%d", s1);
printf("\n");
char* s2 = (char*)malloc(sizeof(char) * 10);
strcpy(s2, "Hello,C!");
printf("%s ", s2);
printf("s2=%d", s2);
free(s1);
free(s2);
}
Hello,C! s1=2036736
Hello,C! s2=2059152
- 实例二
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void){
struct student{
char name[20];
int score;
};
student* s=(student*)malloc(sizeof(student));
strcpy(s->name,"zhangsan");
printf(s->name);
s->score=85;
printf("\nscore=%d",s->score);
free(s);
}
zhangsan
score=85
(二)单向链表
- 一个单向链表节点基本上由两个元素构成,即数据字段和指针组成,而指针将会指向下一个元素在内存中的位置。
- 在单向链表中第一个节点是“链表头指针”,指向最后一个节点的指针设为NULL,表示它是“链表尾”,不知向任何地方。
- 注意,除非必要否则不可移动链表头指针。
1、建立单向链表
- 在C语言中,若以动态分配产生链表节点的方式,可以先定义一个结构数据类型,接着在结构中定义一个指针变量,其数据类型与结构相同,作用是在指向下一个链表节点,另外结构中至少要有一个数据字段。
struct student
{
char name[20];
int score;
struct student *next;
}s1,s2;
- 完成结构数据类型定义之后,就可以动态建立链表中每个节点。假设现在要新增一个节点至链表的末尾,且ptr指向链表的第一个节点,则程序上必须设计以下4步
- 动态分配内存空间给新节点使用
- 将原链表尾部的指针(next)指向新元素所在的内存地址
- 将ptr指针指向新节点的内存位置,表示这是新的链表尾部
- 由于新结点当前为链表的最后一个元素,所以将它的指针(next)指向NULL
- 例如将s1的next变量指向s2的内存地址,而且s2的next变量指向NULL
s1.next = &s2;
s2.next = NULL;
- 建立学生节点的单向链表算法
#include<stdio.h>
#include<stdlib.h>
int main(void){
struct student{
char name[20];
char no[20];
int Math;
int Eng;
struct student*next;
};
typedef struct student s_data;
s_data*ptr;//存取指针
s_data*head;//链表头指针
s_data*new_data;//新增元素所在位置的指针
head=(s_data*)malloc(sizeof(s_data));//新增链表头的元素
ptr=head;//设置存取指针的位置
ptr->next=NULL;//当前无下一元素
int select;
do
{
printf("(1)新增 (2)离开 =>");
scanf("%d",&select);
if(select!=2){
printf("姓名 学号 数学成绩 英语成绩:");
scanf("%s %s %d %d",ptr->name,ptr->no,&ptr->Math,&ptr->Eng);
new_data=(s_data*)malloc(sizeof(s_data));//新增下一个元素
ptr->next=new_data;//存取指针设置为新元素所在的位置
new_data->next=NULL;//下一个元素的next先设置为null
ptr=ptr->next;
}
}while(select!=2);
}
2、遍历单向链表
- 遍历单向链表过程中,就是要使用指针运算访问链表中的每一个节点。例如以上例子中,使用结构指针ptr作为链表的读取游标,一开始指向链表的头。每次读完链表的一个节点就将ptr往下一个节点移动,直到ptr指向NULL为止。
ptr=head;//设置存取指针从头开始
while(ptr->next!=NULL){
{
printf("姓名:%s\t 学号:%s\t 数学成绩:%d\t 英语成绩:%d\n",
ptr->name,ptr->no,ptr->Math,ptr->Eng);
ptr=ptr->next;//将ptr移往下一个元素
}
- 实例一
#include<stdio.h>
#include<stdlib.h>
int main(void){
struct student{
char name[20];
char no[10];
int math;
int Eng;
struct student*next;
};
typedef struct student data;
data*ptr;
data*head;
data*new_data;
head=(data*)malloc(sizeof(data));
ptr=head;
ptr->next=NULL;
int select;
float math_sum,math_ave=0;
float Eng_sum,Eng_ave=0;
int n=0;
do{
printf("(1) 新增 (2) 离开=>");
scanf("%d",&select);
if(select!=2){
printf("姓名 学号 数学成绩 英语成绩:");
scanf("%s %s %d %d",ptr->name,ptr->no,&ptr->math,&ptr->Eng);
new_data=(data*)malloc(sizeof(data));
ptr->next=new_data;
new_data->next=NULL;
ptr=ptr->next;
n++;
}
}while(select!=2);
ptr=head;
while(ptr->next!=NULL){
printf("姓名:%s\t 学号:%s\t 数学成绩:%d\t 英语成绩:%d\n",
ptr->name,ptr->no,ptr->math,ptr->Eng);
math_sum+=ptr->math;
Eng_sum+=ptr->Eng;
ptr=ptr->next;
}
math_ave=math_sum/n;
Eng_ave=Eng_sum/n;
printf("======================================\n");
printf("数学成绩平均值为:%.1f 英语成绩平均值为%.1f",math_ave,Eng_ave);
}
(1) 新增 (2) 离开=>1
姓名 学号 数学成绩 英语成绩:张三 01 65 89
(1) 新增 (2) 离开=>1
姓名 学号 数学成绩 英语成绩:李四 02 98 68
(1) 新增 (2) 离开=>1
姓名 学号 数学成绩 英语成绩:王麻子 03 68 96
(1) 新增 (2) 离开=>2
姓名:张三 学号:01 数学成绩:65 英语成绩:89
姓名:李四 学号:02 数学成绩:98 英语成绩:68
姓名:王麻子 学号:03 数学成绩:68 英语成绩:96
======================================
数学成绩平均值为:77.0 英语成绩平均值为84.3
3、释放单向链表节点的空间
- 在使用动态方式分配链表的内存后,事后必须进行释放内存的操作。除了利用free()函数或者delete运算符来释放单个节点的内存外,还可以利用单向链表的遍历技巧来收回整个链表的内存空间。算法如下:
while(first!=NULL){
ptr=first;//first为头指针
first=first->next;//逐一遍历
free(ptr);
}
- 实例
#include<stdio.h>
#include<stdlib.h>
int main(void){
struct student {
char name[20];
int age;
struct student*next;
};
typedef struct student data;
data*ptr;
data*head;
data*new_data;
head=(data*)malloc(sizeof(data));
ptr=head;
ptr->next=NULL;
int select;
do{
printf("(1)新增 (2)离开 =>");
scanf("%d",&select);
if(select!=2){
printf("姓名 年龄:");
scanf("%s %d",ptr->name,&ptr->age);
new_data=(data*)malloc(sizeof(data));
ptr->next=new_data;
new_data->next=NULL;
ptr=ptr->next;
}
}while(select!=2);
ptr=head;
while(ptr->next!=NULL){
printf("姓名:%s\t 年龄:%d\n",ptr->name,ptr->age);
ptr=ptr->next;
ptr=head;
}
printf("========================================\n");
while(head!=NULL){
ptr=head;
head=head->next;
free(ptr);
}
printf("所有节点释放完毕!");
}
(1)新增 (2)离开 =>1
姓名 年龄:张三 20
(1)新增 (2)离开 =>1
姓名 年龄:李四 18
(1)新增 (2)离开 =>2
姓名:张三 年龄:20
姓名:李四 年龄:18
========================================
所有节点释放完毕!
3、单项链表插入新的节点
单向链表插入新节点有三种方式。加到第一个节点之前,加到最后一个节点之后以及加到此链表中间任意位置。
- 新节点插入第一个节点之前,即成为此链表的首节点:只需要把新节点的指针指向链表原来的第一个节点,再把链表头指针指向新节点即可。
newnode->next=first;
first=newnode;
- 新节点插入最后一个节点之后:只需要把链表的最后一个节点的指针指向新节点,新节点的指针再指向NULL即可。
ptr->next=newnode;
newnode->next=NULL;
- 将新节点插入链表中间的位置:例如插入的节点是在X与Y之间,只要将X节点的指针指向新节点,新节点的指针指向Y节点即可。
newnode->next=x->next;
x->next=newnode;
- 实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void){
struct employee{
int num,score;
char name[10];
struct employee *next;
};
typedef struct employee data;
data*ptr;
data*head;
data*new_data;
head=(data*)malloc(sizeof(data));
ptr=head;
ptr->next=NULL;
int select;
do{
printf("(1) 新增 (2) 离开 =>");
scanf("%d",&select);
if(select!=2){
new_data=(data*)malloc(sizeof(data));
new_data->next=NULL;
printf("姓名 序号 薪资:");
char name1[10];
int num1;
int score1;
scanf("%s %d %d",name1,&num1,&score1);
strcpy(ptr->name,name1);
ptr->num=num1;
ptr->score=score1;
ptr->next=new_data;
ptr=ptr->next;
}
}while(select!=2);
for(int i=0;i<3;i++){
printf("员工编号 薪水\t");
}
printf("\n--------------------------------------------\n");
ptr=head;
int cnt=0;
ptr=head;
while(ptr->next!=NULL){
printf("[%d]\t ¥%d\t",ptr->num,ptr->score);
cnt++;
if(cnt%3==0){
printf("\n");
}
ptr=ptr->next;
}
printf("\n--------------------------------------------\n");
int se;
data*new_e;
do{
printf("\n请输入要插入其后的员工编号,如输入的编号不在此链表中,\n");
printf("\n新输入的员工节点将视为此链表的链表头部,要结束输入过程请输入-1:\n");
scanf("%d",&se);
if(se!=-1){
new_e=(data*)malloc(sizeof(data));
new_e->next=NULL;
printf("请输入新插入的员工编号:");
scanf("%d",&new_e->num);
printf("请输入新插入的员工薪水:");
scanf("%d",&new_e->score);
printf("请输入新插入的员工姓名:");
scanf("%s",new_e->name);
new_e->next=head;
head=new_e;
}
}while(se!=-1);
printf("员工编号 姓名 薪水\t");
printf("\n================================\n");
ptr=head;
while(ptr->next!=NULL){
printf("[%d] [%s] [%d]\t\n",ptr->num,ptr->name,ptr->score);
head=head->next;
ptr=head;
}
}
(1) 新增 (2) 离开 =>1
姓名 序号 薪资:张三 01 5000
(1) 新增 (2) 离开 =>1
姓名 序号 薪资:李四 02 6500
(1) 新增 (2) 离开 =>1
姓名 序号 薪资:赵四 04 5800
(1) 新增 (2) 离开 =>2
员工编号 薪水 员工编号 薪水 员工编号 薪水
--------------------------------------------
[1] ¥5000 [2] ¥6500 [4] ¥5800
--------------------------------------------
请输入要插入其后的员工编号,如输入的编号不在此链表中,
新输入的员工节点将视为此链表的链表头部,要结束输入过程请输入-1:
01
请输入新插入的员工编号:03
请输入新插入的员工薪水:3500
请输入新插入的员工姓名:王麻子
请输入要插入其后的员工编号,如输入的编号不在此链表中,
新输入的员工节点将视为此链表的链表头部,要结束输入过程请输入-1:
-1
员工编号 姓名 薪水
================================
[3] [王麻子] [3500]
[1] [张三] [5000]
[2] [李四] [6500]
[4] [赵四] [5800]
--------------------------------
4、单向链表删除节点
若要在链表中删除节点,根据所删除的节点位置会有三种情况
- 删除链表的第一个节点将链表的头指针指向第二个节点即可。
ptr=head;
head=head->next;
free(ptr);
- 删除链表的最后一个节点,只要指向最后一个节点的指针指向NULL即可
ptr->next = tail;
ptr->next = NULL;
free(tail);
- 删除链表内的中间节点,只要将删除节点的前一个结点的指针,指向将要被删除的下一个节点即可。
Y = ptr->next;
ptr->next = Y->next;
free(Y);
- 实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void){
struct employee{
int num,score;
char name[10];
struct employee *next;
};
typedef struct employee data;
data*ptr;
data*head;
data*new_data;
head=(data*)malloc(sizeof(data));
ptr=head;
ptr->next=NULL;
int select;
do{
printf("(1) 新增 (2) 离开 =>");
scanf("%d",&select);
if(select!=2){
new_data=(data*)malloc(sizeof(data));
new_data->next=NULL;
printf("姓名 序号 薪资:");
char name1[10];
int num1;
int score1;
scanf("%s %d %d",name1,&num1,&score1);
strcpy(ptr->name,name1);
ptr->num=num1;
ptr->score=score1;
ptr->next=new_data;
ptr=ptr->next;
}
}while(select!=2);
for(int i=0;i<3;i++){
printf("员工编号 薪水\t");
}
printf("\n--------------------------------------------\n");
ptr=head;
int cnt=0;
ptr=head;
while(ptr->next!=NULL){
printf("[%d]\t ¥%d\t",ptr->num,ptr->score);
cnt++;
if(cnt%3==0){
printf("\n");
}
ptr=ptr->next;
}
printf("\n--------------------------------------------\n");
int se;
do{
printf("\n请输入要删除的员工编号,要结束删除过程请输入-1。(默认删除第一个)\n");
scanf("%d",&se);
if(se!=-1){
if(se==1){
ptr=head;
head=head->next;
free(ptr);
}
}
}while(se!=-1);
printf("员工编号 姓名 薪水\t");
printf("\n================================\n");
ptr=head;
while(ptr->next!=NULL){
printf("[%d] [%s] [%d]\t\n",ptr->num,ptr->name,ptr->score);
head=head->next;
ptr=head;
}
}
(1) 新增 (2) 离开 =>1
姓名 序号 薪资:1 1 1
(1) 新增 (2) 离开 =>1
姓名 序号 薪资:2 2 2
(1) 新增 (2) 离开 =>2
员工编号 薪水 员工编号 薪水 员工编号 薪水
--------------------------------------------
[1] ¥1 [2] ¥2
--------------------------------------------
请输入要删除的员工编号,要结束删除过程请输入-1。(默认删除第一个)
1
请输入要删除的员工编号,要结束删除过程请输入-1。(默认删除第一个)
-1
员工编号 姓名 薪水
================================
[2] [2] [2]