学习周报-4

任务:学习链表

  1. 看视频、看书
  2. 敲例题
  3. 做练习

1. 存储空间的分配和释放

C语言标准库函数中提供了4个函数malloc()、calloc()、realloc()、free(),用来实现内存的动态分配与释放。

  1. malloc()函数:动态分配一段内存空间

malloc()函数的原型为:void *malloc(unsigned int size);
这个函数的功能是在内存的动态存储区申请一个长度为 size 字节连续存储空间。malloc()函数会返回一个指针,并指向所分配空间的起始地址。如果没有足够的内存空间可分配,则函数的返回值为空指针 NULL。

#include<stdio.h>
#include<malloc.h>	//也可以用<stdlib.h>

int main() {
	int *p = (int *)malloc(sizeof(int));	//分配空间
	*p = 10;								//使用该空间保存数据
	printf("%d",*p);						//输出数据,输出结果为:10
	return 0;
}
  1. calloc()函数:动态分配连续内存空间
    calloc() 函数的原型为:void *calloc(unsigned int n,unsigned int size);
    这个函数的功能是在内存申请 n 个长度为 size 字节的存储空间,并返回该存储空间的起始地址。如果没有足够的内存空间可分配,则函数的返回值为空指针NULL。
    例:int *p = (int *)calloc(3,sizeof(int));
    表示申请3个 int 类型长度的存储空间,并将分配到的存储空间地址转换为 int 类型地址,将其首地址赋给所定义的指针变量 p。然后 p 就可以作为3个元素的整型数组来使用。
    这个语句的功能也可以用malloc() 函数来实现:int *p = (int *)malloc(3*sizeof(int));
    例:
#include<stdio.h>
#include<stdlib.h>		//<stdlib.h>中包含malloc()和calloc()函数.

int main() {
	int i;
	char *ch1 = (char *)calloc(26,sizeof(char));	//使用malloc动态分配一个长度为26字符的字符数组
	char *ch2 = (char *)malloc(26*sizeof(char));	//使用calloc动态分配一个长度为26字符的字符数组
	for(i = 0;i < 26;i++) {							//为两个字符数组赋值
		ch1[i] = 65 + i;							//ch1是大写字符数组
		ch2[i] = 97 + i;							//ch2是小写字符数组
	}
	printf("26个大写字母:\n");
	for(i = 0;i < 26;i++) {
		printf("%c ",ch1[i]);
	}
	printf("\n26个小写字母:\n");
	for(i = 0;i < 26;i++) {
		printf("%c ",ch2[i]);
	}
	return 0;
}
  1. realloc()函数:改变指针指向空间的大小
    realloc() 函数的原型为:void *realloc(void *ptr,size _t size);
    它的功能是改变ptr 指针指向大小为 size 的空间。设定 size 的大小可以是任意的,可以比原来的数值大或者小。
    函数返回值是一个指向新地址的指针,如果出现错误,则返回NUL。

  2. free()函数:释放存储空间
    free() 函数原型为void free(void *p);
    它的功能是将指针变量 p 指向的存储空间释放,交还给系统。free 函数无返回值。
    注意:p 只能是程序中此前最后一次调用 malloc 或 calloc 函数所返回的地址。

2. 概述

定义:链表是物理存储单元上非连续、非顺序的存储结构。

作用:可以动态的进行存储分配。

特征:可以在节点中定义多种数据类型,还可以根据需要添加、删除、插入节点。

构成

  1. 链表都有一个头指针,一般用head表示,存放的是一个地址。
  2. 链表中每个结点都分为两部分:指针域数据域
  3. 节点分为两类:头结点和一般结点。头结点是没有数据的
  4. 在链表中,第一个结点前虚加一个头结点,头指针指向头结点,头结点的指针域指向第一个实际有效结点(也称为首元结点)。头结点的数据域可以不使用。
  5. 对于带头结点的链表,空表也会保留头结点。带头结点的链表比不带头结点的链表在创建、插入和删除等操作时代码更简洁。

分类

  • 从内存角度出发: 链表可分为 静态链表动态链表
  • 从链表存储方式的角度出发:链表可分为 单链表双链表、以及循环链表

3. 静态链表和动态链表

静态链表是把线性表的元素存放在数组中,且每个元素除了存放数据以外,还要存放指向下一个元素的位置,即下一个元素所在数组单元的下标。
静态链表大小固定,插入元素是固定的。一般会设置一个元素为用户自定义类型的数组,大小是固定的。如:

#define MAX 1000
typedef struct StaticList
{
 	int data;			//存放数据
    int cur;			//用下标来代替指针
}SL;
SL Test[MAX];

动态链表是用申请内存函数(C是malloc,C++是new)动态申请内存的,所以在链表的长度上没有限制。动态链表因为是动态申请内存的,所以每个结点的物理地址不连续,要通过指针来顺序访问。

4. 单链表

若在链表中,每个结点只有一个指针,所有结点都是单线联系,除了末尾结点指针以空外,每个结点的指针都指向下一个结点,一环扣一环形成一条线性链,则称此链表为单向线性链表,简称单链表。

特点

  • 有一个 head 指针变量,它存放在头结点的地址,称之为头指针。
  • 头结点的指针域head->next存放首元结点的地址。
  • 从头指针 head 开始,head 指向头结点,头节点指向首元结点,首元结点指向第二个结点,直到最后一个结点。所有结点都是单线联系。
  • 最后一个结点不再指向其他结点,称为“表尾节点”,它的指针域为空指针NULL,表示链表到此结束。指向表尾结点的指针称为尾指针
  • 链表各结点之间的顺序关系由指针域 next 来确定,并不要求逻辑上相邻的结点物理位置上也相邻。即链表依靠指针相连不需要占用一片连续的内存空间
4.1 单链表的初始化

由于链表的每个结点都包含数据域和指针域,即每个节点都要包含不同类型的数据,所以结点的数据类型必须选用结构体类型。且结构体中必须有一个成员的类型是指向本结构体类型的指针类型。
单链表的初始化就是创建一个头结点,头结点的数据域可以不使用,头结点的指针域为空,表示空单链表

  1. 先定义一个需要用的结构体类型:
typedef struct node {
	int number;				//数据域
	char name[20];			//数据域
	struct node *next;		//递归定义指向struct node类型结构体的指针变量next。
}NODE,*LinkList;
  1. 然后才开始链表的初始化。单链表的初始化就是创建一个头结点,头结点的数据域可以不使用,头结点指针域为空,表示空单链表。
LinkList List() {
	LinkList head;							//定义头指针变量
	head = (NODE*)malloc(sizeof(NODE));		//头指针指向分配的头结点内存空间
	head->next = NULL;						//头结点的指针域为空
	return head;							//返回头结点的地址,即头指针
}

如下图:
初始化单链表

4.2 单链表的建立

单链表的建立就是在程序的运行过程中,从无到有的建立一个链表,即一个一个的分配结点的内存空间,然后输入结点中的数据,并建立结点间的相连关系。
单链表的建立可以分为两种方法:尾插法头插法

  1. 尾插法:在单链表的尾部插入新结点。
           从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表尾上,直至读入结束标志为止。
void(CreatByRear(LinkList)) {
	NODE *r,*s;			//s用于创建新结点
	int number;
	char name[20];
	r = head;			//head指向头结点,故r指向头结点
	printf("请输入学生的学号和姓名:\n");
	while(1) {
		scanf("%d",&number);
		scanf("%s",name);
		if(number == 0) break;
		s = (node *)malloc(sizeof(NODE));		//分配结点的内存空间
		s->number = number;
		strcpy(s->name,name);
		r->next = s;							//原来的结点指向新结点
		r = s;									//r指向新结点
	}
	r->next = NULL;								//链表的尾结点指针为空
}

如下图:
尾插法

  1. 头插法:在单链表的头部插入新结点
           从一个空表开始,重复读入数据,生成新结点,将读入的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头节点之后,直至读入结束标志为止。
void CreatByHead(LinkList head) {
	NODE *S;
	int number;
	char name[20];
	printf("请输入学生学号和姓名;\n");
	while(1) {
		scanf("%d",&number);
		scanf("%s",name);
		if(number == 0) break;
		s = (NODE *)malloc(sizeof(NODE));		//分配节点的内存空间
		s->number = number;
		strcpy(s->name,name);
		s->next = head->next;					//让新结点指向首元结点
		head->next = s;							//让头结点指向新结点
	}

}

如下图:
在这里插入图片描述

4.3 单链表的遍历

直接上代码:

void OutPut(LinkList head) {
	NODE *p;				//循环所用的临时指针
	p = head->next;			//p指向链表的首元结点
	while(p) {
		printf("学号:%d\n",p->number);
		printf("姓名:%s\n",p->name);
		p = p->next;		//移动临时指针到下一个结点
	}
}
  • 函数定义了一个临时指针p用来进行循环操作,使其指向要输出链表的首元结点。
  • 在while循环中,每输出一个结点的内容后,就移动临时指针p到下一个节点的位置。如果是最后一个结点,指针指向NULL,表示链表中的节点都已经输出,循环结束。
4.4 单链表的插入

链表的插入操作可以在链表的头指针位置进行插入,也可以在链表中某个结点的位置进行插入,或者在链表的最后面添加结点。在头指针位置和在最后面插入的思路与头插法和尾插法的思路相同。
以下写一段在链表中某个结点的位置插入新结点的代码示例:

void Insert(LinkList head,int i) {
	NODE *P = head,*s;
	int j = 0;
	while(j < i-1 && p) {			//从头结点开始,故开始j=0,头结点是第0个结点,遍历找到第i-1个结点的地址
		p = p->next;
		j++;
	}
	if(p) {
		printf("请输入待添加学生的学号和姓名:\n");	
		s = (NODE *)malloc(sizeof(NODE));	//定义s指向新分配的空间
		scanf("%d",&s->number);
		scanf("%d",s->name);
		s->next = p->next;					//新结点指向原来的第i个结点
		p->next = s;						//新结点成为原来的第i个结点
	}
}
4.5 单链表的删除

在创建单链表删除某个结点的函数时需要两个参数,一个表示链表的头指针head,另一个表示要删除的节点在链表中的位置。
代码示例:

void Delete(LinkList head,int pos) {
	NODE *p = head,*q;
	int j = 0;
	printf("删除第%d个学生的信息",pos);
	while(j < pos-1 && p) {			//通过循环,找到第pos-1个结点的地址
		p = p->next;
		j++;
	}
	if(p == NULL || p->next == NULL) printf("the pos is error");	//第pos个结点不存在
	else {
		q = p->next;				//q 指向第pos个结点
		p->next = q->next;			//连接所要删除结点两边的结点
		free(q);					//释放所要删除结点的内存空间
	}
}
4.6 单链表的查询

在创建单链表查询某个结点的函数时需要两个参数,一个表示链表的头指针head,另一个表示要查找的值。
代码示例:

NODE *Search(LinkList head,char name[]) {		//在单链表head中找到值为name的结点
	NODE *p = head->next;
	while(p) {
		if(strcmp(s->name,name)!=0) p = p->next;	//判断结点的值是不是name的值,若不是则移动p指向下一个结点
		else break;			//查找成功!
	}
	if(p == NULL) 
		printf("未找到值为%d的结点",name)return p;
}
4.7 单链表的长度

单链表的长度是隐形表示的,当从首元结点开始,依次遍历链表的所有结点,并同时统计结点个数,最后返回结点个数值。
代码示例:

int Length(LinkList head) {		
	int count;
	NODE *p;
	p = head->next;			//指针变量p指向链表的首元结点
	while(p) {				//结点存在,表示链表没有遍历结束
		count++;			//结点个数累加器加1
		p = p->next;		//指向当前结点的下一结点
	}
	return count;			//返回链表结点的个数
}
4.8 不带头结点的单链表

不带头结点的单链表,在操作过程中必须针对第一个结点和其余结点分别进行操作。

  1. 插入
    分为在链表的首位置插入不在链表的首位置插入两种情况。

在链表首位置:
插入时,首先为插入的新结点分配内存,然后将新结点的指针指向原来的首结点,最后将头指针指向新结点。需要注意的是,在这种情况下,头指针发生了改变,所以需要返回新的头指针。

不在链表首位置: 例如要在第 i 个结点插入新结点
需要先通过循环找到链表的第 i-1个结点的地址p。如果该结点存在,则可以在第 i-1 个结点后面插入第i个结点。为插入的新结点分配内存,然后向新结点输入数据。插入时,首先将新结点的指针指向原来第 i 个结点,然后将第 i-1 个结点指向新结点。完成。

  1. 删除
    分为删除首结点删除其他结点两种情况。

删除首结点:
定义指针变量去指向待删除的结点,再让头指针指向原来的第二个结点,成为新的首结点。最后释放原来的首结点的内存空间。在这种情况下,头指针也发生了改变,所以需要返回新的头指针。

删除不是头结点的结点:
定义整型变量 j 来控制循环次数,然后定义指针变量p表示该结点之前的结点。接着利用循环找到要删除的结点之前的结点p;如果该结点存在并且待删除结点存在,则将指针变量q指向待删除的结点,再连接要删除结点两边的结点。,并使用free函数将q指向的内存空间进行释放。

5. 循环链表

基于单链表的学习后,很容易知道,循环链表,就是在链表最后一个结点的指针域处存放头结点的地址,头尾相连,形成一个环形的数据链。故对于循环链表表尾的判断,只需判断该节点的指针域是否指向链表头结点。

typedef struct node {							//定义循环链表的结点类型
	int age;
	char name[20];
	struct node *next;
}NODE,*PNODE;

PNODE List() {									//初始化 
	PNODE head;
	head = (NODE *)malloc(sizeof(NODE));
	head->next = NULL;
	return head;
} 

void Creat(PNODE head) {
	NODE *r,*s;
	int age;
	char name[20];
	r = head;
	while(1) {
		scanf("%d",&age);
		scanf("%s",name);
		if(age == 0) break;
		s = (NODE *)malloc(sizeof(NODE));
		s->age = age;
		strcpy(s->name,name);
		r->next = s;
		r = s;
	}
	r->next = head;								//区别于单链表(r->next = NULL;,此处尾结点的指针域存放头指针的地址。 
}

6. 双向链表

单链表每个结点有一个指针域和一个数据域,要访问任何结点都需知道头结点,不能逆着进行。双向链表则添加了一个指针域,通过两个指针域分别指向结点的前一个结点和后一个结点。这样的话,可以通过双链表的任何结点访问到它的前一个结点和后一个结点。
两个指针域一个存储直接后继结点的地址,一般称为右链域,另一个存储直接前驱结点,一般称为左链域

typedef struct node {							//定义双向链表的结点类型				
	int age;
	char name[20];
	struct node *prior;
	struct node *next;
}NODE,*PNODE;

PNODE List() {									//初始化
	PNODE head;
	head = (NODE *)malloc(sizeof(NODE));
	head->prior = NULL;
	head->next = NULL;
	return head;
} 

void Creat(PNODE head) {						
	NODE *r,*s;
	int age;
	char name[20];
	r = head;
	printf("请输入学生信息:年龄和姓名\n");
	while(1) {
		scanf("%d",&age);
		scanf("%s",name);
		if(age == 0) break;
		s = (NODE *)malloc(sizeof(NODE));
		s->age = age;
		strcpy(s->name,name);
		r->next = s;
		s->prior = r;							//结点左链域存储直接前驱结点的地址
		r = s;	
	}
	r->next = NULL; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值