0
点赞
收藏
分享

微信扫一扫

数据结构第二讲-线性表之单链表

中间件小哥 2022-05-04 阅读 109

线性表之单链表

线性表的链式表示

顺序表可以随时存取表中的任意一个元素,存储位置可以用简单直观的公式表示,但是插入和删除需要移动大量的元素,。这是因为顺序表是连续的存储单元。但是链式储存线性表的时候,不需要使用地址连续的存储单元。不要求逻辑上相邻的元素在物理位置上也相邻。

2.1单链表的定义

线性表的链式存储又叫单链表,是指通过一组任意的存储单元来存储线性表中的数据元素。单链表如何建立元素之间的联系呢?对每个链表节点,除存放自身的信息外,话需要存放一个指向其后继的指针。

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

在这里插入图片描述
上图就是单链表节点结构图,data为数据域,next为指针域,存放后继节点的地址。
通常用头指针来标识一个单链表,我们本篇的头指针就是head,头指针为NULL的时候为一个空表,这里还有一个哨兵位的问题,就是在单链表第一个结点之前附加一个结点。这个结点的数据域可以不设任何信息。

引入头结点的优点
1.由于第一个数据结点的位置被存放在头结点的指针域中,因此在链表的第一个位置上的操作和在表的其他位置上的操作一致,无需进行特殊处理
2.无论链表是否为空,其头指针都指向头结点的非空指针,因此空表和非空表的处理就得到了统一

2.2单链表上基本操作实现

2.2.1采用头插法建立单链表

头插的对象可能是一个空链表,也可能是一个非空的链表。实际上我们在插入的时候,只需要将新建立的结点和之间的链表链接起来就好了。其实是很简单的。
这里要讨论一个问题,在参数传递的时候,需要传递一个指向头指针的指针来改变单链表的内容(头插函数的返回值为void),其原因是因为函数是无法改变形式参数的内容的,只能通过传递指针解引用来改变内容
我们来看一下头插的算法:

void SListPushFront(SLTNode** pphead, SLDataType x)
{
 	assert(pphead);
 	SLTNode*Newnode=(SLTNode*)malloc(sizeof(SLTNode));
 	assert(Newnode);
 	Newnode->next=*phead;
 	*phead=Newnode;
}

2.2.2采用尾插法建立单链表

尾插法就是在单链表的末端插入一个元素,如果单链表为空,那么尾插和头插是一个意思。但是这里需要和头插不一样的是尾插需要寻找尾结点。这样就使得尾插的时间复杂度为O(n)。但是如果链表为空,那么需要另外分一种情况。

    void SListPushBack(SLTNode** phead, SLDataType x)
{
	assert(phead);
		SLTNode*newnode=(SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->next==NULL;
	newnode->data=x;
	SLTNode*list=*phead;
	if(*phead==NULL)
	{	
		*phead=newnode;
	}
	else
	{
		while(list->next!=NULL)
		{
			list=list->next;
		}
		list->next=newnode;
	}
}

2.2.3按值查找表结点

在链表中查找某个值为x的结点,并输出这个结点的地址

SLTNode* SListFind(SLTNode* phead, SLDataType x)
SLTNode*list=phead;
whlie(list)
{
	if(list->data==x)
	return list;
	else
	list=list->next;
	return NULL;
}

程序很简单,这里就不多赘述了。

2.2.4插入结点操作

通过按值查找表结点,找到被插入的对象,要是再结点之前插入元素,我们来分析一下:
如果这个结点是头结点,那么操作就是进行头插。可以直接调用头插函数。那么进行普通的插入,需要先遍历一遍链表,找到被插入的元素的之前一个元素
在这里插入图片描述

void SListInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
	assert(pphead);
	assert(pos);
	if(pos==head)
	{
		void SListPushFront(pphead, x);

	}
	else
	{
		SLTNode*list=*pphead;
		while(list->next!=pos)
		{
			list=list->next;
		}
		SLTNode*newnode=(SLTNode*)malloc(sizeof(SLTNode));
		newnode->next=pos;
		list->next=newnode;
	}
}

2.2.5删除结点操作

头删

头删就是删除头结点,当然前提是链表中是有元素可以被删除的,否则无法头删。

void SListPopFront(SLTNode** phead)
{
	assert(*phead);
	SLTNode*next=(*phead)->next;
	free(*phead);
	*phead=next;
}

代码很简单,不多赘述。

尾删

尾删是删除链表的尾结点,然后将表尾的上一个结点的next指向设置为NULL,但是也要考虑链表是否为空或者仅有一个结点,如果仅有一个结点,那么直接将该结点释放并置空。

void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if(*pphead->next==NULL)//判断是否只有一个结点
	{
		free(*pphead);
		*pphead=NULL;
	}
	else
	{
		SLTNode*list=*pphead;
		while(list->next->next!=NULL)
		{
			list=list->next;
		}
			free(list->next);
			list->next=NULL;
	}
}

删除pos位置之后的值

在这里插入图片描述
删除pos位置之后的值,首先确保pos之后还有结点,然后才能进行删除。删除需要将pos指向的下一个结点设置为被删除元素指向的下一个结点。来看一下代码

void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	if(pos->next==NULL)
	{
		return ;
	}
	SLTNode*rem=pos->next;
	pos->next=rem->next;
	free(rem);
	rem=NULL:
}
举报

相关推荐

0 条评论