嵌入式入门Day22

数据结构Day3

双向链表

由于单向链表只能从某个节点开始单向访问其后继节点,并没有储存其前驱节点信息。访问前面的节点是不容易办到的

为了能够凑某个节点开始,既可以访问前驱节点又可以访问后继节点,在结构中多家一个指向前驱节点的指针

c 复制代码
typedef struct Node
{
	union
	{
		datatype data;
		int len;
	}
	struct Node *prio;
	struct NOde *next;
}Node, *Node_ptr;

创建双向链表

c 复制代码
Node_ptr list_create()
{
	//堆区申请空间
	Node_ptr L = (Node_ptr)malloc(sizeof(Node));
	if (NULL == L)
	{
		printf("链表创建失败\n");
		return NULL;
	}
	//初始化
	L->len = 0;
	L->prio = NULL;
	L->next = NULL;

	printf("链表创建成功\n");

	return L;
}

双向链表判空

c 复制代码
//判空 1表示空 0表示非空
int list_empty(Node_ptr L)
{
	//判断合法性
	if (NULL == L)
	{
		return 1;	
	}
	//判断是否为空
	return L->next == NULL;
}

申请节点封装数据

c 复制代码
//申请节点封装数据
static Node_ptr list_node_apply(datatype e)
{
	//堆区申请空间
	Node_ptr p = (Node_ptr)malloc(sizeof(Node));
	if (NULL == p)
	{
		return NULL;
	}
	
	//数据封装
	p->data = e;
	p->next = NULL;
	p->prio = NULL;

	return p;
}

双向链表头插

c 复制代码
//头插
int list_insert_head(Node_ptr L, datatype e)
{
	//判断链表合法性
	if (NULL == L)
	{
		printf("链表不合法\n");
		return -1;
	}
	//申请节点封装数据
	Node_ptr p = list_node_apply(e);
	if (NULL == p)
	{
		return -1;
	}

	//判断链表是否为空
	if (list_empty(L))
	{
		//空双向链表头插
		L->next = p;
		p->prio = L;
	}else
	{
		//非空双向链表头插
		p->prio = L;
		p->next = L->next;
		L->next->prio = p;
		L->next = p;
	}
	L->len++;
	return 0;
}

双向链表遍历

c 复制代码
//显示
void list_show(Node_ptr L)
{
	if (NULL == L || list_empty(L))
	{
		return ;
	}
	printf("链表中的元素为:");
	Node_ptr p = L->next;
	while (p)
	{
		printf("%c\t",p->data);
		p = p->next;
	}
	putchar(10);
	return;
}

按位置查找返回节点地址

c 复制代码
//按位置寻找节点
Node_ptr list_search_pos(Node_ptr L, int pos)
{
	if (NULL == L || list_empty(L) || pos < 0 || pos > L->len)
	{
		return NULL;
	}

	Node_ptr q = L;
	for (int i = 0; i < pos; i++)
	{
		q = q->next;
	}
	return q;
}

指定位置插入

  1. 插入时,可以找到其前驱节点后插入,也可以找到要插入的位置直接插入
  2. 尾插是需要注意,不要访问到NULL
c 复制代码
//按位置插入
int list_insert_pos(Node_ptr L, int pos, datatype e)
{
	//检查链表和插入位置
	if (NULL == L || pos < 1 || pos > L->len+1)
	{
		return -1;
	}
	//定位到插入位置的前一个节点
	Node_ptr q = list_search_pos(L, pos-1);
	//申请节点封装数据
	Node_ptr p = list_node_apply(e);
	if (NULL == p)
	{
		return -1;
	}
	//判断是中间插入还是尾插
	if (q->next == NULL)
	{
		p->prio = q;
		q->next = p;
	}else
	{
		p->prio = q;
		p->next = q->next;
		q->next->prio = p;
		q->next = p;
	}
	//表长变化
	L->len++;
	return 0;
}

头删

  1. 注意删除的节点是否为最后一个节点,还是不要访问到关于NULL的空间
c 复制代码
//头删
int list_delete_head(Node_ptr L)
{
	//判断链表合法性
	if (NULL == L || list_empty(L))
	{
		return -1;
	}
	//标记将要删除的节点
	Node_ptr p = L->next;

	//将头指针的后继修改为标记节点的后继
	L->next = p->next;

	//如果该节点后继不为空
	if (NULL !=  p->next)
	{
		//将他的前驱修改为头结点
		p->next->prio = L;
	}
	//释放内存空间
	free(p);
	//释放指针
	p = NULL;
	//表长变化
	L->len--;
	return 0;
}

指定位置删除

  1. 可以找到制定位置的前驱进行删除,也可以直接定位到该位置进行删除
  2. 注意删除的是否为最后一个节点,如果时依旧不能访问到NULL
c 复制代码
//按位置删除
int list_delete_pos(Node_ptr L, int pos)
{
	//判断链表和位置的合法性
	if (NULL == L || pos < 1 || pos > L->len)
	{
		return -1;
	}
	//标记指针
	Node_ptr p = list_search_pos(L,pos);
	//将该节点前驱的后继指向该节点的后继
	p->prio->next = p->next;
	//如果该节点后继不为空
	if (NULL != p->next)
	{
		//将该节点后继的前驱指向该节点的前驱
		p->next->prio = p->prio;
	}
	//释放空间
	free(p);
	//释放指针  
	p = NULL;
	//表长变化
	L->len--;
	return 0;

}

按位置修改数据

c 复制代码
//按位置更新
int list_update_pos(Node_ptr L, int pos, datatype e)
{
	//合法性判断
	if (NULL == L || list_empty(L) || pos < 1 || pos > L->len)
	{
		return -1;
	}
	//定位到待修改节点
	Node_ptr p = list_search_pos(L,pos);
	//更改数据域
	p->data = e;
	return 0;
}

销毁双向链表

c 复制代码
//销毁双向链表
void list_destroy(Node_ptr *L)
{
	if (NULL == *L)
	{
		return;
	}
	while (!list_empty(*L))
	{
		list_delete_head(*L);
	}
	free(*L);
	*L = NULL;
}

循环链表

  1. 循环链表:首尾相接的链表称为循环链表
    特点:在循环链表中,没有一个节点的指针域为空
  2. 分类:
    • 单向循环链表:将单链表的最后一个节点的指针域指向第一个节点的地址
    • 双向循环链表:将头结点的前驱指向最后一个节点,最后一个节点的后继指向第一个节点

循环链表的操作(以单链表为例)

单向循环链表的节点结构体

c 复制代码
typedef char datatype;

typedef struct Node
{
	union 
	{
		int len;   			//头节点数据域
		datatype data; 		//普通节点数据域
	};

	struct Node *next; 		//指针域
}Node, *Node_ptr;

循环链表的创建

c 复制代码
//创建循环链表
Node_ptr list_create()
{
	//申请头节点空间
	Node_ptr p = (Node_ptr)malloc(sizeof(Node));
	if (NULL == p)
	{
		return NULL;
	}
	//头节点初始化
	p->len = 0;
	p->next = p;
	printf("创建成功\n");
	return p;
}

循环链表判空

c 复制代码
int list_empty(Node_ptr L)
{
	//指向头节点指向自己表示循环链表为空
	if (L == L->next || NULL == L)
	{
		return 1;
	}else
	{
		return 0;
	}
}

循环链表尾插

c 复制代码
//尾插
int list_insert_tail(Node_ptr L, datatype e)
{
	//判断链表合法性
	if (NULL == L)
	{
		return -1;
	}
	//定位到链表的最后一个节点
	Node_ptr q = L;
	while (q->next != L)
	{
		q = q->next;
	}
	//申请空间封装数据
	Node_ptr p = (Node_ptr)malloc(sizeof(Node));
	if (NULL == p)
	{
		return -1;
	}
	//尾插逻辑
	p->data = e;
	p->next = NULL;
	p->next = q->next;
	q->next = p;
	
	return 0;
}

带头节点的循环链表遍历

c 复制代码
//遍历
void list_show(Node_ptr L)
{
	//判断链表是否满足遍历条件
	if (NULL == L || list_empty(L))
	{
		return ;
	}
	printf("链表中的元素分别为:");
	//从链表的第一个节点开始
	Node_ptr q = L->next;
	while (q != L)
	{
		//输出每一个节点的数据域
		printf("%c\t",q->data);
		q = q->next;
	}
	putchar(10);

}

循环链表的尾删

c 复制代码
//尾删
int list_delete_tail(Node_ptr L)
{
	//判断链表是否符合尾删条件
	if (NULL == L || list_empty(L))
	{
		return -1;
	}
	//定位到倒数第二个节点
	Node_ptr q = L;
	while (q->next->next != L)
	{
		q = q->next;
	}
	//释放最后一个节点的内存空间
	free(q->next);
	//当前节点的后继修改为头节点
	q->next = L;
	//长度变化
	L->len--;

}

循环链表删除头节点

c 复制代码
//删除头结点
Node_ptr list_head_delete(Node_ptr L)
{	
	//判断链表合法性
	if (NULL == L)
	{
		return NULL;
	}
	//链表判空
	if (list_empty(L))
	{
		//为空,直接释放L头节点,并返回NULL
		free(L);
		return NULL;
	}
	//不为空,将最后一个节点后继修改为第一个节点(以前是头节点)
	Node_ptr q = L->next;
	while (q->next != L)
	{
		q = q->next;
	}
	q->next = L->next;
	//释放头节点
	free(L);
	L = NULL;
	//返回一个头指针,用于使用链表
	return q->next;
}

使用头指针的循环链表遍历

c 复制代码
//去头后遍历链表
void list_dseplay(Node_ptr H)
{
	if (NULL == H)
	{
		return ;
	}
	printf("链表中的元素分别为:");
	//使用头指针时,H就已经是第一个元素了
	Node_ptr q = H;
	//先输出H元素的数据,后移直到重新回到H节点 
	do
	{
		printf("%c\t",q->data);
		q = q->next;
	} while (q != H);
	putchar(10);
}

删除循环链表

c 复制代码
//销毁
void list_destroy(Node_ptr H)
{	
	//判断
	if (NULL == H)
	{
	 	return;
	}
	while (H->next != H)
	{
		Node_ptr p = H->next;
		H->next = p->next;
		free(p);
		p = NULL;
	}

	free(H);
	H = NULL;
	return;
}	

作业

约瑟夫问题

c 复制代码
#include "looplinklist.h"
int main(int argc, const char *argv[])
{
	//创建一个循环链表
	Node_ptr L = list_create();
	//控制循环变量
	int n;
	//用户提示
	printf("请输入参与游戏的人数:");
	//输入 
	scanf("%d", &n);
	//循环尾插
	for (int i = 0; i < n; i++)
	{
		list_insert_tail(L,i+1);
	}
	//展示链表中的数据
	list_show(L);
	//删除头节点
	L = list_head_delete(L);	

	//定义遍历指针
	Node_ptr p = L;

	//定义计数标志
	int count = 0;

	printf("出列顺序为:");
	//循环到只有最后一个节点
	while (p->next != p)
	{
		//计数增加
		count++;
		//每计数到二进行头删操作,删除第三个节点
		if (count == 2)
		{
			printf("%d\t",p->next->data);
			Node_ptr q = p->next;
			p->next = q->next;
			free(q);
			q = NULL;
			count = 0;
		}
		//遍历指针后移
		p = p->next;		
	}
	//输出最后一个节点
	printf("%d\n",p->data);
	//销毁循环链表
	list_destroy(L);
	//释放指针
	L = NULL;
	//展示链表
	list_show(L);
	return 0;
}

运行结果如下

相关推荐
梦.清..11 分钟前
C语言——指针基础
c语言·开发语言
禾风wyh26 分钟前
【PyTorch】回归问题代码实战
python·算法·机器学习
kikyo哎哟喂43 分钟前
数据结构---图
数据结构
龙的爹23331 小时前
论文 | LazyLLM: DYNAMIC TOKEN PRUNING FOR EFFICIENTLONG CONTEXT LLM INFERENCE
人工智能·深度学习·算法·机器学习·自然语言处理·prompt·剪枝
我是哈哈hh1 小时前
专题二十四_贪心策略(2)_算法专题详细总结
数据结构·c++·算法·leetcode·贪心算法·贪心
程序猿小柒1 小时前
leetcode hot100【Leetcode 72.编辑距离】java实现
java·算法·leetcode
多吃轻食1 小时前
大模型开发和微调工具Llama-Factory-->训练方法(SFT, RLHF, DPO, KTO)
人工智能·深度学习·算法·自然语言处理·llama
Ws_2 小时前
leetcode LCP 开幕式焰火
开发语言·数据结构·python·算法·leetcode
纪怽ぅ2 小时前
深入傅里叶级数与傅里叶变换:从基础到应用
python·算法·机器学习·优化算法·傅里叶变化
tigerffff2 小时前
leetcode每日一题(20241203)
java·数据结构·算法·leetcode