2023/8/8 -- 数据结构

 作业:

 约瑟夫环问题:

#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 概念

  1. 栈的概念:操作受限的线性表,对该容器的插入和删除操作只能在同一端进行
  2. 栈顶:能够操作的一端被称为栈顶
  3. 栈底:不能被操作的一端,称为栈底
  4. 栈的特点:先进后出(FILO)、后进先出(LIFO)
  5. 分类:顺序栈、链式栈
  6. 基本操作:创建栈、判空、判满、入栈、出栈、获取栈顶元素、求栈的大小、遍历栈、销毁栈

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 链式栈

链式栈就是链式存储的栈

实现方式:可以使用单向链表完成

  1. 对单向链表进行头插(入栈)、头删(出栈),此时链表的头部就是链栈的栈顶,链表的尾部,就是链栈的栈底
  2. 对单向链表进行尾插(入栈)、尾删(出栈),此时链表的头部就是栈底,链表的尾部就是栈顶

由于上述实现方式中,第二种方式需要每次遍历到队尾进行操作,不方便,所以一般使用第一种方式完成

三、队列

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");

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值