Bootstrap

单链表(C语言版本)

前提

  1. 不探讨头结点
  2. 空链表可以插入和查找,不可删除
  3. 一般不选择phead移动,定义一个新结点把phead赋给他,移动新结点即可
  4. 单链表不适合在前面和后面插入或删除,适合在后面插入删除

头插

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

在这里插入图片描述

尾插

//给新结点开辟空间并传入值
SLTNode* BuySLTNode(SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}
//不为空的链表的尾插
//尾插的本质是尾结点中要存放新的尾结点的地址

//void SLPushBack(SLTNode* phead, SLTDataType x)
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	给新结点开辟空间
	//SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//if (newnode == NULL)
	//{
	//	perror("malloc fail");
	//}
	//newnode->data = x;
	//newnode->next = NULL;

	SLTNode* newnode = BuySLTNode(x);

	if (*pphead == NULL)
	{
		*pphead= newnode;
	}
	else
	{
		//找尾
		//此时不需要用二级指针
		//要改变的是结构体,与前不同,前面要改变的是结构体的指针
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

在这里插入图片描述

尾删

  1. 要判断链表是否为空
  2. 注意剩一个结点和多个结点的删除方法不同
  3. 要找到前一个结点的指针
//第一种方法
void SLPopBack1(SLTNode** pphead)
{
	//链表本身就为空的情况
	// 方法一:
	/*if (*pphead == NULL)
		return;*/
	//方法二:
	
	//顺序反则没有意义
	assert(pphead);
	assert(*pphead);
	
	//注意优先级
	//一个结点
	if ((*pphead)->next == NULL)
	{
		//这里说明,尾删不能用一级指针
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else
	{
		SLTNode* tail = *pphead;
		//定义prev指针指向最后一个结点的前一个结点
		SLTNode* prev = NULL;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		//不加一个结点的判断,在这里会出问题,不进入循环
		prev->next = NULL;
	}
}
//第二种方法
void SLPopBack2(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else
	{
		SLTNode* tail = *pphead;
		//不加一个结点的判断,在这里会出问题,不进入循环
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

头删

不需要判断一个结点的状况

void SLPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

插入/删除数据

1.在pos指针之前插入

  1. 首先要考虑头插,其次要找到pos的前一个位置
  2. 类似于尾删
//pos结点前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
	assert(pphead);
	//头插
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		//找到pos结点的前一个结点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//要添加的结点以及值
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

在这里插入图片描述

2. 在pos位置删除

//pos位置删除
void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	assert(pphead);
	assert(*pphead);

	//头删
	if (pos == *pphead)
	{
		SLPopFront(pos);
	}
	else
	{
		//找到pos结点的前一个结点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		//滞空无用,形参改变不影响实参
		//pos = NULL;
	}
}

3. pos后面插入

void SLInsertAfter(SLTNode* pos,SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	//顺序不可返
	//否则数据丢失
	newnode->next = pos->next;
	pos->next = newnode;
}

4. pos后面删除

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	
	//ok
	//SLTNode* del = pos->next;
	//pos->next = pos->next->next;
	//free(del);
	//del = NULL;

	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

测试

void TestLinkList1()
{
	SLTNode* plist = NULL;
	//形参的地址不改变实参
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLPushBack(&plist, 5);
	printf("尾插法:");
	SLTPrint(plist);
	printf("头删法:");
	SLPopFront(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	//空链表 -- 报错
	/*SLPopFront(&plist);
	SLTPrint(plist);*/
}

void TestLinkList2()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	printf("头插法:");
	SLTPrint(plist);
	printf("尾删法:");
	SLPopBack1(&plist);
	SLTPrint(plist);
	SLPopBack2(&plist);
	SLTPrint(plist);
	SLPopBack1(&plist);
	SLTPrint(plist);
	SLPopBack2(&plist);
	SLTPrint(plist);
	//空了再删 -- assert警告
	/*SLPopBack2(&plist);
	SLTPrint(plist);*/

	
}
TestLinkList3()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 5);
	SLPushFront(&plist, 7);
	SLPushFront(&plist, 9);
	
	//值为2的结点的值*2
	printf("查找值为2的结点的值乘2:");
	SLTNode* ret = SLFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);

	//pos结点前插入
	//找到结点位置
	SLTNode* ret2 = SLFind(plist, 5);
	printf("在值为%d前插入:",ret2->data);
	SLInsert(&plist,ret2, 100);
	SLTPrint(plist);
	
	//在pos位置删除
	SLTNode* ret3 = SLFind(plist, 7);
	printf("删除值为%d的数:", ret3->data);
	SLErase(&plist,ret3);
	SLTPrint(plist);

	//在pos后插入
	SLTNode* ret4 = SLFind(plist,5 );
	printf("在%d后插入数据:", ret4->data);
	SLInsertAfter(ret4, 299);
	SLTPrint(plist);

	//在pos后删除
	printf("在%d后删除数据:", ret4->data);
	SLTEraseAfter(ret4);
	SLTPrint(plist);

}
int main()
{
	TestLinkList1();
	TestLinkList2();
	TestLinkList3();
	return 0;
}

在这里插入图片描述

补充

区分两种断言,一定不能为空才断言
在这里插入图片描述

;