数据结构:链表实现增删查改的基本功能内含详细代码,建议收藏

Posted lxkeepcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构:链表实现增删查改的基本功能内含详细代码,建议收藏相关的知识,希望对你有一定的参考价值。

前言

hello,大家好,这期文章继续用来更新数据结构方面的知识——链表。。这期文章依旧是对之前的文章数据结构:红玫瑰与白玫瑰之争的补充,希望对大家有所帮助。我们在之前的文章中已经介绍过顺序表,链表和顺序表有很大的不同,相对于顺序表,链表无疑是更加灵活的。顺序表在实现过程中头部插入数据时需要挪动数据,在增容的过程中也会消耗性能,并且以倍数的方式增容也会造成空间的浪费。链表相对于顺序表而言,也许会做的更好

1. 链表的概念

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

通俗理解,链表就是把数据块连接起来,还方便拆卸。可以随时将数据块加入进去,或者拿出去。

3. 链表的结构

在这些链表结构的组合之中,我们最常用的是两种

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了

4. 无头单向非循环链表的实现

4.1 创建工程

依旧采用多文件的方式

4.2 定义结构体

typedef int SLDataType;
typedef struct SListNode
{
	int data;
	struct SListNode *next;
}SLNode;

4.3 创建一个新节点

4.3.1 SList.h 声明

SLNode *BuySLNode(SLDataType x);

4.3.2 SList.c 定义

SLNode *BuySLNode(SLDataType x)
{
	SLNode *node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}


创建一个新节点会在增加操作中频繁使用,所以我们单独定义出一个函数来。创建新节点的思路就是开辟一块空间,空间里存放想要存放的数据,并使这块空间指向NULL。

4.4 头插尾插操作

注意,在头插尾插操作中,我们传递的都是二级指针。因为节点本身就是指针,我们知道按地址传参才可以改变原值,由于plist本身就是指针,要想改变它,所以我们需要传二级指针**pplist。

4.4.1 SList.h 声明

void SListPushFront(SLNode **pplist, SLDataType x);
void SListPushBack(SLNode **pplist, SLDataType x)

4.3.2 SList.c 定义

void SListPushFront(SLNode **pplist, SLDataType x)
{
	SLNode*newnode = BuySLNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}
void SListPushBack(SLNode **pplist, SLDataType x)
{
	SLNode*newnode = BuySLNode(x);
	//空和非空
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		//先遍历找尾
		SLNode *tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		SLNode *newnode = BuySLNode(x);
		tail->next = newnode;
	}
}

头插相对于尾插更好实现,只需要在头节点前链接上新节点就好。但是尾插则需要考虑的更多一些,实现我们要判断链表此时是否为空,如果是空则不需尾插,如果非空,我们则需要首先找到链表的尾,在尾上插入新的节点。尾插动画演示如下:

4.5 头删尾删操作

4.5.1 SList.h 声明

void SListPopFront(SLNode **pplist);
void SListPopBack(SLNode **pplist);

4.5.2 SList.c 定义

void SListPopFront(SLNode **pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else
	{
		SLNode *next= (*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
}
void SListPopBack(SLNode **pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SLNode *prev = NULL;
		SLNode *tail = *pplist;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

头删只需要去除第一个节点,注意判断如果链表为空则不需要操作。对于尾删,依旧是要先找到尾部,还要找到尾部节点的前一个节点prev,将尾部节点释放,然后将prev置为新的尾节点。尾删动画演示如下:

4.6 查找操作

查找操作要返回该节点的指针,注意考虑空链表的情况。

4.6.1 SList.h 声明

SLNode*SListFind(SLNode **pplist, SLDataType x);

4.6.2 SList.c 定义

SLNode*SListFind(SLNode **pplist, SLDataType x)
{
	SLNode *cur = *pplist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

4.7 修改操作

修改操作时在查找的基础上进行的,找到要修改的值的节点,然后进行修改。注意要考虑找不到要修改的数据的情况。

4.7.1 SList.h 声明

void SListModify(SLNode *plist, SLDataType x, SLDataType y);

4.7.2 SList.c 定义

void SListModify(SLNode *plist, SLDataType x, SLDataType y)
{
	SLNode *pos = SListFind(&plist, x);
	if (pos)
	{
		pos->data = y;
	}
	else
	{
		printf("没有该数字,无法修改\\n");
	}
}

4.8 前插后插操作

前插后插比头插尾插更复杂一些。尤其是前插操作,需要考虑的东西会多一点。后插我们只需要在位置后加入一个新的节点,而前插则需要考虑该位置是不是头节点。如果不是头节点,还需要找到该位置并且需要找到该位置的前一个节点,将新节点插入其中。下面通过动画演示非首元素前插的情况:

4.8.1 SList.h 声明

void SListInsertAfter(SLNode *pos, SLDataType x);
void SListInsertBefore(SLNode **pplist,SLNode *pos, SLDataType x);

4.8.2 SList.c 定义

void SListInsertAfter(SLNode *pos, SLDataType x)
{
	assert(pos);
	SLNode *newnode = BuySLNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SListInsertBefore(SLNode **pplist, SLNode *pos, SLDataType x)
{
	assert(pos);
	SLNode *newnode = BuySLNode(x);
	if (pos == *pplist)
	{
		newnode->next = pos;
		*pplist = newnode;
	}

	else
	{
		SLNode *prev = NULL;
		SLNode *cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

4.9 现删后删操作

对于删除操作,我们再这里实现删除指定位置后的节点和指定位置的节点,因为删除指定位置之前的节点实现起来非常麻烦且意义不大。因为我们可以通过改变指定位置来实现删除前一个节点。删除指定位置后一个节点需要考虑是否为空链表。删除当前位置节点则需要考虑当前位置是否为首节点。我们在这里用动画演示删除非首节点当前节点的操作:

4.9.1 SList.h 声明

void SListEraseAfter(SLNode*pos);
void SListEraseCur(SLNode **pplist,SLNode*pos);

4.9.2 SList.c 定义

void SListEraseAfter(SLNode*pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLNode*next = pos->next;
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}
void SListEraseCur(SLNode **pplist,SLNode*pos)
{
	if (pos == *pplist)
	{
		return;
	}
	else
	{
		SLNode *prev = NULL;
		SLNode *cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = pos->next;
		free(cur);
		cur = NULL;
	}
}

4.10 完整代码展示及运行

4.10.1 SeqList.h

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

typedef int SLDataType;
typedef struct SListNode
{
	int data;
	struct SListNode *next;
}SLNode;

//单向+带头+不循环
void SListPrint(SLNode *plist);

SLNode *BuySLNode(SLDataType x);

//二级指针
void SListPushFront(SLNode **pplist, SLDataType x);
void SListPushBack(SLNode **pplist, SLDataType x);
void SListInsertAfter(SLNode *pos, SLDataType x);
void SListInsertBefore(SLNode **pplist,SLNode *pos, SLDataType x);


void SListPopFront(SLNode **pplist);
void SListPopBack(SLNode **pplist);
void SListEraseAfter(SLNode*pos);
void SListEraseCur(SLNode **pplist,SLNode*pos);

SLNode*SListFind(SLNode **pplist, SLDataType x);
void SListModify(SLNode *plist, SLDataType x, SLDataType y);

4.10.2 SeqList.c

#include"SList.h"

void SListPrint(SLNode *plist)
{
	SLNode *cur = plist;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\\n");
}

SLNode *BuySLNode(SLDataType x)
{
	SLNode *node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

void SListPushFront(SLNode **pplist, SLDataType x)
{
	SLNode*newnode = BuySLNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}
void SListPushBack(SLNode **pplist, SLDataType x)
{
	SLNode*newnode = BuySLNode(x);
	//空和非空
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		//先遍历找尾
		SLNode *tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		SLNode *newnode = BuySLNode(x);
		tail->next = newnode;
	}
}

void SListPopFront(SLNode **pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else
	{
		SLNode *next= (*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
}
void SListPopBack(SLNode **pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SLNode *prev = NULL;
		SLNode *tail = *pplist;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}


SLNode*SListFind(SLNode **pplist, SLDataType x)
{
	SLNode *cur = *pplist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


void SListModify(SLNode *plist, SLDataType x, SLDataType y)
{
	SLNode *pos = SListFind(&plist, x);
	if (pos)
	{
		pos->data = y;
	}
	else
	{
		printf("没有该数字,无法修改\\n");
	}
}


void SListInsertAfter(SLNode *pos, SLDataType x)
{
	assert(pos);
	SLNode *newnode = BuySLNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SListInsertBefore(SLNode **pplist, SLNode *pos, SLDataType x)
{
	assert(pos);
	SLNode *newnode = BuySLNode(x);
	if (pos == *pplist)
	{
		newnode->next = pos;
		*pplist = newnode;
	}

	else
	{
		SLNode *prev = NULL;
		SLNode *cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}


void SListEraseAfter(SLNode*pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLNode*next = pos->next;
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}
void SListEraseCur(SLNode **pplist,SLNode*pos)
{
	if (pos == *pplist)
	{
		return;
	}
	else
	{
		SLNode *prev = NULL;
		SLNode *cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = pos->next;
		free(cur);
		cur = NULL;
	}
}

4.10.3 test.c

#include"SList.h"

void TestOne()
{
	SLNode *plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack以上是关于数据结构:链表实现增删查改的基本功能内含详细代码,建议收藏的主要内容,如果未能解决你的问题,请参考以下文章

vector基本内容与增删查改的模拟实现

vector基本内容与增删查改的模拟实现

mybatis实现简单的增删查改

增删查改的实现

高阶数据结构(壹)——一步一步教你手撕AVL树(增删查改)画图详解,内含代码实现包含注释)

C数据结构单链表接口函数逻辑解析与代码实现(含详细代码注释)