数据结构---单向链表

2024-10-09 16:07:18 浏览数 (3)

顺序表回顾—引入链表

缺陷: 1.空间不够了,增容。(增容会带来缺陷,增容会付出一定的性能消耗,其次可能存在一定的空间浪费) 例如:当我有一百个空间,满了之后,增容扩大两倍,但是我插入之后,只插入了10个数据,所以浪费了90个空间。 2.头部或者中部左右的插入删除的效率低。->O(N) 如何解决: 1.空间上,按需给空间。 2.不要求物理空间连续,头部和中部的插入,就不需要挪动数据。 解决方案:链表


一、链表是什么?

1.链表的分类

1.单向链表:单向链表的特点是链表的链接方向是单向的,对链表的访问需要通过顺序读取从头部开始。 2.双向链表:双向链表也叫双链表,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。 3.循环链表:循环链表的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。 4.线性链表:线性链表是一种简单的链表结构,每个结点只包含一个指向下一个结点的指针。 5.树链表:树链表是一种层次化的链表结构,它的每个结点可以有多个子结点,通过指针连接。 6.网链表:网链表是一种复杂一点的链表结构,它的每个结点可以有多个指向前驱和后继的指针。 等等… 本次内容讲的是单向链表。

2.单向链表

注意:最后一个指针应该指向空指针。 链表的概念及结构 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 从逻辑结构看:链表是线性的,是想象出来的。 从物理结构看:

第二个空间存的是第二个节点的地址。 节点的地址是malloc来的,也可以认为是随机的,malloc是操作系统随机找的一个空闲的空间。 最后一个节点存的是空指针。 顺序表和链表的区别就是: 物理上是连续的存储的,链表的物理空间是不连续的。

链表的定义:

代码语言:javascript复制
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;//结构体中不能有结构体嵌套,但是可以是结构体指针,
	//指针就是一个地址,表示4个字节,指针指向的下一个节点是结构体
}SLT;

注意:结构内部是不能用typedef的结构体类型。


二、链表的应用

1.链表的打印

注意:这里的SLT类型是上文定义的结构体类型

代码语言:javascript复制
void SListPrint(SLT* plist)
{
	SLT* cur=plist;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULLn");
}

2.建立新的节点

代码语言:javascript复制
SLT* BuySListNode(SLTDataType x)
{
	SLT* newnode = (SLT*)malloc(sizeof(SLT));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

3.尾插和首插

代码语言:javascript复制
//尾插 
void SListPushBack(SLT** pplist, SLTDataType x)
{
	SLT* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;//形参是实参的零时拷贝,出了定义域会销毁
	}
	else
	{
		SLT* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		//尾节点,链接新节点
		tail->next = newnode;
	}
}
//头插
void SListPushFront(SLT** pplist, SLTDataType x)
{
	SLT* newnode = BuySListNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

4.尾删和头删

代码语言:javascript复制
//尾删
void SListPopBack(SLT** pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
		return;
	}
	SLT* tail = *pplist;
	SLT* prev = NULL;//保存上一个节点的指针变量
	while (tail->next != NULL)
	{
		prev = tail;//将上一个节点存起来
		tail = tail->next;//找下一个节点
	}
	free(tail);//free掉下一个空间
	prev->next = NULL;//找到倒数第二个节点指向的下一个节点,并置为空指针。防止变为野指针
	tail = NULL;//置位空指针
}
//头删
void SListPopFront(SLT** pplist)
{
	SLT* tail = (*pplist)->next;//将下一个节点的位置给存起来
	free(*pplist);//将这个节点给free掉
	*pplist = NULL;//置空
	*pplist = tail;//找到下一个节点,并将其赋值为头结点
}

5.查找链表中的数据

代码语言:javascript复制
//查找链表数据
SLT* SListFind(SLT* plist, SLTDataType x)
{
	SLT* cur = plist;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

6.在指定pos位置之前插入数据或者删除pos位置数据

代码语言:javascript复制
//在pos位置前插入x
void SListInsert(SLT** pplist, SLT* pos, SLTDataType x)
{
	if (*pplist == pos)
	{
		SListPushFront(pplist, x);
	}
	else
	{
		SLT* newnode = BuySListNode(x);
		SLT* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}
//删除pos位置的数
void SListErase(SLT** pplist, SLT* pos)
{
	if (pos == *pplist)
	{
		SListPopFront(pplist);
	}
	else
	{
		SLT* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLT* tail = pos->next;
		free(pos);
		prev->next = tail;
	}
}

三、如何使用链表应用中的函数及函数封装

1.在pos前插入指定x

代码语言:javascript复制
void  TestSlist3()
{
	SLT* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SLT* pos = SListFind(plist, 4);
	if (pos)
	{
		SListInsert(&plist, pos, 30);
	}
	printf("n");
	SListPrint(plist);
}
int main()
{
	TestSlist3();
	return 0;
}

注意:删除pos位置的数也与上述代码雷同


2.封装为3个文件

1. Slist.h
代码语言:javascript复制
#pragma once//防止重复包含

#include<stdio.h>
#include<stdlib.h>


typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;//结构体中不能有结构体嵌套,但是可以是结构体指针,
	//指针就是一个地址,表示4个字节,指针指向的下一个节点是结构体
}SLT;
void SListPrint(SLT* plist);
void SListPushBack(SLT** plist, SLTDataType x);
void SListPushFront(SLT** pplist, SLTDataType x);
void SListPopBack(SLT** pplist);
void SListPopFront(SLT** pplist);


//查找位置
SLT* SListFind(SLT* plist, SLTDataType x);
//在pos前面插入一个数
void SListInsert(SLT** pplist,SLT*pos,SLTDataType x);

//删除pos位置的值
void SListErase(SLT** pplist,SLT*pos);

2. test.c文件
代码语言:javascript复制
#include "Slist.h"

void TestSlist1()
{
	SLT *plist=NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 5);
	SListPrint(plist);
}

void  TestSlist2()
{
	SLT* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPopBack(&plist);
	printf("n");
	SListPrint(plist);
}
void  TestSlist3()
{
	SLT* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SLT* pos = SListFind(plist, 4);
	if (pos)
	{
		SListInsert(&plist, pos, 30);
	}
	printf("n");
	SListPrint(plist);
}

void  TestSlist4()
{
	SLT* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SLT* pos = SListFind(plist, 3);
	if (pos)
	{
		SListErase(&plist, pos);
	}
	printf("n");
	SListPrint(plist);
}
int main()
{
	TestSlist4();
	return 0;
}

注意:Slist.c文件就是上面函数,这里不过多重复阐述

四、总结

链表相较于顺序表来说难度有所上升,但是掌握链表基本概念,练习一些算法题还是很好掌握的。

1 人点赞