无头+单向+非循环链表增删查改实现(c语言)

SList.c


#include"SList.h"
#include<malloc.h>
#include<assert.h>
#include<stdio.h>

//申请节点
SListNode* BuySListNode(SLDataType data)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (NULL == newNode)
	{
		assert(0);//调试宏,参数为0宏就会触发,非0不会触发
		//我们将其为零,意思就是如果为NULL时,会触发,提醒我们一下
		return NULL;//申请空间失败
	}
	//如果申请空间没有问题
	newNode->next = NULL;
	newNode->data = data;
	return newNode;
}

//尾插 链表中有节点
//过程:
//1.找到链表中的最后一个节点
//2.尾插新节点
//1.(1)链表中没有节点
//       head->NULL
//  (2)链表中有节点
//       head->1->2->3->4->NULL
//                      丨->5->NULL

//想要在函数中通过形参head来修改外部实参head指针的指向,一级指针函数做不到
//因为head是外部实参head的一份拷贝
//因此这里需要传递二级指针

//链表不存在:链表不存在即头指针head都没有定义
//链表为空:head--》NULL,表示链表中没有节点 
//尾插:
void SListPushBack(SListNode** head, SLDataType data)
{
	assert(head);//head必定不为NULL,这里检测的是程序的非法输入 

	//空链表
	SListNode* newNode = BuySListNode(data);//申请节点
	if (NULL == *head)
	{
		*head = newNode;
	}
	else
	{
		//1.找到链表中的最后一个节点
		//  问题?怎么才算是找到了最后一个节点?(next = NULL)
		SListNode* cur = *head;//head不为NULL
		while (cur->next)
		{
			//cur++;不能这样写,因为不是连续的空间 
			cur = cur->next;
		}
		//2.插入新节点
		cur->next = newNode;
	}
}
//1.参数检测
//2.程序逻辑
//尾删:每次要删除链表中的最后一个节点
//      1.找最后一个节点(即next为空的节点)
//      2.删除最后一个节点
void SListPopBack(SListNode** head)
{
	assert(head);//检测非法情况
	if (NULL == *head)
	{
		//空链表
		return;
	}
	else if (NULL == (*head)->next)
	{
		//链表中只有一个节点
		free(*head);
		*head = NULL;
	}
	else
	{
		//链表非空--链表中至少有一个节点
		SListNode* cur = *head;
		SListNode* prev = NULL;
		while (cur->next)
		{ 
			prev = cur;
			cur = cur->next;
		}
		prev->next = cur->next;
		//最后一个节点已经找到
		//删除该节点
		free(cur);
	}
}

//1.参数检测
//2.程序逻辑
void SListPushFront(SListNode** head, SLDataType data)//头插 
{
	assert(head);//保证链表在里面存在
	SListNode* newNode = BuySListNode(data);
	 
	newNode->next = *head;
	*head = newNode;

	//链表在插入时,不需要改变其他元素位置,效率特别高,时间复杂夫O(1)
	//1.空链表
	//if (NULL == *head)
	//{
	//	*head = newNode;
	//}
	//else
	//{
	//    //2.链表有多个节点
	//	newNode->next = *head;
	//	*head = newNode;
	//}
}

//head保存的是头指针的地址
//1.先标记  2.移动head  3.删除节点
void SListPopFornt(SListNode** head)//头删
{
	SListNode* delNode = NULL;
	assert(head);//断言链表存在

	if (NULL == *head)
		return;

	delNode = *head;
	*head = delNode->next;
	free(delNode);
}


void SListInsertAfter(SListNode* pos, SLDataType data)//任意位置插入 
//问题:任意位置的插入需要给定三个参数,此处只有两个参数?
//  答:兼容性,c++中会直接给一个这样的链表,为了和它能够兼容
//问题:给一个为什么插入的是后面呢?
//  答:我们不可能从链表中一个元素推断出这个元素的前面一个元素
//过程:1.让新元素的next指向下一个元素
//      2.让新元素插入的位置(元素)的next指向新元素
{
	SListNode* newNode = NULL;
	if (NULL == pos)
		return;
	
	newNode = BuySListNode(data);
	newNode->next = pos->next;
	pos->next = newNode;
}


//pos不能是最后一个节点
void SListEraseAfter(SListNode* pos)//任意位置删除  删除和上面插入同理
{
	SListNode* delNode = NULL;
	if (NULL == pos || NULL == pos->next)
		return;

	delNode = pos->next;
	pos->next = delNode->next;
	free(delNode);
}


//head指向的就是链表中第一个节点
//统计链表中有多少个节点
int SListSize(SListNode* head)
{
	//assert(head);不能用这个断言,因为此处的如果为空链表,空链表是合法情况

	//if (NULL == head)
	//	return 0;
	//当链表为空时,下面的while不运行,count也是0;重复可删除

	SListNode* cur = head;
	int count = 0;
	while (cur)
	{
		count++;
		cur = cur->next;
	}
	return count;

}

//判断链表是否为空
int SListEmpty(SListNode* head)
{
	return NULL == head;
}

//在链表list中寻找一下data这个元素,找到返回节点,没有找到返回空
SListNode* SListFind(SListNode* head, SLDataType data)
{
	SListNode* cur = head;
	while (cur)
	{
		if (cur->data == data)
			return cur;
		cur = cur->next;
	}
	return NULL;
}


//将链表中的有效节点销毁
//采用头删法将列表中一个一个的删除
void SListDestroy(SListNode** head)
{
	assert(head);

	while (*head)
	{
		SListNode* delNode = *head;
		*head = delNode->next;
		free(delNode);
	}
}




//打印一下列表
void PrintSList(SListNode* head)
{
	SListNode* cur = head;
	while (cur)
	{
		printf("%d--->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

void TestSList1()//将单链表做一下
{
	SListNode* listhead = NULL;//head是在外部,是实参

	SListPushBack(&listhead, 1);
	SListPushBack(&listhead, 2);
	SListPushBack(&listhead, 3);
	SListPushBack(&listhead, 4);
	SListPushBack(&listhead, 5);

	//SListPushBack(NULL, 5);非法调用,应该使用assert让程序停止下来,调试

	SListPopBack(&listhead);

	PrintSList(listhead);

	SListDestroy(&listhead);//链表测试完,需要将链表中所有元素删除,否则会造成元素丢失。
}

void TestSList2()
{
	SListNode* listhead = NULL;
	SListPushFront(&listhead, 1);
	SListPushFront(&listhead, 2);
	SListPushFront(&listhead, 3);
	SListPushFront(&listhead, 4);
	SListPushFront(&listhead, 5);

	PrintSList(listhead);

	SListDestroy(&listhead);

}

void TestList3()
{
	SListNode* listhead = NULL;

	SListPushBack(&listhead, 1);
	SListPushBack(&listhead, 2);
	SListPushBack(&listhead, 3);
	SListPushBack(&listhead, 4);
	SListPushBack(&listhead, 5);

	SListInsertAfter(SListFind(listhead, 3), 0);
	PrintSList(listhead);
	SListInsertAfter(SListFind(listhead, 100), 10);//没有这个位置,看插入情况
	PrintSList(listhead);

	SListEraseAfter(SListFind(listhead, 0));
	PrintSList(listhead);
	SListEraseAfter(SListFind(listhead, 5));

	PrintSList(listhead);
}

void TestSList()
{
	//TestSList1;
	//TestSList2();
	TestList3();

}

SList.h

//SList.h
#pragma once//防止头文件引用重复
typedef int SLDataType;

typedef struct SListNode
{
	//存储一个指向下一个元素的地址	SListNode
	struct SListNode* next; //指向下一个节点的地址
	SLDataType data;//存储该节点中的数据
}SListNode;


//注意:
//如果想要在函数中改变头指针的指向,形参必须为二级指针
//如果不需要在函数中改变头指针的指向,只需要传递一级指针即可
//单链表中我们需要什么操作
void SListPushBack(SListNode** head, SLDataType data);//尾插
//我们往SListNode* list里面插入SLDataType data

void SListPopBack(SListNode** head);//尾删

void SListPushFront(SListNode** head, SLDataType data);//头插 
void SListPopFornt(SListNode** head);//头删

void SListInsertAfter(SListNode* pos, SLDataType data);//任意位置插入 
//任意位置的插入需要给定三个参数,此处只有两个参数
void SListEraseAfter(SListNode* pos);//任意位置删除

//统计链表中有多少个节点
int SListSize(SListNode* head);

//判断链表是否为空
int SListEmpty(SListNode* head);

//在链表list中寻找一下data这个元素,找到返回节点,没有找到返回空
SListNode* SListFind(SListNode* head, SLDataType data);

//将链表中的有效节点销毁
void SListDestroy(SListNode** head);



void TestSList();//将单链表做一下

test.c
下面有一部分是课外延伸,需要代码请注意:题外延申以下不要借鉴
题外延申(做代码中,难免会用到一些知识点,我将其中有些难的写下来,方便你我查找资料)

#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h"
int  main()
{
	TestSList();
	return 0;
}

//题外延申:
//if 0
//。。。。。
//endif
//屏蔽代码

//一个程序从写完到可以执行起来,需要经过一下几个步骤:预处理--》编译--》汇编--》链接--》生成可执行程序
//程序链接出错
//错误中:无法解析的外部符号--(问题出自于)--》链接

//c语言中的传参方式
//值传递:形参(left,right)是实参(a,b)的一份拷贝,在函数中交换的是形参(即实参的拷贝)
//        形参与实参没有任何关联--》在函数中最形参进行修改不会影响外部的实参
//值传递
//void Swap(int left, int right)
//{
//	int temp = left;
//	left = right;
//	right = temp;
//}
//int  main()
//{
//	int a = 10;
//	int b = 20;
//
//	Swap(a,b);
//	printf("%d %d", a, b);
//	return 0;
//}

//传地址:pa,pb实际指向 的就是实参a,b
//        在函数中对形参指向空间中的内容进行修改,实际修改的就是实参本身
//void Swap(int* pa, int* pb)
//{
//	int temp = *pa;
//	*pa = *pb;
//	*pb = temp;
//}
//int  main()
//{
//	int a = 10;
//	int b = 20;
//
//	Swap(&a, &b);
//	printf("%d %d", a, b);
//	return 0;
//}
 
//在Swap函数中如果想要交换外部两个指针实参的指向,必须传递二级指针
//void Swap(int** pleft, int** pright)
//{
//	int* temp = *pleft;
//	*pleft = *pright;
//	*pright = *temp;
//
//}
//
//int  main()
//{
//	int a = 10;
//	int b = 20;
//	 
//	int* p1 = &a;
//	int* p2 = &b;
//
//	//通过Swap函数交换p1和p2两个指针的指向
//	Swap(&p1, &p2);
//
//	printf("%d %d", a, b);
//	return 0;
//}
//传参总结:
//传值:实参是形参的一份拷贝,不能通过形参改变外部的实参
//传地址:形参中就是实参的地址,可以通过对形参解引用拿到实参。可以通过修改形参来达到对实参的改变
//因此:如果想要通过形参来改变实参,必须传递实参的地址
//如果实参是普通类型的变量比如:int a ---》&a--》一级指针
//如果实参是指针类型--》int* pa = &a;在函数中想要修改实参pa的指向,必须要传递pa的地址,即int** 二级指针

//我们需要不带头的单链表,链表里面都是由一个一个节点构成的,我们需要
//将链表组织起来,我们只需要一个指针,这个指针指向链表中的第一个节点
//如果链表里面没有放节点,接下来我们需要放节点,刚开始我们需要
//Node*  head = NULL; 
//然后我们需要通过
//SListPushBack(head,1);
//1.申请新节点newNoad
//2.尾插新节点
//  (1)head为NULL:链表中没有节点,head指向newNode
//  (2)head不为NULL:链表中已经有节点
//       找到链表中的最后一个节点--》如果某个节点的next域指向空,则该节点为最后一个节点
//       将新节点链接到最后一个节点之后

//如果想要在函数中用过形参改变外部的实参,则必须传递实参的地址
//难点:在函数中想要通过形参改变外部实参指针的指向,则必须传递外部实参指针的地址

//不论是值传参还是指针传参,在传参过程中都会生成一份拷贝
//(如果是值,那么会生成一份值的拷贝,如果是指针,则会生成一份指针的拷贝,
//  如果在函数中改变函数的指向,他最终改变的是副本的指向)

//对于链表的基本操作中,哪些方法需要传递二级指针,哪些方法需要传递一级指针?
//本质:在函数体中是否需要通过形参指针改变外部实参指针的指向
//      需要:函数的参数必须为二级指针
//            比如:所有的插入,删除,销毁
//      不需要:函数的参数只需要传递一级指针
//              比如:查找,获取有效节点的个数

//参数检测
//有些情况下使用assert:该种问题如果发生,程序是一张错误--》除法的除数为零
//有些情况下使用if判断:该种问题如果发生,程序是一种正常的情况,--》空链表--》合法的链表,只不过链表中没有节点而已

//free(cur)
//注意:不是销毁cur本身,二十销毁cur指向的那个节点
//free(void* addr):释放addr表示的空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值