【零基础入门】C 语言单链表全攻略:节点创建→头尾插删→指定位置操作→链表销毁(附 test/SList 完整代码 + 注释详解)

28.单链表专题

文章目录




链表

每个节点的组成

  • 这个节点的数据
  • 下一个节点的指针

每个节点还有自己的指针(地址)

cpp 复制代码
//单链表的节点	
struct SListNode
	{
		int data;
		struct SListNode* next;
	};
//////////////////////////////////////////下面是升级版本
typedef int SLDataType;


//重命名
typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}STLNode;

test.c

  1. 创建节点指针
    1. malloc,强制类型转换
    2. 生成4个节点
    3. 第n个节点赋为n
  2. 链接节点
    1. 将每个节点的next等于下一个节点(指针,定义的时候本来就是指针)
    2. 最后一个节点的next为NULL
  3. 打印链表
    1. 传参传第一个节点

SList.c

1.链表打印SLTPrint

  1. 传入第一个节点的地址

  2. while循环,条件是用于不断指向下一个节点的指针还存在(不是NULL)

cpp 复制代码
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
    
	while (pcur)//pcur != NULL
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

2.申请新节点

  1. 返回值是节点指针类型(原因是使用malloc),参数是DataType x
  2. malloc,强制类型转换,
  3. 判断malloc是否成功
    1. 如果不成功,就perror+exit(1)
  4. 将DataType x赋值给新节点的data
  5. 新节点的next置为空NULL
  6. return 新节点
cpp 复制代码
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

3.尾插:SLTPushBack

3.1错误的想法:

如果是空链表,比较特殊,特殊处理

  1. phead不为空的情况

    1. 传参:

      1. 指向头节点的链表节点指针
      2. 想插入的内容(类型为链节)
    2. 将尾节点的next指向newnode

      1. 创建新节点函数
      2. 找到尾节点
        1. 定义一个ptail(尾巴的意思)
        2. 从头节点开始while循环,条件是ptail->next不为NULL,循环内容是ptail不断指向对应位置的next
      3. 将ptail的next指向新节点
  2. phead==NULL的情况

    直接将phead指向新创建的链节

3.2错误的原因:

当第一次尾插,也就是链表里还没有元素的时候,我想在尾差函数里将 头节点(这时候是空指针)改变成第一个节点 ,也就是改变头节点本身,这在函数中是不能实现的------因为我想实现的是改变一个参数的内容,传值调用无法实现

这有个问题就是最开始链表是空的然后这时候头节点plist = NULL,这时候我们无法对指针解引用,我们要做的就不是修改这个节点的next值了,而是对plist进行修改,所以这时候我们向尾插中传的参数应该是Plist的地址。👉 你要修改的是:plist 变量自己存的地址值!!!

3.3实参形参的对应

我们在创建节点的时候,创建的就是结构体指针plist,

如果想要改变里面的内容,得先找到这个节点,也就是先对plist解引用

3.4实现
  1. 传参:
    1. 考虑到列表为空的情况,有修改链节指针(上面错误的想法想传的参数)的可能,所以我们这时候传链节指针的地址
    2. 传想增加的链节类型
  2. 断言二级指针,(我们传的是二级指针,在链表为空的时候,我们想通过二级指针改变一级指针,所以至少一级指针存在------>二级指针能够解引用)
  3. 生成nownode
  4. 增加链节的两种情况:
    1. 链表为空:通过解引用二级指针来改变一级指针
    2. 链表非空:
      1. 创建指针,while循环找到尾节点
      2. 改变尾节点的next
cpp 复制代码
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//*pphead 就是指向第一个节点的指针
	//空链表和非空链表
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else 
	{
		//找尾
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		//ptail指向的就是尾结点
		ptail->next = newnode;
	}
}

4.头插

  1. 传参:
    1. 因为也可能涉及到链表为空的情况(需要修改头节点指针的内容),就需要传二级指针
    2. 想插入的链节
  2. 断言二级指针,(我们传的是二级指针,在链表为空的时候,我们想通过二级指针改变一级指针,所以至少一级指针存在------>二级指针能够解引用)
  3. 创造新节点
  4. 将新节点的next指向原来的头节点
  5. 将头节点变成新节点
cpp 复制代码
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	//newnode *pphead
	newnode->next = *pphead;
	*pphead = newnode;
}

5.尾删

  1. 传参
    1. 因为也可能涉及到链表只有一个链节,尾删后为空的情况(需要修改头节点指针的内容为NULL),就需要传二级指针
  2. 断言
    1. 传来的二级指针不为空
    2. 二级指针指向的一级指针(链表首个链节的地址)不为空,链表存在
  3. 尾删的两种情况
    1. 如果只有一个链节(二级指针解引用后的一级指针对应的链节的next 为空),free+置空
    2. 多个链节:
      1. 创造链接指针,while循环找到尾节点,(while循环条件是节点的下一个指针不为空),
      2. 将尾节点的指针free+置空(置空有一点点多此一举------ 它本来就是局部变量,函数一结束就自动销毁了,置不置空都不影响链表;)
      3. 将尾节点的上一个节点的next置空(我们必须改 prev->next = NULL,这是在解引用修改链表本身的结构,把最后一段链接断开,这才是关键!)
cpp 复制代码
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表不能为空
	assert(pphead && *pphead);
	//链表只有一个节点
	if ((*pphead)->next == NULL) //-> 优先级高于*///真是血泪教训
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		//链表有多个节点

		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;//有一点点 多此一举 
			ptail = ptail->next;//这才是关键!
		}
		//prev ptail
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

6.头删

  1. 传参*pphead(因为头删会改变使用malloc时得到的链节的地址,如果想在函数中改变一个地址,传参就要是二级指针)

  2. assert二级指针+二级指针解引用(原指针不能为空,因为想删除)

  3. 创建一个next,保留首链节的next,(类型是链节指针类型)

    注意优先级://-> 优先级高于*

  4. free首链节的指针

  5. 将首链节的指针改为next

    cpp 复制代码
    //头删
    void SLTPopFront(SLTNode** pphead)
    {
    	//链表不能为空
    	assert(pphead && *pphead);
    
    	SLTNode* next = (*pphead)->next; //-> 优先级高于*
    	free(*pphead);
    	*pphead = next;
    }

7.查找

  1. 返回值:看情况吧,老师写的是指针(,是为了给后面的指定位置插入函数服务,因为后面在指定位置之前插入数据对位置进行查找的时候,用的是节点的指针,如果这时候我们的返回值是节点的指针,到后面就可以直接使用find函数来确定有没有和要不要继续)

  2. 传参:

    1. 指针不传二级指针,只传一级指针,就是phead(因为不对一级指针进行修改)
    2. 传想查找的SLLDataType类型的x
  3. 新创建一个参数,pcur,更形象,不会影响phead

  4. while循环,条件是pcur不为空

    1. 比较pcur中的data和x是否相等

    2. 相等就返回pcur

      cpp 复制代码
      //查找
      SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
      {
      	assert(phead);
      	SLTNode* pcur = phead;
      	while (pcur)
      	{
      		if ( pcur->a== x  )
      			return pcur;
      		pcur = pcur->next;
      	}
      	return NULL;
      }

8.在指定位置之前插入数据

  1. 传参:
    1. 二级指针(如果在第一个节点之前插入数据,会改变头节点的指针,改变指针,用二级指针)
    2. 位置:传的是节点指针类型
    3. 想插入的数据:节点数据类型类型的
cpp 复制代码
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead&&pos);
	//首先是头插的情况
	if (pos == *pphead)
	{
		SLTPushFront( pphead, x);
	}

	else
	{
		SLTNode* ptail = (*pphead)->next;
		SLTNode* pre = (*pphead);

		while (ptail!= pos)
		{
			pre = ptail;
			ptail = ptail->next;
		} 
		SLTNode* pnewnode = SLTBuyNode(x);
		pnewnode->next = pre->next;
		pre->next = pnewnode; 
	}
}

9.删除对应节点

cpp 复制代码
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead&&pos);

	if (*pphead == pos)
	{
		SLTPopFront( pphead);
	}

	else
	{
		SLTNode* ptail = (*pphead)->next;
		SLTNode* pre = (*pphead);

		while (ptail != pos)
		{
			pre = ptail;
			ptail = ptail->next;
		}

		pre->next = ptail->next;
		free(ptail);
		ptail = NULL;
	}
}

10.在指定位置之后插入数据

cpp 复制代码
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode*pnewnode= SLTBuyNode( x);
	pnewnode->next = pos->next;
	pos->next = pnewnode;
}

11.删除pos之后的节点

cpp 复制代码
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);

	SLTNode* next = pos->next;
	pos->next = pos->next->next;

	free(next);
	next = NULL;
}

12.销毁链表

cpp 复制代码
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	SLTNode* pcur = *pphead;

	while (pcur)
	{
		SLTNode* pre = pcur;
		pcur = pcur->next;

		free(pre); 
	} 
	*pphead = NULL; 
}

总代码

test.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"reviseLLC.h"
int main()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 666);
	SLTPushBack(&plist, 666);
	SLTPushBack(&plist, 666);

	SLTPushFront(&plist, 888);
	SLTPushFront(&plist, 888);

	SLTPopBack(&plist);
	SLTPopBack(&plist);

	SLTPopFront(&plist);
	SLTPopFront(&plist);

	SLTInsert(&plist, SLTFind(plist,666), 6868);
	SLTInsert(&plist, SLTFind(plist,666), 123);
	SLTInsert(&plist, SLTFind(plist,6868), 0 );

	SLTErase(&plist, SLTFind(plist, 6868));
	SLTErase(&plist, SLTFind(plist, 0));

	SLTInsertAfter(SLTFind(plist,666), 888);
	SLTInsertAfter(SLTFind(plist, 123), 666);

	SLTEraseAfter(SLTFind(plist, 123));
	SLTEraseAfter(SLTFind(plist, 666));
	SLTPrint(plist);


	SListDesTroy(&plist);
	return 0;
}

SList.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"reviseLLC.h"

SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc newnode");
		exit(1);
	}
	newnode->a = x;
	newnode->next = NULL;

	return newnode;
}

void SLTPrint(SLTNode* phead)
{
	assert(phead);
	SLTNode* pcur = phead;

	while (pcur)
	{
		printf("%d ", pcur->a);
		pcur = pcur->next;
	}
	printf("\n");
}

//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	if (*pphead == NULL)
	{
		*pphead = SLTBuyNode(x);
	}

	else
	{
		SLTNode* pnewnode= SLTBuyNode(x);
		SLTNode* pcur = *pphead;

		while (pcur->next)
		{
			pcur = pcur->next;
		}

		pcur->next = pnewnode;
	}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* pnewnode = SLTBuyNode(x);
	pnewnode->next = *pphead;

	*pphead = pnewnode;
	
}
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	//找到最后一个元素的前一个元素,改其next
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL; 
	}

	else
	{
		SLTNode* ptail = (*pphead)->next; 
		SLTNode* pre = (*pphead) ; 

		while (ptail->next)
		{
			pre = ptail;
			ptail = ptail->next;
		}

		free(ptail);
		pre->next = NULL;
	}
}
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);

 
		SLTNode* tmp = (*pphead)->next;
		free(*pphead);
		*pphead = tmp;
	
}

////查找
//SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
//{
//	SLTNode* pcur = phead;
//	while (pcur)//等价于pcur != NULL
//	{
//		if (pcur->data == x)
//		{
//			return pcur;
//		}
//		pcur = pcur->next;
//	}
//	//pcur == NULL
//	return NULL;
//}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if ( pcur->a== x  )
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead&&pos);
	//首先是头插的情况
	if (pos == *pphead)
	{
		SLTPushFront( pphead, x);
	}

	else
	{
		SLTNode* ptail = (*pphead)->next;
		SLTNode* pre = (*pphead);

		while (ptail!= pos)
		{
			pre = ptail;
			ptail = ptail->next;
		} 
		SLTNode* pnewnode = SLTBuyNode(x);
		pnewnode->next = pre->next;
		pre->next = pnewnode; 
	}
}

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

	if (*pphead == pos)
	{
		SLTPopFront( pphead);
	}

	else
	{
		SLTNode* ptail = (*pphead)->next;
		SLTNode* pre = (*pphead);

		while (ptail != pos)
		{
			pre = ptail;
			ptail = ptail->next;
		}

		pre->next = ptail->next;
		free(ptail);
		ptail = NULL;
	}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode*pnewnode= SLTBuyNode( x);
	pnewnode->next = pos->next;
	pos->next = pnewnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);

	SLTNode* next = pos->next;
	pos->next = pos->next->next;

	free(next);
	next = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	SLTNode* pcur = *pphead;

	while (pcur)
	{
		SLTNode* pre = pcur;
		pcur = pcur->next;

		free(pre); 
	} 
	*pphead = NULL; 
}

SList.h

cpp 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>

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

SLTNode* SLTBuyNode(SLTDataType x);


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);
相关推荐
minji...2 小时前
Linux 网络套接字编程(二)从 0 到 1 实现 UDP 回声服务器,recvfrom,sendto
linux·运维·网络·单片机·udp
与遨游于天地2 小时前
HTTP的历史由来
网络·网络协议·http
斯维赤2 小时前
Python学习超简单第八弹:网络编程
网络·python·学习
不会写DN5 小时前
其实跨域问题是后端来解决的? CORS
服务器·网络·面试·go
Harvy_没救了5 小时前
【网络架构】Keepalived + LVS(DR) + MariaDB 双主备实践
网络·架构·lvs
大鹏说大话11 小时前
SSL证书自动化的未来:ACME协议与Let’s Encrypt实践
网络·安全
被摘下的星星11 小时前
网际协议(IP协议)
网络·tcp/ip
爱学习的小囧12 小时前
ESXi VMkernel 端口 MTU 最佳设置详解
运维·服务器·网络·php·虚拟化
TechubNews15 小时前
Base 发布首个独立 OP Stack 框架的网络升级 Azul,将是 L2 自主迭代的开端?
大数据·网络·人工智能·区块链·能源