C语言实现线性动态(单向)链表的示例代码

目录
  • 什么是链表
  • 为什么不用结构体数组
  • 链表的操作
    • 创建表
    • 删除元素
    • 插入元素
  • 代码及运行结果

什么是链表

链表是数据结构里面的一种,线性链表是链表的一种,线性链表的延伸有双向链表和环形链表。在编程语言中优化数据结构可以在处理大数据时大大降低程序的空间复杂性和时间复杂性。这里我只用一个简单的例子——线性单向链表为例,说明C语言是如何实现该结构的。

链表的元素是由结构体来实现struct table *p。结构体中有一个成员是结构体指针struct table *next,而这个结构体指针的类型和此结构体类型相同。除链表最后一个元素外,每一个结构体的指针都指向链表中下一个元素的结构体,最后一个元素的结构体指针为空(NULL)。保存链表时,只需要记录下链表的头指针,即链表中第一个结构体的地址即可。添加一个链表元素时,都需要单独申请一段内存;删除时则将其释放掉。在查找链表时,只需要顺着结构体指针的顺序一个一个往下查找,直到查找的结构体中的成员指针。以下是一个链表结构的示意:

struct Student{
int ID;//学号
char[20];//姓名
int marks[5];//5门考试的成绩
struct Student *next;//指向下一个结构体的结构体指针
}

为什么不用结构体数组

有人会有问为什么不直接用一个结构体数组代替链表,结构体数组占据的内存空间是连续的,如果使用malloc指令一样可以动态存储,而且连续的内存空间肯定比不确定的内存空间效果要好。但是如果对一个动态数组插入或者删除元素的话,它后面的所有元素都需要变动位置,因此修改数组会比链表要难的多。但它的优点在于查找方式很灵活,每一个元素相对于数组首元素地址都有一个偏移量i,因此对于数组来说既可以顺向查找也可以反向查找,还可以二分法查找。

由于链表的每一个元素都有一段内存,这些内存未必是连续的,加上链表本身会比结构体数组多一个指向下一个元素的结构体指针,因此从节省内存的角度看链表是不如数组的。但是链表删除元素的时候(假设这个元素是a[k])只需要a[k-1]的next指针指向a[k+1],再把a[k]的内存释放即可,非常方便;插入元素的时候(假设这个元素是a[n]),只需要a[n-1]的next指针指向a[n]再将a[n]的指针指向a[n+1]即可,相比于数组来说只修改了两个元素,速度快而且非常方便。

链表的操作

链表的操作分为——创建表、插入元素、删除元素、清空表、查找表、打印表。其中插入/删除的元素可以是一个也可以说多个。链表从存储类型上来分可以分为静态链表和动态链表,静态链表是事先编写好的链表,占用的内存是静态存储区的内存,使用时不可以对其中的元素进行删减,只能查找;动态链表是按照程序要求生成的链表,存放于动态存储区,结构比较灵活,每一个元素都占据一部分存储空间,如果要删除元素,则释放该位置的内存;如果要添加元素,则申请一个结构体内存区的内存。

创建表

创建链表需要两个指针,一个作为先行指针(*p1),开辟内存并保存结构体的值;一个作为缓存指针(*p2),保留先行指针的所有值并且将它的next指向先行指针。构建链表时,先行指针赋一个值,后行指针保存一个值并且后行指针的next指向先行指针。赋值终止时,先行指针的next指向NULL,同时将先行指针赋值给后行指针,链表即构建完毕
代码窗口可以通过键盘的"←"和"→"查看。

struct Student * input()
{
	struct Student *p1,*p2,*head=NULL;
	printf("************************动态链表实验***********************\n【输入动态链表】\n");
	printf("请依次输入学号	姓名  身高(cm)  体重(kg)(用空格间隔,学号输入0结束):\n");
	p2=p1=(struct Student *)malloc(LEN);//开辟内存
	scanf("%d %s %f %f",&p1->ID,&p1->name,&p1->height,&p1->weight);
	if(p1->ID==0)return(head);
	else head=p1;
	while(p1->ID!=0)
	{
		p2->next=p1;
		p2=p1;
		p2->BMI=(float)p2->weight/(p2->height/100)/(p2->height/100);//求BMI指数
		p1=(struct Student *)malloc(LEN);//开辟内存
		scanf("%d %s %f %f",&p1->ID,&p1->name,&p1->height,&p1->weight);
	}
	p2->next=NULL;
	return(head);//返回链表头指针
}

删除元素

删除元素链表的第n个元素只需要将第n-1个元素的next指针指向第n+1个元素,再将第n个元素的内存释放即可,我这里是写的其中一个例子,根据关键字学号(int stdID)删除表中的某个元素,同时返回删除后的链表首地址(如果删的是第一个元素,则链表首地址会变)
代码窗口可以通过键盘的"←"和"→"查看。

struct Student *delate(struct Student *head,int stdID)
{
	struct Student *p1,*p2;
	if(head->ID==stdID)
	{
		p1=head->next;
		free(head);
		return p1;
	}//如果删除的是第一个元素,比较特殊,需要修改头指针,其余不动
	//剩余几种情况都是修改next结构体指针
	for(p1=head;p1!=NULL;p2=p1,p1=p1->next)//p1指针和p2指针同时查找,p1指向当前的学生,p2保指向了上一个学生
	{
		if(p1->ID==stdID)
		{
		 	p2->next=p1->next;//假设找到了需要删除的学生的学号,则让它上一个学生的指针指向跳过他的下一个学生
		 	free(p1);
		 	return head;
		}
	}
	return NULL;//返回NULL代表没找到
}

插入元素

插入元素的原理是,假设要在第n个元素前插入一个元素。首先判断它是不是首元素,如果是,则修改头指针指向该元素,并将该元素的next指向原来的头指针。如果不是首元素,是第k个元素之前插入一个元素,则将第k-1个元素的next指针指向插入元素(或者子表)的地址(或者头指针),将插入元素的next指针(或尾指针)指向第k个元素。本示例代码是根据一个学号(主要关键字)插入一个元素(或者子表)的函数,返回链表的首地址(因为如果在第一个元素前面插入,可能改变链表的首地址)。
代码窗口可以通过键盘的"←"和"→"查看。

struct Student *insert(struct Student *head,int stdID,struct Student *insertstd)
{
	struct Student *p1,*p2,*p;
	for(p=insertstd;p->next!=NULL;p=p->next);//找到insert链表的最后一个元素
	if(head->ID==stdID)
	{
		p->next=head;
		return insertstd;
	}

	for(p1=head;p1!=NULL;p2=p1,p1=p1->next)
	{
		if(p1->ID==stdID)
		{
			p2->next=insertstd;
			p->next=p1;
			return head;
		}
	}
	return NULL;
}

代码及运行结果

完整代码及注释如下:
代码窗口可以通过键盘的"←"和"→"查看。

#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
#define LEN sizeof(struct Student)//定义结构体变量的大小为符号常量LEN
struct Student{
	int ID;//学号
	char name[20];//姓名
	float height;//身高
	float weight;//体重
	float BMI;//BMI指数,录入时不需要计算
	struct Student *next;//指向下一个结构体
};
struct Student *input();//输入函数
void output(struct Student * head);//输出函数
struct Student *delate(struct Student *head,int stdID);//删除一个元素,返回删除后表的头指针
struct Student *insert(struct Student *head,int stdID,struct Student *insertstd);//返回插入元素(子表)后的头指针
int append(struct Student *head);//插入一个链表,从input函数输入
struct Student *isexist(struct Student *head,int stdID);
int main()
{
	struct Student *present;//当前链表的头指针
	int choice;
	bool next;
	int stdID;
	/*
	1:插入一个元素
	2:删除一个元素
	3:续表
	4:查找表
	*/
	printf("**********动态链表实验**********\n初始化一个链表:\n");
	present=input();//当前的链表指针
	do{
		printf("请选择:\n|1:插入元素(子表)\n|2:删除元素\n|3:续表\n|4:查找表\n");
		scanf("%d",&choice);
		switch(choice)
		{
			case 1:
				printf("请输入插入地点的后一个同学的学号: ");
				scanf("%d",&stdID);
				if(isexist(present,stdID)==NULL)
				{
					printf("该学生不存在!\n");
					break;//退出switch语句
				}
				present=insert(present,stdID,input());
				printf("插入元素后的链表为:\n");
				output(present);
				break;
			case 2:
				printf("请输入删除元素的学号:  ");
				scanf("%d",&stdID);
				if(isexist(present,stdID)==NULL)
				{
					printf("该学生不存在!\n");
					break;//退出switch语句
				}
				present=delate(present,stdID);
				printf("删除后的链表为:\n");
				output(present);
				break;
			case 3:
				append(present);
				printf("续表后的链表为:\n");
				output(present);
				break;
			case 4:
				printf("当前链表为:\n");
				output(present);
				break;
		}
		printf("是否继续(Yes:1,No:0):  ");
		scanf("%d",&next);
		fflush(stdin);
	}while(next);

	return 0;
}
struct Student * input()
{
	struct Student *p1,*p2,*head=NULL;
	printf("************************动态链表实验***********************\n【输入动态链表】\n");
	printf("请依次输入学号	姓名  身高(cm)  体重(kg)(用空格间隔,学号输入0结束):\n");
	p2=p1=(struct Student *)malloc(LEN);//开辟内存
	scanf("%d %s %f %f",&p1->ID,&p1->name,&p1->height,&p1->weight);
	if(p1->ID==0)return(head);
	else head=p1;
	while(p1->ID!=0)
	{
		p2->next=p1;
		p2=p1;
		p2->BMI=(float)p2->weight/(p2->height/100)/(p2->height/100);//求BMI指数
		p1=(struct Student *)malloc(LEN);//开辟内存
		scanf("%d %s %f %f",&p1->ID,&p1->name,&p1->height,&p1->weight);
	}
	p2->next=NULL;
	return(head);//返回链表头指针
}
void output(struct Student *head)
{
	struct Student *p;
	int num=1;
	p=head;//将头指针地址传给p
	printf("【输出动态链表】\n");
	printf("|学号\t\t|姓名\t|身高\t|体重\t|BMI\n");
	while(p!=NULL)
	{
		printf("%3d|%08d\t|%s\t|%5.2f\t|%5.2f\t|%lf\n",num++,p->ID,p->name,p->height,p->weight,p->BMI);
		p=p->next;
	}
}
struct Student *delate(struct Student *head,int stdID)
{
	struct Student *p1,*p2;
	if(head->ID==stdID)
	{
		p1=head->next;
		free(head);
		return p1;
	}//如果删除的是第一个元素,比较特殊,需要修改头指针,其余不动
	//剩余几种情况都是修改next结构体指针
	for(p1=head;p1!=NULL;p2=p1,p1=p1->next)//p1指针和p2指针同时查找,p1指向当前的学生,p2保指向了上一个学生
	{
		if(p1->ID==stdID)
		{
		 	p2->next=p1->next;//假设找到了需要删除的学生的学号,则让它上一个学生的指针指向跳过他的下一个学生
		 	free(p1);
		 	return head;
		}
	}
	return NULL;//返回NULL代表没找到
}
struct Student *insert(struct Student *head,int stdID,struct Student *insertstd)
{
	struct Student *p1,*p2,*p;
	for(p=insertstd;p->next!=NULL;p=p->next);//找到insert链表的最后一个元素
	if(head->ID==stdID)
	{
		p->next=head;
		return insertstd;
	}

	for(p1=head;p1!=NULL;p2=p1,p1=p1->next)
	{
		if(p1->ID==stdID)
		{
			p2->next=insertstd;
			p->next=p1;
			return head;
		}
	}
	return NULL;
}
int append(struct Student *head)//插入一个链表,从input函数输入
{
	struct Student *p;
	for(p=head;p->next!=NULL;p=p->next);//找到head链表的最后一个元素
	p->next=input();//从input输入需要添加的元素,可以是1个或者多个
	return 0;
}
struct Student *isexist(struct Student *head,int stdID)
{
	struct Student *p;
	for(p=head;p!=NULL;p=p->next)
	{
		if(p->ID==stdID)
		{
			return p;
		}
	}
	return NULL;
}

输出效果如下图:

到此这篇关于C语言实现线性动态(单向)链表的示例代码的文章就介绍到这了,更多相关C语言 线性动态(单向)链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言静态链表和动态链表

    1. 静态链表 结构体中的成员可以是各种类型的指针变量,当一个结构体中有一个或多个成员的基类型是本结构体类型时,则称这种结构体为"引用自身的结构体".如: struct link { char ch; struct link *p; } a; p是一个可以指向 struct link 类型变量的指针成员.因此,a.p = &a 是合法的表达式,由此构成的存储结构如图1所示. 图1 引用自身的结构体 例1 一个简单的链表 #include <stdio.h> stru

  • C语言实现动态链表的示例代码

    目录 结构体定义已经函数声明 函数实现 创建一个链表 判断链表是否为空 获得链表中节点的个数 在某个特定的位置插入一个元素 获得指定下标的节点的元素 删除一个节点 链表逆序 链表的清空 链表的销毁 链表的遍历 按特定的元素查找节点 按某些特定的条件删除所有符合情况的节点 链表的排序 总结 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点包括两个部分:一个是存储

  • C语言创建链表错误之通过指针参数申请动态内存实例分析

    本文实例讲述了C语言创建链表中经典错误的通过指针参数申请动态内存,分享给大家供大家参考之用.具体实例如下: #include <stdio.h> #include <stdlib.h>// 用malloc要包含这个头文件 typedef struct node { int data; struct node* next;// 这个地方注意结构体变量的定义规则 } Node; void createLinklist(Node* pHder, int length) { int i =

  • C语言实现线性动态(单向)链表的示例代码

    目录 什么是链表 为什么不用结构体数组 链表的操作 创建表 删除元素 插入元素 代码及运行结果 什么是链表 链表是数据结构里面的一种,线性链表是链表的一种,线性链表的延伸有双向链表和环形链表.在编程语言中优化数据结构可以在处理大数据时大大降低程序的空间复杂性和时间复杂性.这里我只用一个简单的例子——线性单向链表为例,说明C语言是如何实现该结构的. 链表的元素是由结构体来实现struct table *p.结构体中有一个成员是结构体指针struct table *next,而这个结构体指针的类型和

  • C语言实现无头单向链表的示例代码

    目录 一.易错的接口实现 1.1 新节点开辟函数 1.2 尾插 1.3 尾删 二.常见简单接口 2.1 打印链表 2.2 节点计数器 2.3 判断是否为空链表 2.4 通过值查找节点 2.5 头插 2.6 头删 2.7 在任意节点后插入节点 2.8 在任意节点后删除节点 2.9 销毁链表 三.头文件相关内容 3.1 引用的库函数 3.2 结构体声明 一.易错的接口实现 1.1 新节点开辟函数 由于创建一个新节点是频繁的操作,所以封装为一个接口最佳. 链表节点的属性有:(1)数值.(2)指向下一个

  • JavaScript封装单向链表的示例代码

    使用JavaScript封装单向链表: 1. 封装LinkList的类,用于表示我们的链表结构. 2. 在LinkList类中有一个Node类,用于封装每一个节点上的信息(data与next). 3. 在链表中保存两个属性,一个是链表的长度,一个是链表中的第一个节点. 4.封装一些链表的常用方法: append(element):想列表尾部添加一个新的项: insert(position,element):向列表的特定位置插入一个新的项: get(position):获取对应位置的元素: ind

  • C++语言实现线性表之链表实例

    本文实例讲述了C++语言实现线性表之链表实现方法.分享给大家供大家参考.具体分析如下: 插入.删除结点的代码有点多,但这样提高了代码的可读性,且不增加时间复杂度,不会影响程序性能 #include <iostream> using namespace std; template<typename T> class CList; template<class T> class Node { friend CList<T>; private: T m_data;

  • C/C++实现线性单链表的示例代码

    目录 线性单链表简介 C语言实现代码 C++语言实现代码 线性单链表简介 使用链存储结构的线性存储结构为线性单链表,线性存储结构是元素逻辑结构一对一,链存储结构是元素物理结构不连续,线性单链表操作没有限制,线性单链表优点是可以直接插入和删除元素,线性单链表缺点是不可以使用下标获取和修改元素. C语言实现代码 #include<stdio.h>//包含标准输入输出文件 #include<stdlib.h>//包含标准库文件 typedef struct element//元素 { i

  • C语言实现动态顺序表的示例代码

    目录 顺序表概念及结构 基本操作 功能实现 程序运行 顺序表概念及结构 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储.在数组上完成数据的增删查改. 分类: 一般分为静态顺序表和动态顺序表: 静态顺序表:数组大小是固定的用完了无法增容:同时我们无法控制给数组开多少空间合适,开少了,空间不够:开多了,有回会存在空间浪费: 动态顺序表:空间是可以变动的,空间满了我们就增容:解决了静态顺序表的空间不足问题,同时也在一定程度上减少了空间浪费: 因此本篇博客主要实现

  • C语言实现动态版通讯录的示例代码

    目录 前言 contact.c contact.h test.c 前言 大家好~今天是通讯录的动态版本实现,希望对大家对知识的掌握有所提升! contact.c #include"contact.h" //初始化通讯录 void InitContact(Contact* pc) { assert(pc); pc->sz = 0; pc->data = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo)); if (NULL == pc-

  • C语言编程内存分配通讯录静态实现示例代码教程

    实现一个通讯录: 通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名.性别.年龄.电话.住址 提供方法: 1. 添加联系人信息 2. 删除指定联系人信息 3. 查找指定联系人信息 4. 修改指定联系人信息 5. 显示所有联系人信息 6. 清空所有联系人 7. 以名字排序所有联系人 首先我们采用顺序表的方式来实现一个通讯录,顺序表就是一种静态的模式.但是呢,静态的方式存在着一些明显的弊端,比如说:(1)信息少了存在空间浪费现象,信息多了存在空间不足的现象:(2)无法对信息进行保存,没有实

  • 基于Go语言实现的简易api网关的示例代码

    浏览器的请求去请求目标地址,然后获得结果它再发送给浏览器.对于Go语言来说,实现转发只需要简单的一行代码即可实现,如下所示: httputil.NewSingleHostReverseProxy(address) 基于此功能,进行简单包装,实现从远端admin管理中心获取需要转发的路由信息或者可以从本地配置文件中获取,实现动态转发.后续可以根据业务情况,可以实现如下功能: 开发接口,实现动态添加代理规则,进行转发 过滤不合法的接口 接口限流 统一日志记录 - 代码如下: package main

随机推荐