C/C++数据结构之单链表

大家好,我今天想给大家分享的是c++数据结构中的单链表 ,在这里我们将自己实现一个单链表的基本的操作,以及函数实现。我相信你看了一定会有所收获的。

一、单链表介绍

单链表是一种常见的数据结构,它由一系列结点组成,每个结点包含两个部分:一个是存储数据的数据域,另一个是存储下一个结点地址的指针域。链表的第一个结点称为头结点,最后一个结点的指针域通常为空(在C语言中通常用NULL表示),表示链表的结束。
单链表的特点是:
1.结点之间的连接方向是单向的,即从头到尾。
2.每个结点包含数据部分和一个或多个指向其他结点的指针。
3.链表的头结点存储在链表的开始位置,不一定是第一个存储数据的结点。
4.链表的最后一个结点的指针指向空(NULL),表示链表结束。
5.单链表只可以顺序访问,不能直接访问任意位置的元素
在C语言中,实现单链表通常需要使用结构体来定义链表结点的类型,然后通过动态内存分配函数(如malloc)来创建链表结点,并通过指针来建立结点之间的关系。

二、单链表节点组成代码实现

在单链表介绍中我们知道一个单链表包含一个数据域和指针域,我们就可以通一个结构体来存下这两部分。

typedef int SLDatatype;
typedef struct SLlistNode
{
	SLDatatype data;
	struct SLlistNode* next;
}SLTNode;

上述第一行我们对int重命名,这个作用就是方便我们以后修改数据类型,我们只需把int修改为我们需要的类型即可。我们next是结构体类型指针,因为我们的next指向的是下一个节点,下一个节点也是结构体,所以我们需要使用结构体类型指针。

三、单链表的基本接口函数实现

   (1)SLBuyNode:开辟一个节点的空间。

    在进行基本操作前提都是需要节点的,所以我们能将开辟节点的操作写为一个函数,这样在每次使用前调用即可。

SLTNode* SLBuyNode(SLDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("开辟空间失败");
		exit(0);//直接退出程序;
	}
		newnode->data = x;
		newnode->next = NULL;
		return newnode;
}

上述函数返回的就是一个有效节点,我们需要知道malloc函数开辟失败返回的是一个NULL,所以我们开辟后需要做判断。 

(2)SLPshBack :尾插一个节点。

   思路: 首先我们插入一个节点我们应该调用开辟空间的函数申请一块空间,其次分两种情况讨论:

      1.如果链表是个空链表,那么我们直接把申请的节点空间直接给头节点。

       2.如果非空我们就需要遍历单链表找到最后一个节点,然后把他的next指针指向我们申请的系节点即可。

void SLPushBack(SLTNode** pphead, SLDatatype x)
{
	SLTNode* newnode = SLBuyNode(x);
	SLTNode* ptail = *pphead;
	if (*pphead == NULL) *pphead = newnode;
	else
	{
		while (ptail->next != NULL) ptail = ptail->next;
		ptail->next = newnode;
	}
}

注意:单链表头节点本是一块我们从操作系统申请一块内存,本身就是一个SLTNode* 类型的指针。我们知道要使得实参改变我们就要采用传址的方式传参,不然无法使实参改变。显然这里要使得实参的头节点在链接一个节点,就是使得实参改变,它本身就是一个指针,所以形参我们就需要二级指针来接受实参,进行传址来改变实参。所以我们形参用的是SLTNode**二级指针。在本文章之后的二级指针也是同样的道理。

(3)SLPushfront:头插 

这个实现就相对来说比较简单,就是把申请节点的next指向头节点,在更新头节点。其实当头节点为空的时候就是尾插。

void SLPushFront(SLTNode**pplist,SLDatatype x)
{
	if(*pplist == NULL) SLPushBack(pplist,x);
	else
	{
		SLTNode*newnode = SLBuyNode(x);
		newnode->next = *pplist;
		*pplist = newnode;
	}
}

注:上述代码只用else里边内容也可以,只是个人习惯这么写。

(4)SLPopBack:尾删

思路: 首先判断头节点是否为空,是的话我们把头节点释放即可,否则不判断在后面我们再找后面节点时候会出现对空指针解引用,这个是错误的。如果不为空我们就需要找到最后一个节点将其释放,然后把倒数第二个节点next置为空。如果不置为空,那么会导致链表的最后一个节点不再指向任何有效节点。这意味着你无法从链表的头部遍历到链表的尾部,因为你不知道链表的尾部在哪里。这在逻辑上是一个错误,因为链表的末尾应该有一个指向null的指针来标识链表的结束。在遍历的时候就会导致死循环。

我们来看图理解一下

void SLPopBack(SLTNode**pplist)
{
	assert(*pplist);
	SLTNode*ptail = *pplist;
	SLTNode*pre = *pplist;
	if(*pplist == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
	    while(ptail->next != NULL)//找到最后一个节点
	    {
	        pre = ptail;
		    ptail = ptail->next;
	    }
	    free(ptail);
	    ptail = NULL;
		pre->next = NULL;
	}
}

(5)SLPopFront:头删

思路:头删比较简单,我们只需要把头节点的下一个节点用一个变量储存起来,然后我们把头节点释放,然后把头节点指向我们刚才变量存起来的那个地址即可;

void SLPopFront(SLTNode**pplist)
{
	if(*pplist == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SLTNode*cur = (*pplist)->next;//储存下一个节点;
		free(*pplist);
		*pplist = cur;//把头节点指向下一个节点;
	}
}

 注:上述代码用else里边的代码也可完成这个接口实现

(6)SLInsertposfront:在指定位置之前插入一个节点

思路:

1.如果pos就是头节点,那么在头节点前插入一个节点,那就是头插,直接调用头插函数完成即可。

2.我们需要找到pos位置前一个节点,通过这个节点建立与新节点的关系,那么就可以将这个新节L链接成功。

void SLInsertposfront(SLTNode**pplist,SLTNode*pos,SLDatatype x)
{
	if(*pplist == pos) SLPushFront(pplist,x);
	else
	{
	    SLTNode*newnode = SLBuyNode(x);
	    SLTNode*cur = *pplist;
	    while(cur->next != NULL)
	    {
		    cur = cur->next;
	    }
	    newnode->next = pos;
	    cur->next = newnode;
	}
}

 (7)SLInsertposback:在pos位置后插入一个节点

 思路:改变pos,newnode,pos->next之间的节点关系即可。

void SLInsertposback(SLTNode**pplist,SLTNode*pos,SLDatatype x)
{
	SLTNode*newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

(8)SLEraseposfront:删除指定位置节点 

思路:找到pos位置前一个节点,使其next指向pos指向的next然后释放pos即可

void SLErasepos(SLTNode**pplist,SLTNode*pos)
{

	if (pos == *pplist)
	{
		SLPopFront(pplist);
	}
	else {
		SLTNode* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

(9) SLEraseposback:删除pos位置后面一个节点

思路:释放pos->next,在让pos->next = pos->next->next即可。

void SLEraseposback(SLTNode**pplist,SLTNode*pos)
{
	SLTNode*del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

(10)SLDestory:销毁单链表 

思路:遍历单链表进行销毁

void SLDestory(SLTNode**pplist)
{
	SLTNode*pcur = *pplist;
	while(pcur)
	{
		SLTNode*tmp = (*pplist)->next;
		free(pcur);
		pcur = tmp;
	}
	*pplist = NULL;
}

 四、测试函数的正确性


#include"SLlist.h"
void test()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 10);
	SLPushBack(&plist, 20);
	SLPushBack(&plist, 30);
	SLPushBack(&plist, 40);
	SLPushBack(&plist, 50);
	cout << "尾插结果";
	SLprint(plist);cout << "\n";
	
	SLPushFront(&plist,1000);
	SLPushFront(&plist,2000);
	cout << "头插结果";
	SLprint(plist);cout << "\n";
	
	SLPopBack(&plist);
	cout << "尾删一次";
	SLprint(plist); cout << "\n";
	
	SLPopFront(&plist);
	cout << "头删一次";
	SLprint(plist);cout << "\n";
	
	cout << "查找1000   ";
	SLTNode*find = SLFind(plist,1000);
	if(find == NULL) cout << "没找到!\n";
	else cout << "找到了!\n";
	
	cout << "在1000前插入5000结果  ";
	SLInsertposfront(&plist,find,5000);
	SLprint(plist);cout << "\n";
	
	cout << "在1000后插入6000结果";
	SLInsertposback(&plist,find,6000);
	SLprint(plist);cout << "\n";
	
	cout << "删除1000前的节点结果  ";
	SLErasepos(&plist,find);
	SLprint(plist);cout << "\n";
	
	SLTNode*find1 = SLFind(plist,5000);
	cout << "删除5000后面节点结果  ";
	SLEraseposback(&plist,find1);
	SLprint(plist);cout << "\n";
	
	SLDestory(&plist);
}
int main()
{
	test();
	return 0;
}

运行结果图

我们可以看到我们写的函数是正确的。

五、致谢

    感谢各位读者的阅读,如果这边文章对你有帮助的话,记得评论支持一下哦。让我们共同进步吧。 如果有存在错误或者更好的思路也请各位大佬对我进行指正,再次感谢您的阅读。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值