数据结构 EP2 链表 C语言实现单链表及其增删查改

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 EP2 链表 C语言实现单链表及其增删查改相关的知识,希望对你有一定的参考价值。

1.什么是链表

顺序表在内存中是连续存储的,使用起来有很大的局限性,比如我们在头部插入数据时需要将后面的数据全部往后挪动一位,而且需要频繁的进行的进行扩容会造成效率上的损耗。 当然顺序表也存在自己的优点,比如可以直接通过下标进行随机访问 既然一个连续表无法满足我们的要求,那是否可以直接在内存内开辟不连续的空间呢? 像这样一个通过指针串联起来的一个一个节点的储存方式就叫做链表 当然链表按照链接方式来划分的话可以分为很多类,不过我们最常用的就是不带头单链表和带头双向链表

2.链表的开辟思路

1.我们先将链表节点的基本构造写出来

typedef int Linklistdata;//链表中存储的数据
typedef struct LinkNode

	Linklistdate data;
	struct LinkNode* next;
LinkNode;

注意这里我们重定义一个int类型是为了方便以后的更改,比如我们需要将 整型改为浮点型只需要将int 更改即可

2.存储空间的开辟

1.不合理方法展示

typedef int Linklistdata;
typedef struct LinkNode

	Linklistdate data;
	struct LinkNode* next;
LinkNode;
LinkNode* BuyLinklist();//申请空间

注意函数栈帧在函数执行结束之后会销毁,所以我们开辟空间要在堆上开辟 动态开辟的内存空间是在堆上开辟的,由程序员手动回收或者在程序结束后由操作系统回收

LinkNode* BuyLinkNode()

	LinkNode* ptr = malloc(sizeof(LinkNode));
	assert(ptr);
	return ptr;

注意看我们在这里开辟空间的方法,开辟之后我们并没有将数据存入之中。 这样我们的确可以创建一个链表,但创建之后如果我们需要增添新的节点会非常麻烦,所以我们在开辟节点时最好就先将我们的数据存入到新开辟的节点之中。

2.更为合理的方法

LinkNode* BuyLinkNode(Linklistdata x)

	LinkNode* ptr = malloc(sizeof(LinkNode));
	assert(ptr);
	ptr->data=x;
	return ptr;

LinkNode* Linklist()

	int i = 0;
	int j = 1;
	printf("请输入开辟链表节点个数");
	scanf("%d", &i);
	if(i==0)
	    return NULL;
	LinkNode* phead = BuyLinkNode(j);
	j++;
	LinkNode* tail = BuyLinkNode(j);
	j++;
	phead->next = tail;
	for (j; j <= i; j++)
	
		LinkNode* newnode = BuyLinkNode(j);
		tail->next = newnode;
		tail = newnode;
	
	tail->next = NULL;
	return phead;

在我们的测试函数中测试没有问题,当然注意这里我是为了方便调试所以让链表直接使用了循环中的值,实际使用中你可以加一个scanf输入值。

3.链表的增删查改

1.查

因为我们在插入,删除这些过程中都需要用到查的功能,所以我们先写好查的内容方便后边的使用

void LinklistPrintf(LinkNode* phead)

	LinkNode* cur = phead;
	while (cur)
	
		printf("[%d]->", cur->data);
		cur = cur->next;
	
	printf("NULL");

老规矩写完一个模块测试一下

LinkNode* LinklistFind(LinkNode* ptr, Linklistdate x)

	assert(ptr);
	while (ptr)
	
		if (ptr->date == x)
		
			return ptr;
		
		else
		
			ptr = ptr->next;
		
	
	return NULL;//未找到返回空

2.增

写增的内容时我们需要考虑多种情况,头插,中间插入,尾部插入 并且我们还要考虑当我们找到一个数据之后是在它的前面还是后面插入新节点 我们先以最简单的后面插入新节点为例写一个插入

void LinklistAddback(LinkNode* plist, Linklistdata x)

    if(plist==NULL)
    
      printf("")
    
	LinkNode* newnode = BuyLinkNode(x);
	newnode->next = plist->next;
	plist->next = newnode;

插入的原理很简单 就是先让新节点的next指向后方节点 让插入节点的next指向新节点 当然这个插入函数并不完善 我们以头插为例子来了解一下

void LinklistPushback(LinkNode* phead)

	assert(phead);

这个函数这样写合理吗?明显是不合理的 1.首先 我们在头插时是需要改变头的位置的,很明显这个函数并没有办法改变头的地址,函数中的形参只是实参的临时拷贝,我们可以直接使用一级指针来操作指针指向的内存,但没法直接更改指针的实参. 所以在更改头节点之后我们要将头节点的地址返回或者直接传一个二级指针过来 2.其次 链表是有可能为空的 所以此处不需要进行断言

void LinklistPushback(LinkNode** phead,Linklistdata x)

	if (*phead == NULL)
	
		*phead = BuyLinkNode(x);
		(*phead)->next = NULL;
	
	else
	
		LinkNode* newnode = BuyLinkNode(x);
		newnode->next = *phead;
		*phead = newnode;
	

3.删

删除时我们删除的节点是malloc出来的,所以我们需要将这个空间释放,并且将这个节点前后的两个节点连接起来 并且我们要考虑到头删跟尾删的情况

void LinklistPop(LinkNode** pphead, LinkNode* plist)

	if (*pphead == plist)//头删的情况
	
		*pphead = (*pphead)->next;
		free(plist);
	
	else
	
		LinkNode* cur = *pphead;
		while (cur)
		
			LinkNode* ccur = cur;
			cur = cur->next;
			if (plist==cur&&cur->next==NULL)//尾的情况
			
				free(cur);
				ccur->next = NULL;
				return;
			
			else if (cur == plist)
			
				ccur->next = cur->next;
				free(cur);
				return;
			
		
	

测试没有问题

4.改

你可以通过传递地址进行修改,也可以通过传递数据进行修改,也可以选择让它修改第几个节点 思路很简单,这里不做过多演示 下面这个函数会将链表中所有值为x的节点值修改为y

void LinklistChange(LinkNode* phead, Linklistdata x,Linklistdata z)

	LinkNode* cur = phead;
	while (cur)
	
		if (cur->data == x)
		
			cur->data = z;
		
		cur = cur->next;
	

5.销毁链表

链表所开辟的空间都是我们在堆区开辟出来的所以需要手动释放 考虑到链表的特点我们需要从尾处开始释放 所以我们需要从头开始找到尾,再从尾开始释放,相信看到这里大家的思路瞬间就出来了 这不就是我们的老朋友递归吗

void Linklistdestory(LinkNode* phead)

	LinkNode* cur = phead;
	if (phead->next != NULL)
	
		phead = phead->next;
		Linklistdestory(phead);
	
	cur->next = NULL;
	free(cur);

头文件展示

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef int Linklistdata;
typedef struct LinkNode

	Linklistdata data;
	struct LinkNode* next;
LinkNode;
LinkNode* BuyLinkNode(Linklistdata x);//申请空间
LinkNode* Linklist();//创建一个链表
LinkNode* LinklistFind(LinkNode* phead, Linklistdata x);//查找
void LinklistPrintf(LinkNode* phead);//打印链表
void LinklistAddback(LinkNode* plist, Linklistdata x);//增加
void LinklistPushFront(LinkNode** phead,Linklistdata x);//头插
void LinklistPop(LinkNode** phead ,LinkNode* plist);//删除
void LinklistChange(LinkNode* phead,Linklistdata x,Linklistdata z);//改
void Linklistdestory(LinkNode* phead);//销毁

总结

上述代码因为链表的缺陷和C语言的不便其实很多功能细节并不完善。 当然我更多的是提供思路,所以上述代码所实现的功能并不能满足你的全部需求或想法。所以你可以自己进一步完善。

以上是关于数据结构 EP2 链表 C语言实现单链表及其增删查改的主要内容,如果未能解决你的问题,请参考以下文章

单链表~增删查改(附代码)~简单实现

C数据结构单链表接口函数逻辑解析与代码实现(含详细代码注释)

单链表的(增删查改)的实现

数据结构学习笔记(单链表单循环链表带头双向循环链表)的增删查改排序等)

数据结构学习笔记(单链表单循环链表带头双向循环链表)的增删查改排序等)

数据结构单链表的增删查改,附代码+笔记gitee自取