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

本文详细介绍了在编程中如何使用单链表解决顺序表的效率问题,包括结构体指针的运用,以及各种链表操作方法如头部插入、尾部插入、删除等的实现过程和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在顺序表中实现数据的增删的操作时,都要把操作位置之后的数据全部移动一遍,操作效率低下。其次是容量固定(静态顺序表),虽然在动态顺序表中容量可变,但也会造成空间上的浪费。

单链表就完美解决了上述缺点。

这里要说明一点,单链表与顺序表没有哪个比哪个更好,只有哪个更适应场合。

介绍完上述后,现在开始单链表的实现,实现单链表需要用到结构体指针。

链表中的节点都是动态开辟的空间,是在堆上的,并不会出了作用域就销毁,如果在链表中找到值为1的节点,那就把这个节点的地址返回(将体现在指定位置插入删除的操作,解决了为什么没有传址而传值的疑惑)

创建结构体:

typedef int SLTDataType;
typedef struct SListNode SLTNode;
typedef struct SListNode
{
    SLTDataType data;
    SLTNode* next;
}SLTNode;

结构体指针SLTNode*储存下一个节点,可以理解为一条链条或套娃。直到最后一个节点的next指向空(NULL)时

需要注意的是这里SLTNode我进行了前置声明因此可以直接用转换后的语句。

如果没有前置声明,STLNode不会被识别出来,这里需要说一下,操作系统在找SLTNode时只会向上找不会向向下找,这也就是为什么有许多代码中会有前置声明的函数或者像当先操作的转换一样。

能直观的看到前置声明的重要性。

创建一个头文件SListNode.h用来放需要实现的函数(声明)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode SLTNode;
typedef struct SListNode
{
	SLTDataType data;
	SLTNode* next;
}SLTNode;


void SLTPrint(SLTNode* phead);

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

创建SListNode.c文件用来实现头文件的函数

首先是尾插

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;
    //不能直接这样用*pphead->next = NULL;加括号(*pphead)->next,  优先级问题
    //链表为空
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    //链表不为空,找尾节点
    SLTNode* node = *pphead;
    while (node->next)
    {
        node = node->next;
    }
    node->next = newnode;
}

使用SLTNode** pphead接收,传递的phead是SLTNode*类型,而我们需要改变phead的内容,因此要传址而非传值,故用二级指针接收。

在这里我想用*pphead->next可是会报错,这是为什么呢,因为优先级,->的优先级比*的优先级高,pphead先与->结合,因此报错。

首先创建 节点(结构体指针newnode)保存需要插入的值,然后要判断两种情况,一种是*pphead为空时,直接让*pphead等于newnode。第二种*pphead不为空,创建一个node等于*pphead,循环向后找,找到node->next为空时,node->next = newnode。

头插的实现

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    if (*pphead == NULL)
    {
        *pphead = newnode;
        newnode->next = NULL;
    }
    else
    {
        SLTNode* ret = *pphead;
        newnode->next = ret;
        *pphead = newnode;
    }
}

头插跟尾插一样需要判断两种情况,当*pphead为空和不为空的两种情况。为空直接等于,不为空创建临时的ret保存*pphead,然后让*pphead(头节点)等于newnode。

尾删的实现

//尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(*pphead);
    assert(pphead);
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* node = *pphead;
        SLTNode* ret = node->next;

        while (ret->next)
        {
            node = node->next;
            ret = ret->next;
        }
        free(ret);
        ret = NULL;
        node->next = NULL;
    }
}

删除涉及到空间的释放。因此要判断*ppead和ppead是否为空,不能传递一个空值来删除释放吧,所以要用assert断言。

也是要判断两种情况,一种是只有一个节点时,另一种是多个节点时。当为一个节点时,直接释放后置空即可,当为多个节点时需要遍历到最后一个节点后进行删除。

头删的实现

//头删
void SLTPopFront(SLTNode** pphead)
{
    assert(*pphead);
    assert(pphead);
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* node = (*pphead)->next;
        free(*pphead);
        *pphead = node;
    }
}

头删和尾删还是差不多,需要判断两种情况,是否是一个节点或多个节点。一个节点直接删除即可,多个节点,将头节点指向第二个节点。

查找的实现

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
    assert(phead);
    SLTNode* node = phead;
    while (node)
    {
        if (node->data == x)
        {
            return node;
        }
        node = node->next;
    }
    return NULL;
}

void SLTfind(SLTNode* phead, SLTDataType x)
{
    SLTNode* ret = SLTFind(phead, x);
    if (ret)
    {
        printf("找到了%d\n", ret->data);
    }
    else
    {
        printf("没找到%d\n", x);
    }
}

先创建SLTFind函数返回需要查找的节点(在指定位置操作时需要用到,因此将查找节点的操作分割出来),后用SLTfind判断。

在指定位置之前插入的实现

//指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    assert(*pphead);
    assert(pphead);
    assert(pos);
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;
    if (pos == *pphead)
    {
        SLTNode* node = (*pphead);
        *pphead = newnode;
        (*pphead)->next = node;
        return;
    }

    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
        prev = prev->next;
    }
    prev->next = newnode;
    newnode->next = pos;
}

链表中的节点都是动态开辟的空间,是在堆上的,并不会出了作用域就销毁,如果在链表中找到值为1的节点,那就把这个节点的地址返回

pos节点用SLTFind函数寻找,需要断言 pos是否为空,要判断*ppead和ppead是否为空。

判断两种情况,当插入节点为头节点时和不为头节点时。当插入节点为头节点时将头节点指向newnode。当不为头节点时创建prev使用while循环,当 prev->next=pos时退出,让prev的下个节点指向newnode,newnode的下个节点指向pos。

pos节点删除的实现

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    assert(*pphead);
    assert(pphead);
    assert(pos);

    if (pos == *pphead)
    {
        SLTNode* ret = *pphead;
        *pphead = (*pphead)->next;
        free(ret);
        ret = NULL;
        return;
    }

    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
        prev = prev->next;
    }
    SLTNode* node = pos->next;
    prev->next = node;
    free(pos);
    pos = NULL;
}

SLTFind函数寻找pos节点,判断两种情况,pos是否为头节点,然后进行删除操作。

在指定位置之后插入的实现

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
    assert(pos);
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    newnode->data = x;
    newnode->next = NULL;

    SLTNode* prve = pos->next;
    pos->next = newnode;
    newnode->next = prve;
}

这个更简单。

删除pos之后的节点的实现

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{

    assert(pos);
    SLTNode* prve = pos->next;
    SLTNode* node = prve;
    while (node)
    {
        node = prve->next;
        free(prve);
        prve = node;
    }
    pos->next = NULL;
}

找到pos后循环释放。

销毁链表的实现

void SListDesTroy(SLTNode** pphead)
{
    assert(*pphead);
    assert(pphead);
    SLTNode* prve = (*pphead);
    while(prve)
    {
        prve = prve->next;
        free(*pphead);
        *pphead = NULL;
        *pphead = prve;
    }
}

一样的循环释放。

下面是SListNode.c代码

#include "SListNode.h"

//打印
void SLTPrint(SLTNode* phead)
{
	while (phead)
	{
		printf("%d->", phead->data);
		phead = phead->next;
	}
	printf("NULL\n");
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	//不能直接这样用*pphead->next = NULL;加括号(*pphead)->next,  优先级问题
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	//链表不为空,找尾节点
	SLTNode* node = *pphead;
	while (node->next)
	{
		node = node->next;
	}
	node->next = newnode;
}


//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	if (*pphead == NULL)
	{
		*pphead = newnode;
		newnode->next = NULL;
	}
	else
	{
		SLTNode* ret = *pphead;
		newnode->next = ret;
		*pphead = newnode;
	}
}


//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* node = *pphead;
		SLTNode* ret = node->next;

		while (ret->next)
		{
			node = node->next;
			ret = ret->next;
		}
		free(ret);
		ret = NULL;
		node->next = NULL;
	}
}


//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* node = (*pphead)->next;
		free(*pphead);
		*pphead = node;
	}
}



//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* node = phead;
	while (node)
	{
		if (node->data == x)
		{
			return node;
		}
		node = node->next;
	}
	return NULL;
}

void SLTfind(SLTNode* phead, SLTDataType x)
{
	SLTNode* ret = SLTFind(phead, x);
	if (ret)
	{
		printf("找到了%d\n", ret->data);
	}
	else
	{
		printf("没找到%d\n", x);
	}
}


//指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(*pphead);
	assert(pphead);
	assert(pos);
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	if (pos == *pphead)
	{
		SLTNode* node = (*pphead);
		*pphead = newnode;
		(*pphead)->next = node;
		return;
	}

	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = newnode;
	newnode->next = pos;
}


//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTNode* ret = *pphead;
		*pphead = (*pphead)->next;
		free(ret);
		ret = NULL;
		return;
	}

	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	SLTNode* node = pos->next;
	prev->next = node;
	free(pos);
	pos = NULL;
}


//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;

	SLTNode* prve = pos->next;
	pos->next = newnode;
	newnode->next = prve;
}


//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{

	assert(pos);
	SLTNode* prve = pos->next;
	SLTNode* node = prve;
	while (node)
	{
		node = prve->next;
		free(prve);
		prve = node;
	}
	pos->next = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	SLTNode* prve = (*pphead);
	while(prve)
	{
		prve = prve->next;
		free(*pphead);
		*pphead = NULL;
		*pphead = prve;
	}
}

数据结构单链表插入、删除和修改实验报告 一、实验目的 1.理解数据结构中带头结点单链表的定义和逻辑图表示方法。 2.掌握单链表中结点结构的JAVA描述。 3.熟练掌握单链表的插入、删除和查询算法的设计与JAVA实现。 4.熟练掌握简单的演示菜单与人机交互设计方法。 二、实验内容 1. 编制一个演示单链表插入、删除、查找等操作的程序。 三、实验步骤 1.需求分析 本演示程序用JAVA编写,完成单链表的生成,任意位置的插入、删除,以及确定某一元素在单链表中的位置。 ① 输入的形式和输入值的范围:插入元素时需要输入插入的位置和元素的值;删除元素时输入删除元素的位置;查找操作时需要输入元素的值。在所有输入中,元素的值都是整数。 ② 输出的形式:在所有三种操作中都显示操作是否正确以及操作后单链表的内容。其中删除操作后显示删除的元素的值,查找操作后显示要查找元素的位置。   ③ 程序所能达到的功能完成单链表的生成(通过插入操作)、插入、删除、查找操作。 ④ 测试数据:  A. 插入操作中依次输入1112,13,14,15,16,生成一个单链表    B. 查找操作中依次输入12,15,22返回这3个元素在单链表中的位置    C. 删除操作中依次输入2,5,删除位于2和5的元素 2.概要设计 1)为了实现上述程序功能,需要定义单链表的抽象数据类型:   ADT LinkList {    数据对象:D={ai|ai∈IntegerSet,i=0,1,2,…,n,n≥0}    数据关系:R={|ai,ai+1 ∈D}    基本操作: (1)insert 初始化状态:单链表可以不为空集;操作结果:插入一个空的单链表L。   (2)decelt     操作结果:删除已有的单链表的某些结点。 (3)display     操作结果:将上述输入的元素进行排列显示。    (4)modify     操作结果:将上述输入的某些元素进行修改。    (5)save     操作结果:对上述所有元素进行保存。    (6)load     操作结果:对上述元素进行重新装载。   }   2)本程序包含7个函数:   ① 主函数main()   ② 保存单链表函数save()   ③ 重载操作菜单函数load()   ④ 显示单链表内容函数display ()   ⑤ 插入元素函数insert ()   ⑥ 删除元素函数decelt ()   ⑦ 修改元素函数modify()   各函数间关系如下: 3.详细设计   实现概要设计中定义的所有的数据类型,对每个操作给出伪码算法。对主程序和其他模块也都需要写出伪码算法。   1) 结点类型和指针类型   typedef struct node {    int data;    struct node *next;   }Node,*singleLIST.java;   2) 单链表的基本操作   为了方便,在单链表中设头结点,其data域没有意义。 bool insert(singleLIST) (伪码算法)   bool modify(singleLIST) (伪码算法)   void delect(singleLIST)   (伪码算法)   void display()   (伪码算法) 3) 其他模块伪码算法 4.调试分析   () 5.使用说明 程序名为 ,运行环境为Windows。程序执行后显示   ========================   0----EXIT   1----INSERT   2----DELETE   3----DISPLAY 4----MODIFY 5----EXIST =======================   SELECT:   在select后输入数字选择执行不同的功能。要求首先输入足够多的插入元素,才可以进行其他的操作。每执行一次功能,就会显示执行的结果(正确或错误)以及执行后单链表的内容。 选择5:退出程序   选择1:显示"INSERT =" ,   要求输入要插入的位置和元素的值(都是整数)。   选择2:显示"DELETE =" ,   要求输入要删除元素的位置,执行成功后返回元素的值。   选择3:显示"MODIFY = " , 选择要修改的对象,执行成功后返回新的元素值。 选择4:显示"DIAPLAY= "   显示所有单链表中的元素,自动进行排序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值