嵌入式入门Day22
数据结构Day3
- 双向链表
- 创建双向链表
- 双向链表判空
- 申请节点封装数据
- 双向链表头插
- 双向链表遍历
- 按位置查找返回节点地址
- 指定位置插入
- 头删
- 指定位置删除
- 按位置修改数据
- 销毁双向链表
- 循环链表
- 循环链表的操作(以单链表为例)
- 单向循环链表的节点结构体
- 循环链表的创建
- 循环链表判空
- 循环链表尾插
- 带头节点的循环链表遍历
- 循环链表的尾删
- 循环链表删除头节点
- 使用头指针的循环链表遍历
- 删除循环链表
- 作业
双向链表
由于单向链表只能从某个节点开始单向访问其后继节点,并没有储存其前驱节点信息。访问前面的节点是不容易办到的
为了能够凑某个节点开始,既可以访问前驱节点又可以访问后继节点,在结构中多家一个指向前驱节点的指针
typedef struct Node
{
union
{
datatype data;
int len;
}
struct Node *prio;
struct NOde *next;
}Node, *Node_ptr;
创建双向链表
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;
}
双向链表判空
//判空 1表示空 0表示非空
int list_empty(Node_ptr L)
{
//判断合法性
if (NULL == L)
{
return 1;
}
//判断是否为空
return L->next == NULL;
}
申请节点封装数据
//申请节点封装数据
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;
}
双向链表头插
//头插
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;
}
双向链表遍历
//显示
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;
}
按位置查找返回节点地址
//按位置寻找节点
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;
}
指定位置插入
- 插入时,可以找到其前驱节点后插入,也可以找到要插入的位置直接插入
- 尾插是需要注意,不要访问到NULL
//按位置插入
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;
}
头删
- 注意删除的节点是否为最后一个节点,还是不要访问到关于NULL的空间
//头删
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;
}
指定位置删除
- 可以找到制定位置的前驱进行删除,也可以直接定位到该位置进行删除
- 注意删除的是否为最后一个节点,如果时依旧不能访问到NULL
//按位置删除
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;
}
按位置修改数据
//按位置更新
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;
}
销毁双向链表
//销毁双向链表
void list_destroy(Node_ptr *L)
{
if (NULL == *L)
{
return;
}
while (!list_empty(*L))
{
list_delete_head(*L);
}
free(*L);
*L = NULL;
}
循环链表
- 循环链表:首尾相接的链表称为循环链表
特点:在循环链表中,没有一个节点的指针域为空 - 分类:
- 单向循环链表:将单链表的最后一个节点的指针域指向第一个节点的地址
- 双向循环链表:将头结点的前驱指向最后一个节点,最后一个节点的后继指向第一个节点
循环链表的操作(以单链表为例)
单向循环链表的节点结构体
typedef char datatype;
typedef struct Node
{
union
{
int len; //头节点数据域
datatype data; //普通节点数据域
};
struct Node *next; //指针域
}Node, *Node_ptr;
循环链表的创建
//创建循环链表
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;
}
循环链表判空
int list_empty(Node_ptr L)
{
//指向头节点指向自己表示循环链表为空
if (L == L->next || NULL == L)
{
return 1;
}else
{
return 0;
}
}
循环链表尾插
//尾插
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;
}
带头节点的循环链表遍历
//遍历
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);
}
循环链表的尾删
//尾删
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--;
}
循环链表删除头节点
//删除头结点
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;
}
使用头指针的循环链表遍历
//去头后遍历链表
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);
}
删除循环链表
//销毁
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;
}
作业
约瑟夫问题
#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;
}
运行结果如下