数据结构 —— 单链表的实现(附有源代码)

本文详细介绍了单链表的概念、优缺点,并通过C语言实现了单链表的创建、打印、尾插、头插、头删、尾删、查找、插入和删除等操作。特别强调了在函数参数传递中,使用二级指针确保链表结构的正确修改。此外,还讨论了项目编写时的注意事项,如采用文件分布式实现,及时测试和调试。
摘要由CSDN通过智能技术生成

数据结构 —— 单链表的实现(附有源代码)


在这里插入图片描述


在这里插入图片描述


每博一文案🔍🔍🔍

师傅说:很多感情的结束都并非突然,只是攒够了失望,然后离开的。
或许是争吵后平复下来的某一天,或许是散步路过曾经约会的公园,
也许最让人伤心的不是争吵。
而是亲眼见证曾经热情慢慢变淡,
从一开始的嘘寒问暖到后来的冷眼相待,从一开始的掏心掏肺到后来
的警醒硕放,曾经深爱过的那个人,最后却变成了这样,但伤心吗?或者
歇斯底里真到了临别之际,也许你只顺平静。
你,你,你,你,你,你真正的失望都是悄无声息的,
每次伤心一次就心上留下一道伤痕,起初每次都会很痛。
然而随着伤痕的积累,你的心慢慢变得麻木,变得寒冷麻木,
到即便离开,他也不会再疼无声得告别,都是因为绝望吧?
因为付出了太多,所以不会有后悔,
因为付出了太多,所以走得洒脱。
曾经为爱该做的都已经做了,就算最后一拍两散,那也是值得,
毕竟人生也没有几次愿意这般付出得无怨无悔,在每一段付出
真心的感情里,人人都是弱者。
每时忘一次,就会套上一层铠甲,到最后铠甲后,你变强了,而曾经相爱
过的两个人也再也无法拥抱了。
                              ————————————   一禅心里庙语      

顺序表的问题:🔍🔍🔍

  • 在顺序表中无论是中间/ 头部的插入,其时间的复杂度都是为 O(N)的
  • 增容需要申请新的空间,拷贝数据,释放旧空间。会有不小的消耗,影响程序的执行速度。
  • 在增容方面上一般是2倍或者是1.5倍,这样是为了,防止你在不断的开辟动态内存空间,大大消耗程序的效率
  • 但是,这样做也同样存在一个巨大的弊端:浪费空间 ,例如:当前容量为 100 ,满了以后增容200 ,可我们需要插入的数据却只是需要 5 个而已,那么就会浪费 95 个数据空间。

单链表的概念🔍🔍🔍

  • 就是基于顺序表的缺点,我们就有了:单链表的存在
  • 概念:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针(地址)链接次序实现的。
  • 如下图:单链表:逻辑结构示图: 😊😊😊

在这里插入图片描述


  • 单链表:物理结构图 🔍🔍🔍

在这里插入图片描述


  • 我们不难发现,我们的物理结构,表示的更加,清晰,简单,明了。😍😍😍

写项目的注意事项:🔍🔍🔍

  • 使用文件分布的方式来写项目,这样项目内容分类明显,代码的可读性,可维护性,大大提高
  • 每实现一个对应的功能就,运行,测试,看看是否存在错误,这样错误出现的范围就大大减小了,容易排查,修正,便于调试代码。
  • 不要写了一大部分才运行测试,结果:出现一堆 BUG ,看得人都眼花缭乱,容易让你放弃
  • 当然,如果出现一堆的 BUG ,也不要慌,一步一步的,监视,调试,改正代码,就可以了,总之就是不要,放弃😍😍😍

单链表的实现:💕💕💕💕💕💕💕💕💕💕💕💕💕💕💕

单链表的定义:🔍🔍🔍

  • 这里我们使用 关键字 typedef 对单链表存放的数据,进行别名制 :方便后面的维护,修改,比如:修改数据类型为 :double
typedef int STLDataType; 
  • 创建自定义结构体:
  • 这里存在一个注意事项:
  • 如下:🔍🔍🔍
  • 不可以结构体中嵌套了自身的结构体: 这里你想想看,我们如何计算该结构体的大小:**结构体中有结构体,再再结构体中:又有结构体,这样无限循环下去,这不就是无限循环了吗?其大小你能计算出来吗?,不能是吧?
struct SListNode
{
	STLDataType data;
	struct SListNode next;   // 结构体中嵌套了结构体,
};  
  • 结果:🔍🔍🔍

在这里插入图片描述

  • 所以:我们要使用指针,代表该类型的指针,其指针的大小:64位 8个字节,32 位 4个字节空间的大小

struct SListNode
{
	STLDataType data;          // 单链表的数值
	struct SListNode* next;    // 单链表存放下一个节点的指针
};

typedef struct SListNode SLTNode;  // 该单链表结构体的别名


单链表的打印:🔍🔍🔍

  • 通过结构体的引用,打印该结构体中的数值:
  • 从下图(单链表物理示图),我们可以发现,当 指针(地址)=NULL ,后面就没有数值值了,所以我们打印的结束条件可以为 NULL
    在这里插入图片描述
// 打印单链表的数值
void SListprint(SLTNode* phead)
{
	SLTNode* cur = phead;     // 这里我们使用 cur 变量来代替 phead头的访问
	                          // 尽量不要移动头指针
	while (cur != NULL)
	{
		printf("%d->", cur->data);
        cur = cur->data;      // 移动指针
	}
	printf("NULL\n");
}

单链表的尾插法🔍🔍🔍

  • 首先我们需要生成插入的节点;使用关键字 malloc
// 生成插入的节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = num;
	newnode->next = NULL;


  • 没有一个节点时的插入
  • 存在多个节点的插入:
    1. 找到尾节点: 通过遍历 找到尾节点**(NULL)**
    2. 找到了尾节点,进行地址的交互,从而实现插入

tail = tail->next; // 移动指针

在这里插入图片描述



在这里插入图片描述


  • 注意了 🔍🔍🔍 下面这段代码是有问题的
  • 我们调用看看,再打印看看 💕💕💕

// 单链表的尾插法
void SListPushBack(SLTNode* phead, STLDataType num)
{
	// 生成插入的节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = num;
	newnode->next = NULL;

	if (phead == NULL) // 没有节点时插入
	{
		phead = newnode;
	}
	else
	{
		SLTNode* tail = phead;
		while (tail->next != NULL)  // 或者 (tail->next)
		{
			tail = tail->next;   // 移动指针;
		}
		// 当跳出循环,表示找到了,尾节点
		tail = newnode->next;
	}
}

int main()
{
	SLTNode* plist = NULL;
	SListprint(plist);
	SListPushBack(plist, 99);
	SListprint(plist);

	return 0;
}
  • 注意:结果:🔍🔍🔍
  • 我们发现结果:并没有把99插入进单链表当中去.

在这里插入图片描述

  • **其原因是:**🔍🔍🔍
  • 注意:**形参和实参的关系:**🔍🔍🔍

  • 我们的形参的改变并不会,影响到实参的改变

    可以理解为,形参是实参的一份临时打拷贝,出了

    对应的作用域,就会被释放,掉空间的

  • 而我们这里传的是实参,接受的是形参,所以形参的赋值的改变

    ,不会影响这里的实参的改变,就出现了,现在的结果:尾插失败

  • ==解决方法:==🔍🔍🔍`

    • 首先,遇到问题不要慌,这个问题很好解决的:😊😊😊
    • 我们不要传实参了,我们使用传地址 的方式就可以,就可以很好的解决这个问题了

    • 注意: 因为我们传的是地址,所以要用指针,来接受这个地址,而请注意了一个关键点:

      这里的地址是一级指针的地址 ,所以我们需要使用二级指针 接受这个一级指针的地址。

  • 实现方法如下:😍😍😍


// 单链表的尾插法
void SListPushBack(SLTNode** pphead, STLDataType num)   
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = num;
	newnode->next = NULL;
	if (*pphead == NULL)   // 注意是地址为 NULL,不是解引用
	{
		*pphead = newnode;
	}
	else
	{
		// 找到尾节点,从而实现尾插入
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next; // 移动指针
		}
		// 跳出循环,找到了,尾节点
		tail->next = newnode;
	}
}

  • 测试结果:😊😍👍

在这里插入图片描述


单链表的头插法🔍🔍🔍

对创建节点的封装 🔍🔍🔍
  • 这里我们为了方便多次,创建节点,我们把创建节点写成内部函数使用 static 关键字,进行封装函数,对于 关键字 static 还不了解的朋友可以移步到这 🔜🔜🔜:一点都不安静的 ——static_
  • 注意: 这个函数的返回类型是:SLTNode* ,因为我们要返回的是一个单链表的节点的地址,地址吗!😊😊😊,自然需要,指针对吧,而且需要相对应的指针类型,这里是单链表的类型的指针
  • 这里我们使用 操作符 sizeof() ,来计算该数据结构的大小,提高代码的兼容性,应用于不同位数的PC,想要详细了解的朋友可以移步到 🔜🔜🔜: 整形在内存中的存储的方式
  • 实现如下:😍😍😍
// 创建节点
static SLTNode* BuySListNode(STLDataType num)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = num;
	newnode->next = NULL;
	return newnode;
}

  • 头插法的物理图示: 😍😍😍

在这里插入图片描述

// 单链表的头插法
void SListPushFront(SLTNode** pphead, STLDataType num)  
{
	// 创建节点
	SLTNode* newnode = BuySListNode(num);
	newnode->next = *pphead;
	*pphead = newnode;
}
newnode->next = *pphead;
*pphead = newnode;
  • 注意:这段代码不可以交换的,

  • 一旦交换了,你就会丢失整个单链表的

    *pphead = newnode;
    newnode->next = *pphead;
    
  • 看交换后,我们分析这段代码:

  • 我们会发现,第一条语句,*pphead 这个原先指向整个单链表的地址,被 newnode 地址覆盖了,变成了newnode 的地址了,而我们原先的指向整个单链表的 的地址,没了,没了,意味着什么?—— 意味着,我们的单链表丢失了。

  • 所以,千万注意,不要把链表丢失了


  • 实现效果:

在这里插入图片描述


单链表的头删法🔍🔍🔍

  • 注意:我们不可以一开始就把头节点释放掉了
  • 一旦,一开始就释放空间了,我们的整个单链表就丢失了
  • 所以我们需要先把头节点,临时保存起来,后,再释放
  • 单链表的头删法的示图:

在这里插入图片描述


  • 代码实现:

// 单链表的头删法
void SListPopFront(SLTNode** pphead)
{
	SLTNode* next = (*pphead)->next; // 头节点的下一个节点的地址,
	// 这里注意一点,就是*操作符和-> 结构体的引用的操作符的优先级一样,所以,带上括号
	free(*pphead); // 释放空间
	*pphead = next; // 重新指向,新的地址
}

  • **结果:**💕💕💕

在这里插入图片描述


单链表的尾删法🔍🔍🔍

  • 单链表的尾删法:存在三种情况:
  • 当单链表为空节点的时候
    • 直接返回,不用删除了,已经是空的了
  • 当单链表为一个节点的时候
    • 直接释放空间,置为空(NULL),
  • 当单链表存在多个节点的时候
    • 我们就要找到了两个节点的位置:一个是尾节点,另一个是倒数第二个节点的位置
    • 我们需要把尾节点的空间释放掉,把倒数第二个节点的位置的next 置为 NULL ,因为后面没有数据了
  • [x]
  • **单链表的尾删法的图示:**💕💕💕

在这里插入图片描述

  • **找到倒数第二个节点的位置的方法:**😍😍😍
  • 我们可以使用一前一后两个指针 的方式:
        SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next; // 移动指针
		}
/*当tail->next不等于 NULL的时候,
我们把我们把tail的地址赋值给我们的后指针(prev) ,
一直这样一前一后,直到 tail->next == NULL
我们就可以发现,尾节点的位置,和倒数第二个节点的位置我们也找到了*/

  • **单链表的尾删法的代码实现:**😍😍😍

// 单链表的尾删法
void SListPopBack(SLTNode** pphead)
{
	/*1.空节点
	  2.一个节点
	  3.多个节点*/
	if (*pphead == NULL)  // 空节点的情况
	{
		return;
	}
	else if ((*pphead)->next == NULL)  // 一个节点的时候
	{
		free(*pphead);
		*pphead = NULL;
	}
	else             // 多个节点的时候
	{
		SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next; // 移动指针
		}
		/*跳出循环,找到了尾节点,和倒数第二个节点的位置,开始操作交互*/
		free(tail);
		tail = NULL;
		prev->next = NULL;

	}
}

  • **结果:**😊😊😊

在这里插入图片描述


单链表的查找🔍🔍🔍

  • 直接遍历一遍单链表,看看是否有对应数值,有的话返回该数值的地址

  • **实现代码:**😊😊😊



// 单链表的查找
SLTNode* SListFind(SLTNode* phead, STLDataType num)
{
	while (phead != NULL)
	{
		if (phead->data == num)
		{
			return phead;   // 返回该数值的地址
		}
		phead = phead->next;  // 移动指针

	}
	
	return NULL; // 不存在该数值,返回空;
}

单链表的中 pos (数值)位置前的插入数值🔍🔍🔍

  • 首先我们需要遍历整个链表,查看是否有该数值,如果有,我们就进行插入,没有,是无法插入的
  • **单链表的 pos (数值)位置前的插入数值的示图:**😊😊😊

在这里插入图片描述


  • 同样关键点是找到,该数值的前一个数值的节点的位置,同样我们也是可以用,一前一后 的指针的
  • 但是当插入的是头节点的时候,我们的前后指针,就没有它前一个节点了,这里我们就需要特殊处理了
  • 特殊处理:当单链表为空的时候就不行了,我们可以复用 前面我们写过的头插法
  • **代码实现如下:**😊😊😊

// 单链表 pow 位置的插入法
void SListInsert(SLTNode** pphead, STLDataType x, STLDataType num)
{
	// 首先判断是否该数值是否存在
	SLTNode* pos = (SListFind(*pphead, x));
	if (pos) // 存在不为空(NULL),插入
	{
		if (pos == *pphead)  // 插入位置,是头节点
		{
			SListPushFront(pphead, num); // 复用前面的头插法
		}
		else         // 其他位置
		{
			SLTNode* newnode = BuySListNode(num);  // 生成该插入数值的节点空间
			SLTNode* prev = *pphead;  // 代替头指针,遍历
			while (prev->next != pos)
			{
				prev = prev->next; // 移动指针
			}

			prev->next = newnode;
			newnode->next = pos;

		}
	}

}

  • **其结果如下:**💕💕💕

在这里插入图片描述


单链表的中 pos(数值的)的删除🔍🔍🔍

  • 同样我们在删除之前,也要判断一下该数值是否存在,存在才可以,删除的
  • 原理是和单链表的 pos (数值)位置前的插入数值是一样的,
  • 关键点: 就是找到该数值的 前节点的位置,从而才可以实现删除操作的
  • 单链表的中 pos(数值的)的删除 的图示:如下😊😊😊

在这里插入图片描述


  • 同样当删除的是头节点的时候,我们的前后指针,就没有它前一个节点了
  • 但是我们同样可以,复用 ,前面我们实现过的头删法实现
  • **具体代码实现如下:**💕💕💕


// 删除数值为 num 的节点
void SListErase(SLTNode** pphead, STLDataType num)
{
	SLTNode* pos = SListFind(*pphead, num);
	if (pos)  // 判断数值是否存在
	{
		if (pos == *pphead)   // 判断该数值是否是头节点
		{
			SListPopFront(pphead);  // 复用头删法
		}
		else          // 其他情况
		{
			SLTNode* prev = *pphead;
			while (prev->next != pos)
			{
				prev = prev->next; // 移动指针
			}

			// 找了,该数值的前一个节点的位置,删除操作
			prev->next = pos->next;
			free(pos);  // 释放空间
			pos = NULL;  // 手动置为(空)NULL
		}
	}
}

  • 其结果: 😊😊😊

在这里插入图片描述


完整的单链表 (文件分布式实现)🔍🔍🔍

  • 先查看整体脉络:

    • SList.h 文件放的是各种需要的头文件,以及函数声明,自定义结构体,typedef 关键字的别名

    • Test.c 文件放的是 main 函数,各种测试操作

    • SLIst.c 文件放的是 关于 单链表 功能实现的有关的函数

  • 😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


在这里插入图片描述

SList.h 的代码:🔍🔍🔍

  • SList.h 文件放的是各种需要的头文件,以及函数声明,自定义结构体,typedef 关键字的别名
  • 😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


#pragma once

#include<stdio.h>
#include<stdlib.h>

typedef int STLDataType;          // 类型的的别名的使用,方便后面的维护,修改,比如但我们把数据改为 flost的类型的时候
 
struct SListNode                  // 创建单链表的结构体
{
	STLDataType data;             // 单链表的数值 
	struct SListNode* next;       // 单链表的节点,注意:这里是指针地址的引用
};

typedef struct SListNode SLTNode; // 重新命名这个结构体的 别名。


///*注意下面这个是和上面那个的不同*/
//struct SListNode
//{
//	SLDataType data;
//	struct SListNode next; 
//	/*注意不可以:这样写的,这个代表的是:
//	结构体中套娃,嵌套了一个结构体,结构体中又嵌套了一个
//	结构体,这样无限嵌套结构体下去,就没有头了,你就不知道其大小了,sizeof()
//	*/
//};

// 注意:声明使用上 extern 关键字,代码好风格;
extern void SListPrint(SLTNode* phend);                        // 打印单链表
extern void SListPushBack(SLTNode** pphead, STLDataType num);  // 单链表的尾插法
extern void SListPushFront(SLTNode** pphead, STLDataType num); // 单链表的头插法
extern void SListPopFront(SLTNode** pphead);                   // 单链表的头删法
extern void SListPopBack(SLTNode** pphead);                    // 单链表的尾删法;
extern void SListInsert(SLTNode** pphead, STLDataType x, STLDataType num); // 单链表中数值为x的节点后插入 num数值
extern void SListErase(SLTNode** pphead, STLDataType num);     // 删除单链表中数值为 num 的节点
extern void SListErase_2(SLTNode** pphead, STLDataType num);   // 删除单链表中数值为 num 的节点
extern void SListSeek(SLTNode* phead, STLDataType num);        // 查找单链表中是否存在该数值的节点

😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


Test.h 的代码:🔍🔍🔍

  • 文件放的是 main 函数,各种测试操作
  • 😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


#define  _CRT_SECURE_NO_WARNINGS  1

#include"SList.h"

void test1()
{
	SLTNode* plist = NULL;         // 创建头节点
	SListPushBack(&plist, 0);      // 单链表的尾插法
	SListPushBack(&plist, 1);      // 单链表的尾插法
	SListPushBack(&plist, 2);      // 单链表的尾插法
	SListPrint(plist);             // 单链表的打印
	SListPushFront(&plist, 10);    // 单链表单位头插法
	SListPushFront(&plist, 100);   // 单链表单位头插法
	SListPushFront(&plist, 1000);  // 单链表单位头插法
	SListPrint(plist);             // 单链表的打印
}

void test2()
{
	SLTNode* plist = NULL;         // 创建头节点
	SListPushBack(&plist, 0);      // 单链表的尾插法
	SListPushBack(&plist, 1);      // 单链表的尾插法
	SListPushBack(&plist, 2);      // 单链表的尾插法
	SListPopFront(&plist);         // 单链表单位头插法
	SListPrint(plist);             // 单链表的打印
	SListPushFront(&plist, 10);    // 单链表的头插法
	SListPushFront(&plist, 100);   // 单链表的头插法;
	SListPushFront(&plist, 1000);  // 单链表的头插法;
	SListPopFront(&plist);         // 单链表的头删法;
	SListPopFront(&plist);         // 单链表的头删法
	SListPopFront(&plist);         // 单链表的头删法
	SListPopFront(&plist);         // 单链表的头删法
	SListPopFront(&plist);         // 单链表的头删法

	SListPrint(plist);             // 单链表的打印
}

void test3()
{
	SLTNode* plist = NULL;         // 创建头节点
	SListPushBack(&plist, 0);      // 单链表的尾插法
	SListPushBack(&plist, 1);      // 单链表的尾插法
	SListPushBack(&plist, 2);      // 单链表的尾插法
	SListPrint(plist);             // 单链表的打印   
	SListPopBack(&plist);          // 单链表的尾删法
	SListPrint(plist);             // 单链表的打印   

}

void test4()
{
	SLTNode* plist = NULL;         // 创建头节点
	SListPushBack(&plist, 0);      // 单链表的尾插法
	SListPushBack(&plist, 1);      // 单链表的尾插法
	SListPushBack(&plist, 2);      // 单链表的尾插法
	SListPrint(plist);             // 单链表的打印 
	SListInsert(&plist, 0, 99);    // 单链表中在pos数值前插入数值节点
	SListInsert(&plist, 1, 10);    // 单链表中在pos数值前插入数值节点
	SListInsert(&plist, 2, 99);    // 单链表中在pos数值前插入数值节点
	SListInsert(&plist, 3,99);     // 单链表中在pos数值前插入数值节点
	SListPrint(plist);             // 单链表的打印 
	printf("********************\n");
	SListErase_2(&plist, 0);       // 单链表中删除pos数值的节点
	SListErase_2(&plist, 2);       // 单链表中删除pos数值的节点
	SListErase_2(&plist, 10);      // 单链表中删除pos数值的节点
	SListPrint(plist);             // 单链表的打印 
	SListPrint(plist);             // 单链表的打印 
	SListSeek(plist, 0);           // 单链表中删除pos数值的节点
	SListSeek(plist, 1);           // 单链表中删除pos数值的节点

}

int main()
{
	test1();
	test2();
	test3();
	printf("*********************\n");
	test4();
	return 0;
}

😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


SList.c 的代码:🔍🔍🔍

  • SLIst.c 文件放的是 关于 单链表 功能实现的有关的函数
  • 😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


#define  _CRT_SECURE_NO_WARNINGS  1

#include"SList.h"  // 注意:这是自定义的头文件,与书名号括起来的的头文件,
                   // 的查询方式是不一样的,


// 开辟节点空间
static SLTNode* BuySListNode(STLDataType num)  // staitc 修饰的方法只能在该文件中使用,
{                                              // 实质上是改变了其方法的作用域
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = num; 
	newnode->next = NULL;

	return newnode;
}


static SLTNode* SListFind(SLTNode* phead, STLDataType num)
{
	while (phead != NULL)              //地址不为空,存在节点
	{
		if (phead->data == num)
		{
			return phead;              // 找到返回地址
		}
		phead = phead->next;           // 移动指针;

	}

	return NULL;                       // 不存在,返回空(NULL)

}


void SListPrint(SLTNode* phend)     // 打印单链表的数据
{
	SLTNode* cur = phend;           // 用 cur 指针来代替头指针的访问,尽量不要移动头指针
	while (cur != NULL)             // 当节点的为 NULL的时候,就表示后面没有数据了
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n"); 
}


/*这里注意:形参和实参的传值的,改变传递地址,而只是一级指针的
的地址,所以注意它,接受的是二级指针,保存一级指针的地址,*/
void SListPushBack(SLTNode** pphead, STLDataType num)    // 单链表的尾插法
{
	SLTNode* newnode = BuySListNode(num);
	
	if (*pphead == NULL)         // 当插入的是第一个节点的时候,我们直接,用头指针指向该的位置地址
	{
		*pphead = newnode;
	}
	else                         // 当插入的不是第一个节点的时候,我们找到最后一个节点NULL
	{
		SLTNode* tail = *pphead;    // 同样使用一个变量,来代替头指针的访问,我们尽量不要头指针
		while (tail->next != NULL)  // 找到最后一个节点的位置 
		{
			tail = tail->next;      // 移动指针;
		}
		// 当跳出循环,找到了,插入
		tail->next = newnode;
	}
}


void SListPushFront(SLTNode** pphead, STLDataType num)  // 单链表的头插法
{
	SLTNode* newnode = BuySListNode(num);               // 创建节点空间
	newnode->next = *pphead;                            // 交互
	(*pphead) = newnode;
}


void SListPopFront(SLTNode** pphead)    // 单链表的头删法
{
	SLTNode* next = (*pphead)->next;    // 注意:不可以率先释放头指针的空间的,不然,你就丢失
	                                    // 个整个链表了。
	free(*pphead);                      // 释放空间,
	*pphead = next;
}


void SListPopBack(SLTNode** pphead)     // 单链表的尾删法
{
	/*1. 就是空的时候
	  2. 只有一个节点
	  3. 多个节点,主要就是要找到倒数第二个节点的位置*/

	if (*pphead == NULL)
	{
		return;
	}
	else if( (*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{ // 前后指针,主要就是为了,定位到倒数第二个节点的位置
		SLTNode* prev = NULL;
		SLTNode* tail = (*pphead);

		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next; // 移动指针

		}
		// 跳出循环,找到了,最后一个节点
		free(tail); 
		tail = NULL;
		// 把倒数第二个的节点置为 NULL;
		prev->next = NULL;
	}

}


// 单链表 pow 位置的插入法
void SListInsert(SLTNode** pphead,STLDataType x, STLDataType num)
{
	SLTNode* pos = SListFind(*pphead, x);      // 判断找寻该数值的节点,
	if (pos)                       // 该数值的节点存在,在实行插入操作
	{
		if (pos == *pphead)             // 插入位置位于头部,使用复用,头插法
		{
			SListPushFront(pphead, num);   // 复用,头插法
		}
		else
		{    // 主要:找到该数值的,前一个节点的位置,进行交互
			SLTNode* newnode = BuySListNode(num);    // 创建节点
			SLTNode* prev = *pphead;        
			while (prev->next != pos)
			{
				prev = prev->next;                // 移动指针;
			}

			prev->next = newnode;
			newnode->next = pos;
		}
	}
	
}


void SListErase(SLTNode** pphead, STLDataType num)     // 删除数值为 num的节点
{
	SLTNode* pos = SListFind(*pphead, num);            // 找寻是否存在该数值的节点
	if (pos)                 // 存在才,进行删除操作
	{
		if (pos == *pphead)
		{
			// 头删法 复用
			SListPopFront(pphead); // 注意这里的参数是二级指针
		}
		else
		{
			// 前后指针 ,主要是为了;找到该数值的前面的节点,后进行交互
			SLTNode* prev = *pphead;
			SLTNode* tail = *pphead;
			while (tail != pos)
			{
				prev = tail;
				tail = tail->next;
			}
			prev->next = tail->next;
			free(pos);  // 释放空间
			tail = NULL;
			pos = NULL;  // 手动置为空(NULL)
		}
	}
}



 // 单链表中删除该数值位置的节点的 第二种方法,其中不同的就是找该数值,前的节点的位置的方法不同,而已 
void SListErase_2(SLTNode** pphead, STLDataType num)
{
	SLTNode* pos = SListFind(*pphead, num);      // 同样判断该数值的位置是否存在
	if (pos)           // 存在才,执行操作,
	{
		if (pos == *pphead)    // 位于头节点 ,复用
		{
			SListPopFront(pphead);  // 复用,头删法
		}
		else  // 找到该数值的前个位置的节点
		{
			SLTNode* prev = *pphead;
			while (prev->next != pos)
			{
				prev = prev->next;
			}
			
			prev ->next = pos->next;
			free(pos);  // 释放空间
			pos = NULL; // 手动置为空
			prev = NULL;

		}
	}
}

 
void SListSeek(SLTNode* phead, STLDataType num)   // 查找该数值是否存在
{
	SLTNode* tail = SListFind(phead, num);   // 判断数值节点是否存在,
	if (tail == NULL)
	{
		printf("该数值的节点不存在!\n");
	}
	else
	{
		printf("%d:该数值的节点存在\n",tail->data);
	}
}

😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊😊


最后:😊😊😊

限于自身的水平,其中存在的错误,希望大家,给予指教,韩信点兵——多多益善 ,谢谢大家!后会有期,江湖再见!😊😊😊


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值