线性表(二) 单链表的增删改查及源码 (修改)

<!--author--Kang-->

<!--time--2020/8/22-->

线性表的链式存储结构

链表

单链表的特点:

顺序表中,只要单单存放数据元素,而链表中,不仅要存放数据元素,还要存放下一个数据元素在内存中的位置。因此,存放数据元素的域称为数据域,存放后继位置的域为指针域,两者组成一个模块,称为结点,链表就是由n个节点组成的。链表具有以下几个特点:

1)将数据封装成结点,由结点组成的线性表;

2)通过结点的指针域将结点连接到一起;

3)结点的内存空间不一定时连续的,但逻辑上是连续的

4)有一个头指针指向第一个数据元素;

5)链表是线性表的一种,是线性表的链式存储结构的实现。

其中,链表中每个结点只有一个指针域指向后继结点,这种链表称为单链表线性链表

单链表的结构:

头结点:为了处理方便,会在链表的第一个元素前增加一个头结点,该结点的数据域中不存储线性表的数据元素,其作用是为了对链表进行操作时,可以对空表、非空表的情况以及对首元结点进行统一处理。

尾结点:最后一个结点的指针域为空指针

首元结点:链表中存储第一个数据元素的结点。如上图中data1结点。

头指针:通常用head名称定义,指向链表中第一个结点的指针,有头结点时指向头结点,若链表设计时不设头结点,则指向首元结点。

单链表的定义

通过单链表的头指针可以完成单链表的所有操作,而单链表的结点有两个必须的属性:

1)数据元素信息:节点中的数据元素基本信息;

2)指针域:结点之间通过指针进行逻辑关联。用来存储下一个结点的地址。

由此可定义出线性表的链式存储的结构体类型:

typedef int ElementType;//链表中元素的类型,根据实际情况而定,此处以int为例

//单链表结构体定义
typedef struct link_node{
   ElementType data;//结点的数据域
   stuct link_node *next;//结点的指针域:(此时结构体尚未定义完整,不能用 NODE *next; )用该结构体类型定义一个指针,用来指向下一个结点的地址
}LINK_NODE,*LINK_LIST;

注意这里的定义,按照前面的分析,实际上只需要有LINK_NODE类型定义就行了,我们可以通过定义LINK_NODE *head 来定义一个头指针,但为了提高程序的可读性,在用typedef定义单链表时起了两个别名,分别为LINK_NODE和LINK_LIST,而实际上LINK_LIST就是struct link_node * ,因此在定义单链表时可以定义为:

LINK_LIST list;

或者

LINK_NODE *list

当然,这里的list其实就是头指针head;

单链表的初始化操作:

该步骤主要是为了完成头结点的创建以及相关指针的置空(下一结点)

1、先通过malloc给头结点分配空间

2、判断内存是否分配失败,失败返回0

3、头结点的next指针置空:(*L)->next = NULL;

4、操作结束,返回1

int init_list(LINK_LIST *L){//在函数内部去修改函数外部数据时要用指针
*L = (LINK_NODE *)malloc(sizeof(LINK_NODE));//分配头结点空间
if (*L ==NULL)//如果内存分配失败
{
return 0;
}
(*L)->next = NULL;//头结点的next置为空
return 1;
}

获取单链表的长度:

计算单链表中有效数据结点的个数(不包含头结点)

1、定义变量length,用来统计有效结点个数;

2、定义tmp指针指向首元结点,首元结点为头结点的下一结点:LINK_NODE *tmp = L->next;

3、通过while循环计算有效结点个数,循环条件:tmp指针为非空结点:tmp!=NULL

4、循环体内为length自增,tmp指针指向下一结点

5、返回长度length

int get_length(LINK_LIST L) {//这里不需要修改链表,直接用形参操作
int length = 0;
LINK_NODE *tmp = L->next;//从首元结点开始,而非头结点
while (tmp!=NULL)
{
length++;
tmp = tmp->next;//指针指向下一个结点
}
return length;
}

单链表的遍历操作:

1、先定义一个指针指向首元结点

2、通过while循环实现遍历,循环条件为 tmp!=NULL,即链表为非空或tmp指针没有超出链表范围

3、打印数据域中tmp->data;

4、将tmp指针指向下一结点,实现循环,当tmp指向尾结点时,即tmp->next==NULL,在下一个循环开始时跳出循环

void show(LINK_LIST L) {
LINK_NODE *tmp = L->next;//从首元结点开始,而非头结点
while (tmp != NULL)
{
printf("%d", tmp->data);//注意输出数据类型,这里以int为例
if (tmp->next!=NULL)
{
printf(",");
}
else {
printf(" ");//对最后一个结点数据输出后不加逗号处理
}
tmp = tmp->next;//将指针指向下一个结点
}
}

单链表插入操作

1、定义一个p_insert指针指向头结点,用来存储待插入结点的前一个结点

2、判断插入位置 i 是否合理:即1< i < get_length(L) +1 (这里长度+1是将新结点插在末尾)

3、通过for循环移动p_insert指针,将p_insert指针指向插入位置 i 的前一个结点

4、创建新结点p_new,并给新结点分配空间。这里可以添加一个判断,如果空间分配失败,则返回0

5、给新结点的数据域赋值:p_new->data = e;

6、将第 i -1个结点所在地址(即 p_insert->next)赋值给新结点的next指针:p_new->next = p_ins->next;

7、将新结点的地址赋值给前一结点的next指针:p_insert->next = p_new;

//单链表的插入操作
int insert(LINK_LIST L,int i,ElementType e) {
LINK_NODE *p_ins = L;//先定义一个指针指向头结点
//
if (i<1||i>get_length(L)+1)
{

return 0;
}
for (int j = 0; j < i-1; j++)//循环中可能会使得指针指向空,则意味着插入位置将会超过链表长度+1,使得操作失败,
{
p_ins = p_ins->next;//将p_ins指向要插入结点的前一个结点
}
LINK_NODE *p_new = (LINK_NODE *)malloc(sizeof(LINK_NODE));
if (p_new ==NULL)//分配空间失败,返回0
{
return 0;
}
p_new->data = e;//给数据域赋值
p_new->next = p_ins->next;//将新结点的next指向插入位置的原结点
p_ins->next = p_new;//将新结点链接到插入位置上

return 1;
}

单链表的删除操作:

1、判断删除位置是否合理,不合理则返回0 :1<i<get_length(L)

2、定义一个指针指向头结点,用来存储删除位置的前一个结点的地址:LINK_NODE *p_prev = L;

3、定义待删除结点的指针:LINK_NODE *p_del;

4、移动p_prev 指针,将指针指向删除节点 i 的前一个结点所在位置

5、通过待删除结点的前一结点 p_prev 给待删除结点 p_del 赋值地址

6、跳过删除结点,将待删除结点的下一结点指针地址p_del->next赋值给前一个结点的下一结点指针p_prev->next

7、释放待删除结点的空间:free(p_del),返回1,表示操作完成

//单链表的删除操作
int delt(LINK_LIST L,int i) {
if (i<1||i>get_length(L))
{
printf("操作失败,删除位置不存在!");
return 0;//删除失败
}
LINK_NODE *p_prev = L;//previous 定义 前一个结点
LINK_NODE *p_del;//定义待删除结点指针
for (int j = 0; j < i-1; j++)//移动i-1次,将prev指针指向要删除结点 i 的前一个结点;
{
p_prev = p_prev->next;
}
p_del = p_prev->next;//通过待删除结点的前一结点给待删除结点赋值地址
p_prev->next = p_del->next;//跳过删除结点,将待删除结点的指针域赋值给前一结点的指针域
free(p_del);//释放待删除结点的空间

return 1;
}

单链表的修改操作

两种方法,方法一:先创建一个新的结点,再将新结点覆盖到需要需要修改的结点上,最后删除旧结点;方法二:先找到要修改的结点,再直接修改该结点
int updata(LINK_LIST L,int i,ElementType e) {
	if (i<1||i>get_length(L))//判断是否空表或位置是否合理
	{
		printf("操作失败,该位置不存在!");
		return 0;
	}
	LINK_NODE *p_mod = L;//modify 定义指针指向头结点。也可以指向首元结点,但要注意下面循环次数会发生变化
	for (int j = 0; j < i;j++) {//因为头指针指向头结点,所以移动i次。若指向首元结点,则移动i-1次,条件改为j<i-1
		p_mod = p_mod->next;//将指针指向需要修改的位置
	}
	p_mod->data = e;//直接修改目标结点的数据域
	return 1;
}

单链表的查找操作

1、创建p_find指针指向起始节点

2、通过循环变脸查找,循环条件为p_find!=NULL;

3、判断,如果当前指针指向结点的数据域与查找目标一致,返回当前结点的地址

4、p_find指针向后移一位,保证循环

5、跳出循环,返回NULL,说明未查找到数据

LINK_NODE *search(LINK_NODE *p_from,ElementType e) {//返回一个结点的指针
	LINK_NODE *p_find = p_from;//指向起始结点
	
	while (p_find!=NULL)//当前指向结点为有效结点
	{
		if(p_find->data==e){//当前指针指向结点的数据域与查找目标一致
	
			//printf("已查找到目标数据!该数据地址为:0x%p
",p_find);
			return p_find;
		}
		p_find = p_find->next;//指针后移
	}
	printf("未找到该数据!
");
	return NULL;
}

至此,单链表的增删改查结束。下面为调试的源码:

源码:

#include <stdio.h>
#include <malloc.h>
typedef int ElementType;//链表中元素的类型,根据实际情况而定,这里以int为例
						
//单链表结构体定义:
typedef struct link_node {
	ElementType data;//结点的数据域
	struct link_node* next;//结点的指针域
}LINK_NODE,*LINK_LIST;//这里LINK_LIST和 next 类型一致,都是struct link_node*类型
/*(LINK_LIST本身就是指针,因此定义头指针时可以直接这样  LINK_LIST head;)
也可以这样定义:LINK_NODE * head;
*/

//单链表的初始化操作:
	//主要时为了完成头结点的创建以及相关指针的置空
int init_list(LINK_LIST *L){//在函数内部去修改函数外部数据时要用指针
	*L = (LINK_NODE *)malloc(sizeof(LINK_NODE));//分配头结点空间
	if (*L ==NULL)//如果内存分配失败
	{
		return 0;
	}
	(*L)->next = NULL;//头结点的next置为空
	return 1;
}

//获取单链表长度:计算单链表中有效数据结点个数(不包含头结点)
int get_length(LINK_LIST L) {//这里不需要修改链表,直接用形参操作
	int length = 0;
	LINK_NODE *tmp = L->next;//从首元结点开始,而非头结点
	while (tmp!=NULL)
	{
		length++;
		tmp = tmp->next;//指针指向下一个结点
	}
	return length;
}


//单链表的遍历操作
void show(LINK_LIST L) {
	LINK_NODE *tmp = L->next;//从首元结点开始,而非头结点
	while (tmp != NULL)
	{
		printf("%d", tmp->data);//注意输出数据类型,这里以int为例
		if (tmp->next!=NULL)
		{
			printf(",");
		}
		else {
			printf("
");//对最后一个结点数据输出后不加逗号处理
		}
		tmp = tmp->next;//将指针指向下一个结点
	}
}


//单链表的插入操作
int insert(LINK_LIST L,int i,ElementType e) {
	LINK_NODE *p_ins = L;//先定义一个指针指向头结点
	//
	if (i<1||i>get_length(L)+1)
	{

		return 0;
	}
	for (int j = 0; j < i-1; j++)//循环中可能会使得指针指向空,则意味着插入位置将会超过链表长度+1,使得操作失败,
	{
		p_ins = p_ins->next;//将p_ins指向要插入结点的前一个结点
	}
	LINK_NODE *p_new = (LINK_NODE *)malloc(sizeof(LINK_NODE));
	if (p_new ==NULL)//分配空间失败,返回0
	{
		return 0;
	}
	p_new->data = e;//给数据域赋值
	p_new->next = p_ins->next;//将新结点的next指向插入位置的原结点
	p_ins->next = p_new;//将新结点链接到插入位置上
	
	return 1;
}

//单链表的删除操作
int delt(LINK_LIST L,int i) {
	if (i<1||i>get_length(L))
	{
		printf("操作失败,删除位置不存在!");
		return 0;//删除失败
	}
	LINK_NODE *p_prev = L;//previous 定义 前一个结点
	LINK_NODE *p_del;//定义待删除结点指针
	for (int j = 0; j < i-1; j++)//移动i-1次,将prev指针指向要删除结点 i 的前一个结点;
	{
		p_prev = p_prev->next;
	}
	p_del = p_prev->next;//通过待删除结点的前一结点给待删除结点赋值地址
	p_prev->next = p_del->next;//跳过删除结点,将待删除结点的指针域赋值给前一结点的指针域
	free(p_del);//释放待删除结点的空间

	return 1;
}

//单链表的修改操作
/*一:先删除,再插入	
		1、先新建一个新结点
		2、给新结点赋值
		3、删除要修改的结点
		4、将新结点插入到删除结点的位置上

二:	
		1、找到要修改的结点
		2、直接修改该结点
*/


int updata(LINK_LIST L,int i,ElementType e) {
	if (i<1||i>get_length(L))//判断是否空表或位置是否合理
	{
		printf("操作失败,该位置不存在!");
		return 0;
	}
	LINK_NODE *p_mod = L;//modify 定义指针指向头结点。也可以指向首元结点,但要注意下面循环次数会发生变化
	for (int j = 0; j < i;j++) {//因为头指针指向头结点,所以移动i次。若指向首元结点,则移动i-1次,条件改为j<i-1
		p_mod = p_mod->next;//将指针指向需要修改的位置
	}
	p_mod->data = e;//直接修改目标结点的数据域
	return 1;
}

//单链表的查找操作

LINK_NODE *search(LINK_NODE *p_from,ElementType e) {//返回一个结点的指针
	LINK_NODE *p_find = p_from;//指向起始结点
	
	while (p_find!=NULL)//当前指向结点为有效结点
	{
		if(p_find->data==e){//当前指针指向结点的数据域与查找目标一致
	
			//printf("已查找到目标数据!该数据地址为:0x%p
",p_find);
			return p_find;
		}
		p_find = p_find->next;//指针后移
	}
	printf("未找到该数据!
");
	return NULL;
}



int main() {
	LINK_LIST list = NULL;//list为指针
	//创建单链表
	init_list(&list);

	//调用插入函数
	//int rlt = insert(list,2,1);//rlt:0 第一个位置没有元素,不能插入到第二个位置,
	insert(list,1,1);
	insert(list, 2, 2);
	insert(list, 3, 3);
	insert(list, 1, 4);
	insert(list, 3, 5);
	insert(list, 2, 99);
	insert(list, 3, 99);
	show(list);

	//调用删除函数
	int rlt1 = delt(list,6);
	printf("rlt = %d
",rlt1);
	show(list);

	int rlt2 = delt(list, 4);
	printf("rlt2 = %d
", rlt2);
	show(list);

	//调用修改函数

	int rlt3 = updata(list,0,100);
	printf("rlt3 = %d
",rlt3);

	int rlt4 = updata(list, 5, 100);
	printf("rlt4 = %d
", rlt4);

	updata(list,1,99);
	show(list);
	updata(list, 2, 100);
	show(list);

	//调用查询函数
	LINK_NODE *p_rlt1 = search(list, 88);
	LINK_NODE *p_rlt2 = search(list,100);

	LINK_NODE *p_rlt3 = list;
	int index = 0;
	while (1)//循环查找所有重复数据
	{
		p_rlt3 = search(p_rlt3->next,99);//从上一个找到的结点开始查询
		if (p_rlt3==NULL)
		{
			break;
		}
		index++;
		printf("已查找到第%d个数据的地址为0x%p
",index,p_rlt3);
	}
	index = 0;

	return 0;
}

 

原文地址:https://www.cnblogs.com/destiny-2015/p/13567051.html