数据结构 ——单链表

发布于:2025-03-21 ⋅ 阅读:(28) ⋅ 点赞:(0)

前言

单链表和顺序表相比可就好太多了,效率高不少,在进行头插和头删时的效率相差最大,顺序表在进行头插和头删时时间复杂度为O(n^2),而在单链表中只需要更改几个指针就可以,效率大大提升,相信通过这篇文章可以让你清楚认识了解单链表。

目录

单链表的定义

 单链表头插

单链表头删 

单链表尾插

单链表尾删

单链表查找 

单链表在pos位置上插入一个值 

链表的打印 


单链表的定义

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

物理结构:

 逻辑结构:

每一个都是一个结构体,在写单链表先创建一个结构体,里面成员有结构体指针和数据,结构体指针指存储下一个结构体的地址。

typedef int SLTDateType;

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

 单链表头插

写单链表需要创建两个源文件和一个头文件,头文件用来进行函数的声明,一个源文件用来函数的定义,另一个源文件用来测试链表。

在写单链表头插函数之前需要想一个问题,参数是用一级指针还是二级指针呢?

这里因为需要改变传入指针的指向,所以在传参是需要传该结构体指针的地址,需要用到二级指针,如果使用一级指针会怎么样?

我们都知道形参是实参的一份临时拷贝,形参的改变并不影响实参,所以在进行头插时,我们这里使用二级指针,简单理解就是我们要改变int,就需要使用int*,改变int*,使用int**。

头插代码:

void SListPushFront(SListNode** pphead, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	
}

当然这里我们返回类型是void ,如果返回类型是结构体指针也可以不使用二级指针。

一开始该链表为空,不需要进行断言,因为空链表也可以进行插入,打印一系列操作。

插入一个结构体我们需要向操作系统申请空间来存放赋值该结构体然后对其进行链接,所以需要一个申请动态节点的函数 

代码示例:

SListNode* BuySListNode(SLTDateType x)
{
	SListNode* p = (SListNode*)malloc(sizeof(SListNode));
	if (p == NULL)
	{
		perror("malloc:fail");
		return NULL;
	}
	p->data = x;
	p->next = NULL;
	return p;
}

单链表头删 

 对单链表进行删除,一个链表的长度是有限的,不可以一直删下去,因为在进行删除操作之前要对单链表进行断言,头删只需要将第一个节点删除(这里没有头结点也就是哨兵位),删除完后切记将该节点进行释放置空,要养成好的代码习惯。

代码示例:

void SListPopFront(SListNode** pphead)
{
	assert(*pphead);
	SListNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
	cur = NULL;
}

 这里也是传结构体指针的地址,需要对参数进行改变。

单链表尾插

尾插和头插相差不大,只是尾插需要将最后一个结构体的成员指针指向新插入的结构体,这里我们还是使用二级指针,在链表为空时,我们需要改变传入的参数来指向新开辟的空间。

代码示例:

void SListPushBack(SListNode** pphead, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead= newnode;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

单链表尾删

 代码示例:

void SListPopBack(SListNode** pphead)
{
	assert(*pphead);
	SListNode* cur = *pphead;
	/*while (cur->next->next != NULL)
	{
		cur = cur->next;
	}
	free(cur->next);
	cur->next = NULL;*/
	SListNode* p = *pphead;
	while (cur->next != NULL)
	{
		p = cur;
		cur = cur->next;
	}
	free(p->next);
	p->next = NULL;
}

先对链表进行断言,如果链表为空就不能继续删除,否则会报错,尾删就需要找到最后一个节点,所以循环结束条件为该节点的成员指针指向NULL,就可以找到最后一个节点,最后对该节点进行释放置空。

单链表查找 

代码示例:

SListNode* SListFind(SListNode* phead, SLTDateType x)
{
	assert(phead);
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

先对链表断言,在遍历该链表,如果有一个节点的值和我们传的值相同就返回该节点的地址,反之返回NULL。

单链表在pos位置上插入一个值 

这个函数需要和查找函数配合,我们可以找到一个值的地址,然后在该位置之后插入一个结构体,那为什么是在pos之后插入而不是在pos之前插入呢?

这时我们就需要知道这是一个单链表是单向的,想要找到一个节点只能从头开始遍历,在pos之前插入的话就需要遍历链表找到pos前一个节点的地址(双向链表就没有这个困扰),如果链表很长的话效率就很低。如果在pos之后插入的话就非常方便,我们可以通过pos来找到下一个节点的地址,改变两个指针就可以插入一个结构体,效率高了不少。

代码示例:

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

有了这个函数就可以将这个函数放入尾插中,只需要传尾节点就可以。

链表的打印 

代码示例:

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

这里就不需要对链表断言,因为链表为空也可以打印。

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 


网站公告

今日签到

点亮在社区的每一天
去签到