数据结构单链表的增删查改,附代码+笔记gitee自取

Posted 凛音Rinne

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构单链表的增删查改,附代码+笔记gitee自取相关的知识,希望对你有一定的参考价值。

单链表

gitee自取:
源代码+工程文件
单链表笔记



导言

  • 虽然顺序表一定程度上解决了定长数组带来的空间浪费

  • 但是在我们约定顺序表以2倍的形式扩容的时候,还是会有些空间上的浪

  • 每次头部插入和尾部插入都需要移动数据,效率低下

那么数据结构中还有一种存储结构,叫做链表,可以让我们充分利用空间

本文重点介绍常见链表的一种——单链表


一、链表的概念及其结构

1. 概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的


2. 结构特点

上一个结构体有下一个结构体的地址,最后指向null

这样一个结构,数据不用连续存储,只要有头部位置的地址就可以找到所有的数据


二、单链表的增删查改

1. 单链表主体

每个主体都是一个结构体,上面放数据,下面放下一个指向下一个结构体的地址

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SLTNode;


2. 单链表申请节点

无论实现增删的时候都需要有需要一个链表结构,首先我们得拥有一个节点

// 动态申请一个节点,此节点为新节点
SLTNode* BuySListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("单链表开辟空间失败\\n");
		exit(-1);

	}
	newnode->data = x;//存的数据
	newnode->next = NULL;//最后一个节点的标志

	return newnode;


3. 单链表打印

直到该节点指针指向下一节点的地址为NULL停止

// 单链表打印
void SListPrint(SLTNode* plist)
{
	while(plist != NULL)
	{
		printf("%d->", plist->data);
		plist = plist->next;
	}
	printf("NULL\\n");
}


4. 单链表尾插

新建一个节点,分为两种情况考虑:

  • 链表不存在,将新建的节点地址为链表地址
  • 链表存在,找到最后一个节点,将节点指向的NULL,改为指向新节点地址
// 单链表尾插
void SListPushBack(SLTNode** pplist, SLTDateType x)
{
	SLTNode* newnode = BuySListNode(x);//先新定义一个尾节点,将数字也插进去
	//此数据待链接
	//尾部节点
	SLTNode* tail = *pplist;

	if (tail == NULL)//需要链接的节点为空
	{
		*pplist = newnode;//新的空间就是第一个节点地址就是newnode无需链接
		//这里不能写tail因为是局部变量,改变其数据不能改变本身
	}
	else
	{
		while (tail->next != NULL)//找到最后一个尾节点
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

测试一下我们的打印和申请节点和尾插

void test1()//测试尾插+打印
{
	SLTNode* s = NULL;
	
	//不申请直接插入
	SListPushBack(&s, 1);

	//尾插
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);


	//打印
	SListPrint(s);
}

int main()
{
	test1();//尾插测试

	return 0;
}

测试结果:符合预期


5. 单链表头插

头插也是先考虑两种情况

  • 链表不存在,将新建的节点地址为链表地址
  • 链表存在,新节点指向的NULL,改为头节点地址,再将头节点地址改为新节点地址
// 单链表的头插
void SListPushFront(SLTNode** pplist, SLTDateType x)
{
	SLTNode* newnode = BuySListNode(x);//新的节点

	//当链表为空的时候
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}

}

代码改进:

其实后来发现,两个代码其实可以合并为一个,就算是空也没有关系

简化代码:

// 单链表的头插
void SListPushFront(SLTNode** pplist, SLTDateType x)
{
	SLTNode* newnode = BuySListNode(x);//新的节点
	newnode->next = *pplist;
	*pplist = newnode;

}

测试代码:

void test2()//测试头插+打印
{
	SLTNode* s = NULL;
	
	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);
	SListPushFront(&s, 3);
	SListPushFront(&s, 4);

	//打印
	SListPrint(s);

}

int main()
{
	test2();//头插测试

	return 0;
}

调试效果:


6. 单链表尾删

删除单链表最后一个元素需要考虑3种情况

大概分为可以删和不能删2种情况,可以删还分2种

  • 单链表为空
  • 单链表还剩最后一个数
  • 常规单链表

图解

代码实现:

// 单链表的尾删
void SListPopBack(SLTNode** pplist)
{
	assert(*pplist);
	SLTNode* tail = *pplist; 

	if (tail->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

测试代码:

void test3()//测试尾删+打印
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);

	//尾删
	SListPopBack(&s);
	SListPopBack(&s);

	//打印
	SListPrint(s);
}

int main()
{
	test3();//尾删测试

	return 0;
}

测试结果


7. 单链表头删

头删其实和头插类似,也是需要分3种情况,但其中两种情况可以合并,所以最终是2种情况:

  • 删不了的情况
  • 删的了的情况

图解:

合并出代码:

// 单链表头删
void SListPopFront(SLTNode** pplist)
{
	assert(*pplist);
	SLTNode* head = *pplist;
	head = head->next;
	free(*pplist);
	*pplist = head;
}

在尾删测试代码上做一下修改

void test4()//测试头删+打印
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);

	//尾删
	SListPopFront(&s);
	SListPopFront(&s);

	//打印
	SListPrint(s);
}
int main()
{
	test4();//头删测试

	return 0;
}

测试结果:


8. 单链表查找

单链表查找和尾删有点像,思路上是分两种情况:

  • 链表为空
  • 常规链表查找

注意不是找到就可以了,要返回该数字所在节点的地址

// 单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDateType x)
{
	while (plist)
	{
		if (plist->data == x)
		{
			printf("找到了!\\n");
			return plist;
		}
		plist = plist->next;
	}

	return NULL;//没有找到
}

测试代码

void test5()//测试查找
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2); 
	
	//查找
	s = SListFind(s, 1);
	s = SListFind(s, 3);
	//打印
	SListPrint(s);
}

int main()
{
	test5();//查找测试

	return 0;
}

测试结果

一个找到了,一个没找到


9. 单链表在pos之后插入

也是两种情况:

  • 链表为空
  • 常规链表插入
//在pos之后插入
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
	assert(pos);

	SLTNode* newnode = BuySListNode(x);

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

测试代码:

void test6()//测试查找pos后插入
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);

	//查找
	SLTNode* pos = SListFind(s, 2);

	//插入
	SListInsertAfter(pos, 11);

	//打印
	SListPrint(s);
}

int main()
{
	test6();//pos后插入测试

	return 0;
}

测试结果:


10. 单链表在pos之前插入

考虑情况:

  • 头插
  • 中间插
  • 尾插
//在pos之前插入
void SListInsert(SLTNode** pplist, SLTNode* pos, SLTDateType x)
{
	assert(pos && (*pplist));
	SLTNode* newnode = BuySListNode(x);
	SLTNode* tail = *pplist;

	if (*pplist == pos)
	{
		newnode->next = tail;
		*pplist = newnode;
	}
	else
	{
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		newnode->next = tail->next;
		tail->next = newnode;
	}

}

测试代码:

void test7()//pos前插入测试
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);

	//查找
	SLTNode* pos = SListFind(s, 1);

	//插入
	SListInsert(&s, pos, 11);

	//打印
	SListPrint(s);
}

int main()
{
	test7();//pos前插入测试

	return 0;
}

测试结果:


11. 单链表在pos之后删除

需要考虑情况:

  • 链表为空
  • 链表的pos之后为空
  • 常规
// 单链表删除pos位置之后的值
void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* tag = pos->next;
	pos->next = pos->next->next;
	free(tag);
	//因为tag是局部变量,函数结束后会销毁,所以就不需要令它为NULL

}
 

测试代码:

test7()
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);

	//查找
	SLTNode* pos = SListFind(s, 2);

	//删除
	SListEraseAfter(pos);

	//打印
	SListPrint(s);

}

int main()
{
	test7();//pos后删

	return 0;
}

测试结果


12. 单链表在pos位子值删除

考虑因素:

  • 链表为空
  • pos在首位
  • pos在中间
// 单链表删除pos位置的值
void SListErase(SLTNode** pplist, SLTNode* pos)
{
	assert(pos && *pplist);
	if (pos == *pplist)
	{
		SListPopFront(pplist);//头删
	}
	else
	{
		SLTNode* tail = *pplist;
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		tail->next = pos->next;
		free(pos);
	}
}

测试

test8()
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);

	//查找
	SLTNode* pos = SListFind(s, 2);

	//删除
	SListErase(&s, pos);

	//打印
	SListPrint(s);

}

int main()
{

	test8();//pos位置删除

	return 0;
}

结果


13. 销毁单链表

// 单链表的销毁
void SListDestory(SLTNode** plist)
{
	assert(plist);
	SLTNode* tag = *plist;

	while (*plist != NULL)
	{
		*plist = tag->next;
		free(tag);
		tag = *plist;
	}
}

测试

test9()
{
	SLTNode* s = NULL;

	//头插
	SListPushFront(&s, 1);
	SListPushFront(&s, 2);
	//销毁单链表
	SListDestory(&s);
	//打印
	SListPrint(s);

}

结果


以上是关于数据结构单链表的增删查改,附代码+笔记gitee自取的主要内容,如果未能解决你的问题,请参考以下文章

数据结构学习笔记(单链表单循环链表带头双向循环链表)的增删查改排序等)

数据结构学习笔记(单链表单循环链表带头双向循环链表)的增删查改排序等)

数据结构之单链表的增删查改等操作画图详解

单链表的(增删查改)的实现

单链表的增删查改等基本操作C++实现

数据结构学习笔记(数据结构概念顺序表的增删查改等)详细整理