数据结构——单链表(不带头)【C】

单链表

  • [1. 链表的概念以及结构](#1. 链表的概念以及结构)
  • [2. 单链表的模拟实现](#2. 单链表的模拟实现)
    • 接口总览
    • [2.1 单链表的尾插和尾删](#2.1 单链表的尾插和尾删)
      • [2.1.1 尾插](#2.1.1 尾插)
      • [2.1.2 尾删](#2.1.2 尾删)
    • [2.2 头插和头删](#2.2 头插和头删)
      • [2.2.1 头插](#2.2.1 头插)
      • [2.2.2 头插的应用(链表的逆置)](#2.2.2 头插的应用(链表的逆置))
      • [2.2.3 头删](#2.2.3 头删)
    • [2.3 查找链表中的元素](#2.3 查找链表中的元素)
    • [2.4 在任意位置插入元素](#2.4 在任意位置插入元素)
    • [2.5 删除任意位置的元素](#2.5 删除任意位置的元素)
    • [2.6 遍历链表 和 销毁链表](#2.6 遍历链表 和 销毁链表)
  • [3. 链表 VS 顺序表](#3. 链表 VS 顺序表)
    • [3.1 比较存储结构](#3.1 比较存储结构)
    • [3.2 从基本操作比较](#3.2 从基本操作比较)

1. 链表的概念以及结构

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

单链表结构:

注意:

  1. 从图上可以看出,单链表在逻辑结构上是连续的,但是物理结构上不一定连续。
  2. 在实际过程中我们单链表的节点都是从堆上申请出来的。
  3. 从堆上的空间是编译器分配出来的,因此申请的空间地址有可能连续,也有可能不连续。

2. 单链表的模拟实现

接口总览

c 复制代码
typedef int SLDateType;
typedef struct SListNode
{
	SLDateType date; 
	struct SListNode* next;
}SLNode;

//打印链表
void SLPrint(SLNode* plist);
//尾插
void SLPushBack(SLNode** plist, SLDateType num);
//头插
void SLPushFront(SLNode** plist, SLDateType num);
//尾删
void SLPopBack(SLNode** plist);
//头删
void SLPopFront(SLNode** plist);
//查找链表中的元素
SLNode* SLFind(SLNode* plist, SLDateType num);
//在任意位置插入元素
void SLInsert(SLNode** plist, SLNode* pos, SLDateType num);
//删除任意位置的元素
void SLErase(SLNode** plist, SLNode* pos);
//摧毁链表
void SLDestory(SLNode** plist);

2.1 单链表的尾插和尾删

2.1.1 尾插

c 复制代码
void SLTPushBack(SLNode** pphead, SLNDataType x)
{
	assert(pphead);
	
	SLNode* newnode = CreateNode(x);
  //判断空链表 
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		// 找尾
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}	
}

2.1.2 尾删

c 复制代码
void SLTPopBack(SLNode** pphead)
{
	assert(pphead);
	//assert(*pphead);

	// 温柔的检查
	//if (*pphead == NULL)
	//	return;

	// 空
	assert(*pphead);

	// 1、一个节点
	// 2、一个以上节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next->next != NULL) // 找到尾结点的前驱结点 
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

2.2 头插和头删

2.2.1 头插

c 复制代码
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	assert(pphead);

	SLNode* newnode = CreateNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

2.2.2 头插的应用(链表的逆置)

反转链表

根据头插法将源链表的头结点,头插入新建的newNode 链表中。



c 复制代码
struct ListNode* ReverseList(struct ListNode* head ) {
    struct ListNode* cur = head ;
    struct ListNode * newNode = NULL;

    while(cur != NULL)
    {
        struct ListNode * next = cur ->next ;
        cur->next = newNode;
        newNode = cur;
        cur = next ;
    }
    return newNode ;

}

2.2.3 头删

c 复制代码
void SLTPopFront(SLNode** pphead)
{
	assert(pphead);
	// 空
	assert(*pphead);
	
	SLNode* tmp = *pphead;
	*pphead = (*pphead)->next;

	free(tmp);
}

2.3 查找链表中的元素

按值查找 时间复杂度为: O(N)

c 复制代码
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->val == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}

	return NULL;
}

2.4 在任意位置插入元素

c 复制代码
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	// 严格限定pos一定是链表里面的一个有效节点
	assert(pphead);
	assert(pos);
	assert(*pphead);

	if (*pphead == pos)
	{
		// 头插
		SLTPushFront(pphead, x);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLNode* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

2.5 删除任意位置的元素

c 复制代码
void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (*pphead == pos)
	{
		// 头删
		SLTPopFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

2.6 遍历链表 和 销毁链表

时间复杂度为 O(N)

c 复制代码
void SLTPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}
c 复制代码
void SLTDestroy(SLNode** pphead)
{
	assert(pphead);

	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}

	*pphead = NULL;
}

3. 链表 VS 顺序表

3.1 比较存储结构

顺序表

  1. 优点:支持随机存储 ,存储密度高
  2. 缺点:大片连续空间分配不方便 ,更改容量不方便

链表

  1. 优点:离散的小空间分配方便,方便更改容量
  2. 缺点:不可以随机存取,存储密度底

3.2 从基本操作比较

一、创建

顺序表

  1. 需要分配大片连续的空间,若空间过小则之后不方便扩容 ,若空间过大则回造成空间的浪费
  2. 静态分配:数组容量不可改变
  3. 动态分配:容量可以改变单是存入数据需要移动大量的元素,时间代价高

链表

  1. 只需要分配结点即可,之后方便拓展
  2. 相较于顺序表灵活性高

二、销毁

顺序表

  1. 静态: 系统回收
  2. 动态: 需要手动"fee" 回收堆中的空间

链表

  1. 一次删除各个结点 "fee" 注意要将链表置空

三、 增加和删除

顺序表 :增删都需要移动元素,前移或是后移 时间复杂度为 O(N) 开销主要来自移动元素 (代价高)

链表:只需要插入和删除修改指针既可 时间复杂度为 O(N) ,开销主要来自查找目标元素 (代价底)

四、 查找

顺序表 : 按位查找时间复杂度为 O(1)、按值查找时间复杂度为 O(N)有序情况下为O(logn)

链表: 按位查找时间复杂度为 O(N)、按值查找时间复杂度为 O(N)都只能从头结点开始查找
注 : 顺序表的查找效率比链表高

链表------表长难以估计、经常要增加和删除元素 。

顺序表------表长可以估计、查询操作较多。

相关推荐
清铎3 小时前
项目_Agent实战
开发语言·人工智能·深度学习·算法·机器学习
m0_748708053 小时前
C++代码移植性设计
开发语言·c++·算法
Dr.Kun3 小时前
【鲲码园PsychoPy】Go/No-go范式
开发语言·后端·golang
进击的小头3 小时前
设计模式组合应用:智能硬件控制系统
c语言·设计模式
郝学胜-神的一滴3 小时前
Linux Socket模型创建流程详解
linux·服务器·开发语言·网络·c++·程序人生
可问春风_ren3 小时前
Vue3 入门详解:从基础到实战
开发语言·前端·javascript·vue.js·前端框架·ecmascript·edge浏览器
-Try hard-3 小时前
数据结构 | 双向链表、双向循环链表、栈
数据结构·链表·vim
cici158743 小时前
基于MATLAB的四旋翼无人机三维动态仿真实例
开发语言·matlab·无人机
EmbedLinX3 小时前
FreeRTOS 学习笔记
c语言·笔记·学习