IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构——链表 (单向不带头不循环链表 与 双向带头循环链表) -> 正文阅读

[数据结构与算法]数据结构——链表 (单向不带头不循环链表 与 双向带头循环链表)

目录

一、类型

1、单链表、双向链表

2、不带头单链表、带头链表

3、单链表、循环链表

二、单向+ 不带头+ 不循环链表

链表接头

函数定义:

1、打印链表

2、新节点的创建

3、尾插节点

? ? ?3.1、非空情况

? ? ?3.2、空链表情况

4、头插节点

5、尾删(三种情况)

5.1、没有节点

5.2、一个节点

5.3、多个节点

6、头删

6.1、多个节点

6.2、单个节点

7、查找节点

7、插入pos节点的后方

8、插入pos的前方

8.1、pos是链表第一个节点

8.2、pos是链表中间节点

9、删除pos节点后的节点

三、双向+ 带头+ 循环链表

链表接头

函数定义:

1.打印单链表

2、新节点的创建

2.1、新节点的初始化

3、尾插节点

? ? ?3.1、非空链表情况

4、头插节点

5、尾删

5.1、没有节点

5.2、一个节点、多个节点

6、头删

6.1、多个节点、单个节点

?编辑

7、查找节点

7、插入pos节点

8、删除pos节点后的节点

9、检查链表(空返回1,非空返回0)

10、计算链表有效值个数

11、清空链表

12、双向链表总结

顺序表和链表的优缺点对比


一、类型

1、单链表、双向链表

2、不带头单链表、带头链表

3、单链表、循环链表

  • 可以结合共成八种

二、单向+ 不带头+ 不循环链表

链表的定义结构

typedef int SLTDataType;//方便后期修改数据类型
typedef struct SListNode
{
	SLTDataType data;      //数据域
	struct SListNode* next;//指针域
}SLTNode;

  • 链表接头

接头函数声明

//单项+ 不带头+ 不循环
void SListPrint(SLTNode* plist);

void SListPushBack(SLTNode** pplist, SLTDataType x);//尾插
void SListPushFront(SLTNode** plist, SLTDataType x);//头插

void SListPopBack(SLTNode** pplist);//尾删
void SListPopFront(SLTNode** pplist);//头删

SLTNode* SListFind(SLTNode* plist, SLTDataType x);//搜索

void SListInsertAfter(SLTNode* pos, SLTDataType x);//任意插入
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x);
//任意插入pos位置前

void SListEraseAfter(SLTNode* pos);//删除pos后节点
//void SListEraseCur(SLTNode* pos);//删除pos

函数定义:

?1.打印链表

void SListPrint(SLTNode* plist)
{
???????SLTNode* cur = plist;
???????while (cur != NULL)
???????{
??????????????printf("%d ", cur->data);
??????????????cur = cur->next;
???????}
???????printf("\n");
}

2、新节点的创建

//将所需数据放入节点里,return 创建的地址,需要一个指针接收
SLTNode* BuySLTNode(SLTDataType x)
{
???????SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
???????node->data = x;
???????node->next = NULL;
???????return node;
}

3、尾插节点

? ? ?3.1、非空情况

? ? ?3.2、空链表情况

void SListPushBack(SLTNode** pplist, SLTDataType x)//尾插
{
???????//创建节点存储
???????SLTNode* newnode = BuySLTNode(x);
???????//1、空的情况
???????//2、非空情况
???????if (*pplist == NULL)
???????{
??????????????*pplist = newnode;//修改的是地址,且函数无return,所以用二级指针
???????}
???????else
???????{
??????????????//找尾
??????????????SLTNode* tail = *pplist;
??????????????while (tail->next != NULL)
??????????????{
??????????????????????tail = tail->next;
??????????????}
??????????????tail->next = newnode;//用节点尾巴去接上新节点
???????}
}

4、头插节点

void SListPushFront(SLTNode** pplist, SLTDataType x)//头插
{
???????SLTNode* newnode = BuySLTNode(x);
???????newnode->next = *pplist; //先将new的next指向pplist原本指向的节点
???????*pplist = newnode;???????//再将new节点的地址设置为pplist
}

5、尾删(三种情况)

5.1、没有节点

? ? -直接return,或者断言

5.2、一个节点

将其free再置空

5.3、多个节点

void SListPopBack(SLTNode** pplist)
{
	//1、没有节点
	//2、一个节点
	//3、多个节点
	if (*pplist == NULL)
	{
		return;
	}
	else if((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SLTNode* prev = NULL;//前驱指针
		SLTNode* tail = *pplist;
		while (tail->next != NULL)
		{
			prev = tail;       //前驱后移
			tail = tail->next; //tail也后移
		}
		free(tail);//将tail置空,此时plist的最后一个节点也被置空
		tail = NULL;//防止野指针

		prev->next = NULL;//将尾指针置空
	}
	
}

6、头删

6.1、多个节点

6.2、单个节点

void SListPopFront(SLTNode** pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else
	{
		SLTNode* next = (*pplist)->next;//制定一个next存放首节点后的节点
		free(*pplist);

		*pplist = next;
	}
}

7、查找节点

//查找,返回节点x的指针
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
	SLTNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

7、插入pos节点的后方

void SListInsertAfter(SLTNode* pos, SLTDataType x)//任意插入
{
	assert(pos);

	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;//先将新封装的节点连接到d3
	pos->next = newnode;//再讲d2的next连接到新节点
}

8、插入pos的前方

8.1、pos是链表第一个节点

8.2、pos是链表中间节点

//任意插入pos位置前
void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
{
	//只要涉及到改变第一个节点,也就是要修改传入的指针变量
	//那么就要用二级指针
	assert(pos);

	SLTNode* newnode = BuySLTNode(x);

	if (pos == *pplist)//就是头插
	{
		newnode->next = pos;
		*pplist = newnode;//将新节点指定为第一个节点,用二级指针传值
	}
	else
	{
		SLTNode* prev = NULL;
		SLTNode* cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

思考:

在一个无头单链表的某一节点的前面插入一个值x,如何插入?(不告诉头指针)

  • 后插入一个值为x的节点,然后和前面的节点值data交换

9、删除pos节点后的节点

对于两种情况,一种解决方案即可

void SListEraseAfter(SLTNode* pos)//删除节点
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLTNode* next = pos->next;//用next存放d3
		pos->next = next->next;//再将pos连接到d3的后一节点
		free(next);//释放d3
	}
}

10、删除当前pos

思路:


三、双向+ 带头+ 循环链表

链表的定义结构

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;//前驱指针
	LTDataType data;
}ListNode;

  • 链表接头

接头函数声明

ListNode* BuyListNode(LTDataType x);//创建节点

ListNode* ListInit();//初始化节点
void ListPrint(ListNode* phead);//打印链表

void ListPushBack(ListNode* phead, LTDataType x);//尾插
void ListPushFront(ListNode* phead, LTDataType x);
void ListPopBack(ListNode* phead);
void ListPopFront(ListNode* phead);

ListNode* ListFind(ListNode* phead, LTDataType x);
void ListInsert(ListNode* pos, LTDataType x);
void ListErase(ListNode* pos);

//空返回1,非空返回0
int ListEmpty(ListNode* phead);
int ListSize(ListNode* phead);
void ListDestory(ListNode* phead);

函数定义:

1.打印单链表

void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2、新节点的创建

//将所需数据放入节点里,return 创建的地址,需要一个指针接收
ListNode* BuyListNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

2.1、新节点的初始化

  • 双向带头循环链表在初始化只有一个头节点时,要见头尾指向自己
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;//让头指向指向自己
	phead->prev = phead;//让尾指向指向自己

	return phead;
}

3、尾插节点

? ? ?3.1、非空链表情况

在只有一个头节点或者多个节点都可以一次实现

void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	//初链表只有一个节点或者多节点都可以实现

	ListNode* tail = phead->prev;//因为循环,所以头节点的前一个是为节点,用tail记录
	ListNode* newnode = BuyListNode(x);

	tail->next = newnode;//将原尾节点tail连接新节点
	newnode->prev = tail;//新节点的前驱节点设置为tail
	newnode->next = phead;//新节点的下一节点设置为头节点
	phead->prev = newnode;//头节点的前驱指针指向新节点

	//此方法也是尾插
	/*ListInsert(phead, x);*/ 

}

4、头插节点

void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	//头插注意要先将newnode先连接第一有数据节点(不是头指针)
	//或者设置一个新的指针(frist)先存放第一有数据节点
	ListNode* first = phead->next;
	ListNode* newnode = BuyListNode(x);

	//开始连接三个指针
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//此方法也是头插
	/*ListInsert(phead->next, x);*/
}

5、尾删

5.1、没有节点

? ? 删除要注意断言只有头指针的情况

5.2、一个节点、多个节点

两种情况一种解决方案

void ListPopBack(ListNode* phead)
{
	//不用遍历即可找到尾项
	assert(phead);
	assert(phead->next != phead);//如果链表只有头节点在此情况也是错误链表

	ListNode* tail = phead->prev;//设置指针指向原尾项
	ListNode* tailPrev = tail->prev;//设置指针指向倒数第二项
	free(tail);//释放最后一项

	tailPrev->next = phead;//倒数第二项晋升为最后一项,用其连接头节点
	phead->prev = tailPrev;//将头节点的前驱指针指向新尾节点

	//此方法也是尾插
	/*ListErase(phead->prev);*/
}

6、头删

6.1、多个节点、单个节点

两种情况一种解决方案

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//如果链表只有头节点也是错误链表

	ListNode* frist = phead->next;
	ListNode* fristNext = frist->next;
	free(frist);

	fristNext->prev = phead;
	phead->next = fristNext;

	//此方法也是头插
	/*ListErase(phead->next);*/

}

7、查找节点

ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	return NULL;//没找到返回空
}

7、插入pos节点

  • 插入前(对于双向链表在pos前后插入节点都很简单)
  • 就算pos是头节点 或者 是最后一个节点 都很容易实现
  • 不过pos是头节点用此方法相当于 尾插
  • pos是phead->next则相当于 头插

//插入前(对于双向链表在pos前后插入节点都很简单)
//就算pos是头节点 或者 是最后一个节点 都很容易实现
//不过pos是头节点用此方法相当于 尾插
//pos是phead->next则相当于 头插
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

	prev->next = newnode;//双向
	newnode->prev = prev;

	newnode->next = pos;//双向
	pos->prev = newnode;
}

8、删除pos节点后的节点

对于两种情况,一种解决方案即可

void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	
	prev->next = next;
	next->prev = prev;

	free(pos);
}

9、检查链表(空返回1,非空返回0)

int ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead ? 1 : 0;
}

10、计算链表有效值个数

int ListSize(ListNode* phead) 
{
	assert(phead);

	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
	
}

11、清空链表

void ListDestory(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
	phead = NULL;//此处没用,需要传递二级指针,但是会破坏一致性
	//最后要置空头指针,需要在.c文件最后plist = NULL;置空
}

12、双向链表总结

只要拥有7、8函数表头,即可实现头插尾插,头删尾删

但是要注意phead节点的使用

//尾插节点
void ListPushBack(ListNode* phead, LTDataType x)
{

	ListInsert(phead, x);

}

//头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	
	ListInsert(phead->next, x);
}

//尾删
void ListPopBack(ListNode* phead)
{
	
	ListErase(phead->prev);
}

//头删
void ListPopFront(ListNode* phead)
{

	ListErase(phead->next);

}


顺序表和链表的优缺点对比

(双向带头循环链表)

  • 顺序表优点:

1、按下标去进行随机访问

2、 CPU高速缓存命中率较高

  • 顺序表缺点:

1、 空间不够需要增容(一定程序的性能消耗),可能存在一定的空间浪费

2、 头部或中间插入删除数据,需要挪动数据,效率比较低->O(N)

  • 链表优点:

1、 按需申请内存,需要存一个数据,就申请一块内存,不存在浪费

2、 任意位置O(1) 时间内插入删除数据

  • 链表缺点:

1、 不支持下标的随机访问

总结: 这两数据结构是相辅相成的,互相弥补对方的缺点,需要用谁存数据,具体看场景

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 11:07:37  更:2022-08-06 11:08:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:47:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码