单链表整理

先看头文件 SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;  //链表是linklist,这里是singlelinklist,单链表

typedef struct SListNode   //这个太长了,typedef一下
{
	SLTDataType data;
	struct SListNode* next;   //存在一个指向自己结构的指针(可以认为指向一个同类)
}SLTNode;  //定义成STLNode,这样简单


//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x);
SLTNode* CreateSList(int n); //创建一个n个的链表 (注意:出了CreateSList这个函数的作用域,phead,ptail就销毁了,所以要返回phead)


//打印链表     注意:链表只需要一个头指针就可以了,而顺序表有指针还不行,还需要有size(记录存储多少个有效数据)和capacity(空间容量大小)
void SLTPrint(SLTNode* phead); //这里的phead和刚刚的phead不是一个phead,刚刚CreateSList函数的phead已经销毁了

void SLTPushBack(SLTNode** pphead, SLTDataType x);//链表尾插

void SLTPopBack(SLTNode** pphead);//链表尾删

void SLTPushFront(SLTNode** pphead,SLTDataType x);  //链表头插

void SLTPopFront(SLTNode** pphead);  //链表头删

SLTNode* SLTFind(SLTNode* phead, SLTDataType x);  //查找数据



void SLTInsertAfter(SLTNode* pos, SLTDataType x);//单链表在pos位置之后插入x
//分析思考为什么不在pos位置之前插入?

void SLTEraseAfter(SLTNode* pos);//单链表删除pos位置之后的值
//分析思考为什么不删除pos位置?

void SLTInsert(SLTNode** phead, SLTNode* pos, SLTDataType x);  //在pos之前插入x

void SLTErase(SLTNode** phead, SLTNode* pos);//删除pos位置

void SLTDestroy(SLTNode** pphead);  //销毁链表

下面是函数实现(SList.c)重点都在注释中,一行一行注释的 

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //在堆上申请空间
	if (newnode == NULL)
	{
		perror("malloc fail");//申请失败
		exit(-1);
	}

	newnode->data = x;  //把x的值存上
	newnode->next = NULL; //一个节点一开始最好初始化为空

	return newnode;  //返回这个指针
}

 

SLTNode* CreateSList(int n)  //创建一个n个的链表 (注意:出了CreateSList这个函数的作用域,phead,ptail就销毁了,所以要返回phead)
{
	SLTNode* phead = NULL;  //链表的第一个节点喜欢叫头节点,这个的目的是找到头
	SLTNode* ptail = NULL;  //这个的目的是用来找到尾部
	for (int i = 0; i < n; ++i)
	{
		SLTNode* newnode = BuySLTNode(i);   //创建一个节点,值为i
		if (phead == NULL)
		{
			ptail = phead = newnode;  //如果一开始是空的,那么把头和尾都指向newnode节点
		}
		else
		{
			ptail->next = newnode;  //此时的ptail的next指针存newnode的地址
			ptail = newnode;  //ptail放后面,指向的就是新节点了(这里phead是不变的,而ptail是一直变化的,指向最新的newnode节点)
		}
	}

	//ptail->next = NULL;
	return phead;

}

 

void SLTPrint(SLTNode* phead) //这里的phead和刚刚的phead不是一个phead,刚刚CreateSList函数的phead已经销毁了
{
	SLTNode* cur = phead; // 把phead的值给一个cur指针变量
	while (cur != NULL)
	{
		printf("[%d|%p]->", cur->data, cur->next); //打印cur中的数据和下一个的地址都打印出来
		cur = cur->next;  //cur指向下一个节点
	}
	printf("NULL\n");
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)   //尾插
{
	SLTNode* newnode = BuySLTNode(x); //创建一个节点(data为x,next为空)
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;  //给tail指针一个初值

		//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void SLTPopBack(SLTNode** pphead)  //尾删
{
	//暴力的检查
	assert(*pphead);

	if ((*pphead)->next == NULL) //如果这个链表只有一个元素
	{
		free(*pphead);        //释放这个指针指向的地址空间
		*pphead = NULL;       
	}
	else
	{
		SLTNode* tail = *pphead;   //跟上面一样,先给tail赋值头节点
		while (tail->next->next)   //一个一个往后面找,知道找到最后一个节点,其next为null
		{
			tail = tail->next;
		}
		
		free(tail->next);      //释放这个节点
		tail->next = NULL;
	}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)  //前插
{
	SLTNode* newnode = BuySLTNode(x);    //创建一个新节点,data为x
	newnode->next = *pphead;   //新节点的next指针指向之前的头节点
	*pphead = newnode;   //让新节点的指针成为头节点
}

 

void SLTPopFront(SLTNode** pphead)    //前删
{
	assert(*pphead);    //断言一下,头指针不能为空

	SLTNode* next = (*pphead)->next;  //保存一下头节点的下一个指针
	free(*pphead); //释放头节点
	//(pphead是一个二级指针,存放的是头节点的指针的地址,解引用之后是一个一级指针,指向的是头节点,而free函数括号里就是一个指针)
	*pphead = next;  //把刚刚保存的指针赋值给头节点
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)  //寻找节点,需要两个参数(头节点和查找的数据x)
{
	SLTNode* cur = phead;      //用cur指针来查找这个数据,一开始赋值头节点指针
	while (cur)         //当cur指针不为空时,一直循环
	{
		if (cur->data == x)    //如果找到这个数据了,返回cur指针
		{
			return cur;
		}

		cur = cur->next;    //把链表中指向的下一个节点的地址赋值给cur
	}
	return NULL;   //找不到返回空指针
}

 

void SLTInsertAfter(SLTNode* pos, SLTDataType x)   //在pos之后插入一个新节点
{
	assert(pos);    //断言pos不能为空指针
	SLTNode* newnode = BuySLTNode(x)  ;  //创建一个新节点
	newnode->next = pos->next;    //让新节点的next指向前一个数据原本指向的节点
	pos->next = newnode;      //让前一个节点指向新创建的节点newnode
}

 

//在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)  //链表找不到前面的节点,只能从头找,所以要头节点指针
{
	assert(pos);  //pos不能为空指针
	if (*pphead == pos)    //如果要插入的位置是首节点,相当于头插
	{ 
		SLTPushFront(pphead, x);  //直接调用头插函数
	}
	else
	{
		SLTNode* prev = *pphead; //保存一下头指针
		while (prev->next != pos)   //用循环找到pos指针,当next指向的的是pos节点时停止(链表找不到前面的节点,只能从头找)
		{
			prev = prev->next;   //从下一个节点开始找
		}

		SLTNode* newnode = BuySLTNode(x);   //找到位置后,就得创建一个新节点了
		prev->next = newnode;    //让pos节点前一个节点指向新创建的节点
		newnode->next = pos;   //让新节点指向pos节点
	}
}

 

void SLTEraseAfter(SLTNode* pos)  //删除pos节点后面的节点
{
	assert(pos);   //如果是空节点删个锤子
	if (pos->next == NULL)   //如果pos后面是空节点,那么不需要删除,直接返回即可
	{
		return;
	}
	else
	{
		SLTNode* nextNode = pos->next;  //这里保存一下pos节点的下一个节点(想删除的目标节点)的地址
		//pos->next = pos->next->next; 
		pos->next = nextNode->next;     //让pos节点的next指向目标节点后面的节点(跳过想要删除的节点)
		free(nextNode);   //释放这个目标节点
		//nextNode = NULL;
	}
}

 

//删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)    //需要一个头指针(为了找到pos前面的节点),和一个pos目标指针
{
	assert(pos);  //pos是空指针还删啥
	assert(*pphead);  //这是个空链表还删除什么

	if (pos == *pphead)   //如果pos是首结点,那么问题就变成了一个头删
	{
		SLTPopFront(pphead);  //调用头删函数
	}
	else
	{
		SLTNode* prev = *pphead;   //设prev指针用来寻找pos前面的节点,一开始给它首节点的地址
		while (prev->next != pos)  //如果节点指向pos节点(找到了),终止循环
		{
			prev = prev->next;    //指针指向下一个节点
		}

		prev->next = pos->next;   //让pos前面的节点指向pos后面的节点
		free(pos);   //释放pos结点

		//pos = NULL;
	}
}

 

void SLTDestroy(SLTNode** pphead)  //销毁链表
{
	SLTNode* cur = *pphead;   //把头节点的指针赋给cur指针
	while (cur)     
	{
		SLTNode* next = cur->next;  //在删除节点之前,先把这个节点next指向的下一个节点地址存下来
		free(cur);    //再释放
		cur = next;   //让cur指针指向刚刚保存的下一个节点地址
	}
	*pphead = NULL;  
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值