作业:
约瑟夫环问题:
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct Node {
datatype data;
struct Node* next;
}Node,*Looplink;
Looplink createCircularLinkedList(int n) {
Looplink head = NULL;
Looplink prev = NULL;
for (int i = 1; i <= n; i++) {
Looplink newNode = (Looplink)malloc(sizeof(Node));
newNode->data = i;
if (head == NULL) {
head = newNode;
} else {
prev->next = newNode;
}
prev = newNode;
}
prev->next = head; // 将最后一个节点的next指针指向头节点,形成循环
return head;
}
//约瑟夫环问题
void josephusCircle(Looplink *head, int start, int step) {
Looplink curr = *head;
Looplink prev = NULL;
// 找到起始位置的节点
while (curr->data != start) {
prev = curr;
curr = curr->next;
}
// 开始循环删除节点,直到只剩下一个节点
while (curr->next != curr) {
// 找到要删除的节点
for (int i = 1; i < step; i++) {
prev = curr;
curr = curr->next;
}
// 删除节点
prev->next = curr->next;
Looplink temp = curr;
curr = curr->next;
free(temp);
}
*head = curr; // 更新头节点
}
int main() {
int n, start, step;
printf("请输入总人数:");
scanf("%d", &n);
printf("请输入起始位置:");
scanf("%d", &start);
printf("请输入步长:");
scanf("%d", &step);
Looplink head = createCircularLinkedList(n);
josephusCircle(&head, start, step);
printf("最后剩下的节点为:%d\n", head->data);
return 0;
}
进制转换:
void stack_turn(StackPtr S,datatype e)
{
if(NULL==S)
{
printf("转换失败\n");
return;
}
int num=0;
while(e!=0)
{
num=e%2;
e=e/2;
stack_push(S,num);
}
}
一、循环链表
1.1 概念
循环链表,就是首尾相连的链表,通过任意一个节点,都能将整个链表遍历一遍
分类:单向循环链表、双向循环链表
1.2 单向循环链表节点结构体
typedef int datatype;
//定义结点结构体类型
typedef struct Node
{
union
{
datatype data; //普通结点数据域
int len; //头结点数据域
};
struct Node *next; //指针域
}Node, *LoopLink;
1.3 创建循环链表
1> 核心逻辑:在堆区申请一个头结点的空间,将数据域置空,指针域指向自身
2> 返回值:成功返回头结点的指针,失败返回NULL
3> 参数:无
//创建循环链表
LoopLink list_creat()
{
//堆区申请一个头结点
LoopLink L = (LoopLink)malloc(sizeof(Node));
if(NULL == L)
{
printf("创建失败\n");
return NULL;
}
//初始化
L->len = 0;
L->next = L; //指针域执行自己
printf("创建成功\n");
return L;
}
1.4 判空
1> 核心逻辑:头结点的指针域是否执行头结点
2> 返回值:成功返回真,失败返回假
3> 参数:循环链表
int list_empty(LoopLink L)
{
if(NULL != L)
{
return L->next == L;
}
printf("链表不合法\n");
return -1; //链表不合法
}
1.5 尾插
1> 核心逻辑:找到最后一个节点,将新节点的指针域指向头结点,最后一个节点的指针域指向新节点
2> 返回值:成功返回真,失败返回假
3> 参数:循环链表、要插入的元素
int list_insert_tail(LoopLink L, datatype e)
{
//判断逻辑
if(NULL==L)
{
printf("所给链表不合法\n");
return 0;
}
//申请结点封装数据
LoopLink p = (LoopLink)malloc(sizeof(Node));
if(NULL==p)
{
printf("结点申请失败\n");
return 0;
}
//将数据封装到结点中
p->data = e;
p->next = NULL;
//遍历找到最后一个结点
LoopLink q = L;
while(q->next != L) //最后一个结点特点:后继节点为头结点
{
q = q->next;
}
//尾插操作
p->next = L; //p->next = q->next;
q->next = p;
//表长变化
L->len++;
printf("插入成功\n");
return 1;
}
1.6 遍历
1> 核心逻辑:从第一个节点出发,只要没有回到头结点,将遍历过程中的每个节点进行输出数据域
2> 参数:链表
3> 返回值:无
void list_show(LoopLink L)
{
//判断逻辑
if(NULL==L || list_empty(L))
{
printf("遍历失败\n");
return ;
}
printf("链表中的元素分别是:");
//定义遍历指针,从第一个结点出发
LoopLink q = L->next;
while(q != L)
{
printf("%d\t", q->data);
q = q->next;
}
printf("\n");
}
1.7 尾删
1> 核心逻辑:先找到倒数第二个节点,然后释放最后一个节点,将倒数第二个节点的指针域指向头结点
2> 返回值:成功返回真,失败返回假
3> 参数:循环链表
int list_delete_tail(LoopLink L)
{
//判断逻辑
if(NULL==L || list_empty(L))
{
printf("删除失败\n");
return 0;
}
//遍历链表找到倒数第二个结点
LoopLink q = L->next;
while(q->next->next != L)
{
q = q->next;
}
//尾删操作
free(q->next); //将最后一个结点释放
q->next = L; //将倒数第二个结点的指针域指向头结点
//表长变化
L->len--;
printf("删除成功\n");
return 1;
}
1.8 销毁
1> 核心逻辑:先将所有的节点进行释放,最后将头结点的空间释放
2> 返回值:无
3> 参数:链表
void list_free(LoopLink L)
{
//判断逻辑
if(NULL==L)
{
printf("销毁失败\n");
return ;
}
//如果不为空,就将所有结点释放
while(!list_empty(L))
{
list_delete_tail(L);
}
//将头结点释放
free(L);
L = NULL;
printf("释放成功\n");
return ;
}
二、栈
2.1 概念
- 栈的概念:操作受限的线性表,对该容器的插入和删除操作只能在同一端进行
- 栈顶:能够操作的一端被称为栈顶
- 栈底:不能被操作的一端,称为栈底
- 栈的特点:先进后出(FILO)、后进先出(LIFO)
- 分类:顺序栈、链式栈
- 基本操作:创建栈、判空、判满、入栈、出栈、获取栈顶元素、求栈的大小、遍历栈、销毁栈
2.2 顺序栈
1> 顺序栈的概念:顺序存储的栈叫顺序栈
2> 特点:使用一片连续的存储空间,来存储一个栈,但是,除了存储栈的容器外,还需要一个记录栈顶元素的下标的变量
3> 结构体类型
#define MAX 8
typedef int datatype;
typedef struct
{
datatype *data; //指向堆区空间,用于存储栈的容器
int top; //记录栈顶元素的下标
}Stack, *StackPtr;
i) 创建栈
1> 核心逻辑:在堆区申请一个栈的空间,需要再给栈的指针申请一个堆区空间,用来存储栈
2> 返回值:堆区栈的首地址,失败返回NULL
3> 参数:无
//创建栈
StackPtr stack_create()
{
//在堆区申请一个顺序栈的大小
StackPtr S = (StackPtr)malloc(sizeof(Stack));
if(NULL == S)
{
printf("栈申请失败");
return NULL;
}
//给栈中的连续空间申请内容
S->data = (datatype *)malloc(sizeof(datatype)*MAX);
if(NULL==S->data)
{
printf("申请失败\n");
free(S); //数组空间申请失败,整个栈就没意义了
return NULL;
}
//初始化
S->top = -1; //表明现在是一个空栈
printf("创建成功\n");
return S;
}
ii)判空、判满
1> 核心逻辑:判断记录栈顶元素的下标是否为-1或者为MAX-1
2> 返回值:成功返回真,失败返回假
3> 参数:顺序栈
//判空 空返回真,非空返回假
int stack_empty(StackPtr S)
{
if(NULL != S) //判断逻辑
{
return S->top == -1;
}
printf("所给链表不合法\n");
return -1;
}
//判满 满返回真 非满返回假
int stack_full(StackPtr S)
{
//判断逻辑
if(NULL != S)
{
return S->top == MAX-1;
}
printf("所给链表不合法\n");
return -1;
}
iii)入栈
1> 核心操作:先加后压,先将栈顶后移,然后将数据放入栈中
2> 返回值:成功返回真,失败返回假
3> 参数:栈的地址、要入栈的元素
int stack_push(StackPtr S, datatype e)
{
//判断逻辑
if(NULL==S || stack_full(S))
{
printf("入栈失败\n");
return 0;
}
//先加后压
S->top++;
S->data[S->top] = e; //将数据存放到栈中
printf("入栈成功\n");
return 1;
}
iv)遍历栈
1> 核心逻辑:类似于数组的遍历
2> 返回值:无
3> 参数:顺序栈
void stack_show(StackPtr S)
{
//判断逻辑
if(NULL==S || stack_empty(S))
{
printf("遍历失败\n");
return ;
}
//开始遍历
printf("从栈顶到栈底元素分别是:");
for(int i=S->top; i>=0; i--)
{
printf("%d\t", S->data[i]);
}
printf("\n");
}
v)出栈
1、核心逻辑:先弹后减,先将数据弹出、然后,记录栈顶元素下标的变量自减
2、返回值:成功返回真,失败返回假
3、参数:栈的地址
int stack_pop(StackPtr S)
{
//判断逻辑
if(NULL==S ||stack_empty(S))
{
printf("出栈失败\n");
return 0;
}
//出栈逻辑:先弹后减
datatype e = S->data[S->top];
printf("%d出栈成功\n", e);
S->top--;
return 1;
}
vi)获取栈顶元素
1、核心逻辑:需要返回栈顶所在元素的地址,返回值是一个左值
2、返回值:栈顶元素的指针
3、参数:顺序栈
datatype * stack_top(StackPtr S)
{
//判断逻辑
if(NULL==S || stack_empty(S))
{
printf("获取失败\n");
return NULL;
}
//可以获取,将栈顶元素的地址返回
return &S->data[S->top];
}
vii)销毁栈
1、核心逻辑:需要先将存储栈的动态数组内存释放,然后再将栈的内存释放
2、返回值:无
3、参数:顺序栈
void stack_free(StackPtr S)
{
//判断逻辑
if(NULL==S)
{
printf("释放失败\n");
return;
}
//将动态数组空间释放
free(S->data);
S->data = NULL;
//将整个栈释放
free(S);
S = NULL;
printf("释放成功\n");
return ;
}
2.3 链式栈
链式栈就是链式存储的栈
实现方式:可以使用单向链表完成
- 对单向链表进行头插(入栈)、头删(出栈),此时链表的头部就是链栈的栈顶,链表的尾部,就是链栈的栈底
- 对单向链表进行尾插(入栈)、尾删(出栈),此时链表的头部就是栈底,链表的尾部就是栈顶
由于上述实现方式中,第二种方式需要每次遍历到队尾进行操作,不方便,所以一般使用第一种方式完成
三、队列
3.1 概念
1> 队列:操作受限的线性表,其插入和删除槽在表的异端进行,而且只能在端点处进行操作
2> 特点:先进先出(FIFO)
3> 队头:能够被删除的一端称为队头
4> 队尾:能够插入的一端称为队尾
5> 种类:顺序队列、链式队列
3.2 顺序队列
1> 顺序存储的队列称为顺序队列
2> 队列的组成:一个连续存储的内存,用于存放整个队列中的元素;有两个变量分别记录对头和队尾所在的元素下标
3> 假溢满现象:
3.3 循环顺序队列
1> 引入目的:为了解决普通顺序队列假溢满现象
2> 队列结构体类型
typedef int datatype;
typedef struct
{
datatype data[MAX]; //存储队列的数组容器
int front; //记录对头所在元素的下标
int tail; //记录最后一个元素的下一个位置的下标
}QUeue, *QueuePtr;
i)创建循环队列
1、核心逻辑:在堆区申请一个队列的大小,初始化时,将队头和队尾指向同一个位置即可,该位置可以是数组的任意一个位置,一般为0
2、返回值:成功返回申请的队列地址,失败返回NULL
3、参数:无
QueuePtr queue_create()
{
//创建一个循环顺序队列
QueuePtr Q = (QueuePtr)malloc(sizeof(Queue));
if(NULL == Q)
{
printf("队列创建失败\n");
return NULL;
}
//初始化
Q->front = Q->tail = 0;
printf("队列创建成功\n");
return Q;
}
ii)判空、判满
1、核心逻辑:判空是判断队头和队尾是否在同一个位置,判满,是判断队头和队尾是否相差1个空间
2、返回值:成功返回真,失败返回假
3、参数:队列
//判空
int queue_empty(QueuePtr Q)
{
//判断逻辑
if(NULL == Q)
{
printf("所给队列不合法\n");
return -1;
}
return Q->front == Q->tail;
}
//判满
int queue_full(QueuePtr Q)
{
//判断逻辑
if(NULL == Q)
{
printf("所给队列不合法\n");
return -1;
}
return (Q->tail+1)%MAX == Q->front;
}
iii)入队
1、核心操作:将数据放入队尾所在位置,然后队尾后移,注意后移时的特殊位置
2、返回值:成功返回真,失败返回假
3、参数:队列、要入队的元素
//入队
int queue_push(QueuePtr Q, datatype e)
{
//判断逻辑
if(NULL == Q || queue_full(Q))
{
printf("入队失败\n");
return 0;
}
//入队逻辑:将数据放在队尾所在地方
Q->data[Q->tail] = e;
//队尾后移
//Q->tail++;? Q->tail = Q->tail + 1;
Q->tail = (Q->tail+1)%MAX;
printf("入队成功\n");
return 1;
}
iv)遍历
1、核心逻辑:从队头到队尾将数据遍历一遍
2、参数:队列
3、返回值:无
//遍历
void queue_show(QueuePtr Q)
{
//判断逻辑
if(NULL==Q || queue_empty(Q))
{
printf("遍历失败\n");
return ;
}
//开始遍历
printf("从队头到队尾元素分别是:");
for(int i=Q->front; i!=Q->tail; i=(i+1)%MAX )
{
printf("%d\t", Q->data[i]);
}
printf("\n");
}
v)出队
1、核心逻辑:将队头所在的位置元素删除,然后队头后移,后移时注意特殊位置
2、参数:队列
3、返回值:成功返回真,失败返回假
int queue_pop(QueuePtr Q)
{
//判断逻辑
if(NULL==Q || queue_empty(Q))
{
printf("出队失败\n");
return 0;
}
//出队逻辑
printf("%d出队成功\n", Q->data[Q->front]);
//队头后移
Q->front = (Q->front+1)%MAX;
return 1;
}
vi) 求队列长度(时间复杂度常量级)
1、核心操作:需要使用队头和队尾所在位置进行数据运算,不得使用循环
2、参数:队列
3、返回值:队列的长度
int queue_size(QueuePtr Q)
{
//判断逻辑
if(NULL==Q)
{
printf("所给链表不合法\n");
return -1;
}
return (Q->tail + MAX - Q->front)%MAX;
}
vii)释放队列
void queue_free(QueuePtr Q)
{
//判断逻辑
if(NULL != Q)
{
free(Q);
Q =NULL;
printf("释放成功\n");
return;
}
printf("所给队列不合法\n");
return;
}
3.4 链式队列
1> 结构体类型
需要构造节点结构体类型,还需要构造队列结构体类型,队列结构体类型中,包含两个节点类型的结构体指针
typedef int datatype;
//定义结点类型
typedef struct Node
{
union
{
datatype data;
int len;
};
struct Node *next;
}Node;
//定义链队
typedef struct
{
Node * Head; //记录队列的头部
Node * Tail; //记录队列的尾部
}LinkQueue, *LinkQueuePtr;
2> 创建
i)创建链式队列
1、需要在堆区中创建一个队列,此时会产生两个野指针,然后在申请一个头结点,让两个指针都指向该节点
2、参数:无
3、返回值:成功返回创建的队列地址,失败返回NULL
LinkQueuePtr list_create()
{
//创建一个队列
LinkQueuePtr LQ = (LinkQueuePtr)malloc(sizeof(LinkQueue));
if(NULL == LQ)
{
printf("创建失败\n");
return NULL;
}
//创建成功的话,说明里面有两个指针
//LQ->Head LQ->Tail 这两个是野指针
//申请一个链表,将Head指针指向头结点
LQ->Head = (Node *)malloc(sizeof(Node));
if(NULL == LQ->Head)
{
printf("创建失败\n");
return NULL;
}
//成功后需要对链表初始化
LQ->Head->len = 0;
LQ->Head->next = NULL;
//将尾指针指向头结点
LQ->Tail = LQ->Head;
printf("队列创建成功\n");
return LQ;
}
ii)判空
1、核心逻辑:判断队头指针和队尾指针是否指向同一个节点(头结点)
2、参数:队列
3、返回值:成功返回真,失败返回假
int list_empty(LinkQueuePtr LQ)
{
//判断逻辑
if(NULL==LQ || NULL==LQ->Head)
{
printf("所给队列不合法\n");
return -1;
}
return LQ->Head == LQ->Tail;
}
iii)入队
1、核心逻辑:申请出一个节点,封装数据后,将该节点连接到尾指针指向的节点后面,在更新一下尾指针
2、参数:队列、要入队的元素
3、返回值:成功返回真,失败返回假
int list_push(LinkQueuePtr LQ, datatype e)
{
//判断逻辑
if(NULL==LQ)
{
printf("所给队列不合法\n");
return 0;
}
//1、申请结点封装数据
Node *p = (Node*)malloc(sizeof(Node));
if(NULL==p)
{
printf("入队失败\n");
return 0;
}
//将数据封装到结点中
p->data = e;
p->next = NULL;
//2、将新结点连接到队尾指针指向的结点后面
LQ->Tail->next = p;
//3、更新尾指针
LQ->Tail = p;
//4、长度自增
LQ->Head->len++;
printf("入队成功\n");
return 1;
}
iv)出队
1、核心逻辑:其实就是担心链表的头删,注意:如果全部元素都出队后,需要将尾指针重新指向头结点
2、参数:队列
3、返回值:成功返回真,失败返回假
int list_pop(LinkQueuePtr LQ)
{
//判断逻辑
if(NULL==LQ || list_empty(LQ))
{
printf("出队失败\n");
return -1;
}
//1、标记要出队的结点
Node * p = LQ->Head->next;
//2、孤立要删除的结点
LQ->Head->next = p->next;
printf("%d出队成功\n", p->data);
//3、释放要删除的结点
free(p);
p = NULL;
//队伍长度自减
LQ->Head->len--;
//判断队伍中是否已经删除完所有结点
if(LQ->Head->next == NULL)
{
//将尾指针重新指向头结点
LQ->Tail = LQ->Head;
}
return 1;
}
v)销毁队伍
1、核心操作:需要将队伍中的所有节点全部出队,然后释放头结点,最后释放队伍空间
2、参数:队伍
3、返回值:无
//销毁队伍
void list_free(LinkQueuePtr LQ)
{
//判断逻辑
if(NULL==LQ)
{
printf("释放失败\n");
return ;
}
//1、释放整个链表
while(!list_empty(LQ))
{
list_pop(LQ);
}
//释放头结点
free(LQ->Head);
LQ->Head = LQ->Tail = NULL;
//2、释放队列
free(LQ);
LQ = NULL;
printf("释放成功\n");
}