苏嵌实训——day11

一、队列

1.1 队列的概念

队列的性质:先进先出(FIFO)
队列的操作
入队
出队

1.2 链式队列

为了实现队列的先进先出的原理,链式存储里面可以采用头插法和尾删法或者尾插法和头删法实现功能,但是尾插和尾删都需要将链表遍历一次,效率比较低
所以,我们可以定义两个指针变量,一个front保存头结点的地址,另一个rear保存最后一个结点的地址
入队操作时将新结点插入到rear对应的结点的后面,然后rear保存最后一个结点的地址,出队操作就是将头结点后面的结点删除。
在这里插入图片描述

1.2.1 linkqueue.h

//头文件
#ifndef _LINKQUEUE_H_
#define _LINKQUEUE_H_
#include <stdio.h>
#include <stdlib.h>

typedef int DataType;
typedef struct node  //定义结点结构体
{
    DataType data;
    struct node *next;
}node;

typedef struct  //定义结构体便于返回操作链式队列的两个指针
{
    node *front;  //保存头结点地址
    node *rear;   //每次保存最后一个结点的地址
}linkqueue;
//创建一个空的队列
linkqueue *LinkQueueCreate();
//入队
void LinkQueueInput(linkqueue *q, DataType value);
//出队
DataType LinkQueueOutput(linkqueue *q);
#endif

1.2.2 linkqueue.c

//linkqueue.c
#include "linkqueue.h"
//创建一个空的队列
linkqueue *LinkQueueCreate()
{
    //创建两个指针
    linkqueue *q = (linkqueue *)malloc(sizeof(linkqueue));
    //创建一个头结点并且让next指针指向NULL
    q->front = (node *)malloc(sizeof(node));
    q->rear = q->front;
    q->front->next = NULL;


    return q;
}


//入队
void LinkQueueInput(linkqueue *q, DataType value)
{
    //申请一个新的结点并且赋值
    node *tmp = (node *)malloc(sizeof(node));
    tmp->data = value;
    tmp->next = NULL;


    //将新节点插入到rear对应的结点的后面
    //rear结点的指针域保存新结点的地址
    q->rear->next = tmp;
    //新节点的指针域保存NULL
    tmp->next = NULL;  
    q->rear = tmp;
    return;
}
//出队
DataType LinkQueueOutput(linkqueue *q)
{
    if(q->front->next == NULL)
    {
        printf("队列为空!\n");
        return (DataType)-1;
    }
    //将front对应的结点后面的结点删除
    node *tmp = q->front->next;
    q->front->next = tmp->next;
    DataType value = tmp->data;

    free(tmp);
    tmp = NULL;
    //当最后一个结点删除之后,需要将rear指针指向头结点
    if(q->front->next == NULL)
    {
        q->rear = q->front;
    }
    return value;
}

1.3 顺序队列(循环队列)

在这里插入图片描述

1.3.1 sequeue.h

//头文件
#ifndef _SEQUEUE_H_
#define _SEQUEUE_H_
#include <stdio.h>
#include <stdlib.h>

#define NUM 32
#define N (NUM + 1)
typedef int DataType;
typedef struct{  //定义结构体
    DataType data[N];
    int front;  //保存出队的元素下标
    int rear;  //保存入队的位置
}sequeue;

//创建一个空的循环队列
//判断队列是否为满
//判断队列是否为空
//入队
//出队
#endif

1.3.2 sequeue.h

//功能函数
#include "sequeue.h"
//创建一个空的循环队列
sequeue* SequeueCreate()
{
    //申请空间
    sequeue *sq = (sequeue *)malloc(sizeof(sequeue));
    //初始状态让front和rear都保存0,标识为空
    sq->front = sq->rear = 0;


    return sq;
}
//判断队列是否为满
int SequeueIsFull(sequeue *sq)
{
    return (sq->rear + 1) % N == sq->front ? 1 : 0;
}
//判断队列是否为空
int SequeueIsEmpty(sequeue *sq)
{
    return sq->front == sq->rear ? 1 : 0;
}
//入队
void SequeueInput(sequeue *sq, DataType value)
{
    if(SequeueIsFull(sq))
    {
        printf("队列为满");
        return;
    }
    //将数据保存在rear对应的数组位置上
    sq->data[sq->rear] = value;
    //rear保存下一个元素的下标
    sq->rear = (sq->rear + 1) % N;
    return;
}
//出队
DataType SequeueOutput(sequeue *sq)
{
    if(SequeueIsEmpty(sq))
    {
        printf("队列为空,出队失败!\n");
        return (DataType)-1;
    }
    //定义临时变量保存要出队的数据
    DataType value = sq->data[sq->front];
    //front后移保存下一个要出队的数据的下标
    sq->front = (sq->front + 1) % N;
    return value;
}

1.3.3 main.c

#include "sequeue.h"
int main(int argc, char const *argv[])
{
    sequeue *sq = SequeueCreate();
    int i;
    for(i =0 ; i < 10;i++)
    {
        SequeueInput(sq,i + 1);
    }
    while(!SequeueIsEmpty(sq))
    {
        printf("出队:%d\n",SequeueOutput(sq));
    }
    return 0;
}

二、树和二叉树

2.1 概念

如果数据和数据之间满足一对多的关系,将其逻辑结构称之为树
最上层的第一个数据称之为根节点
如果一个结点有直接后继,将这些后继称之为子节点或子树,这个结点称之为这些子节点的父节点

度数
一个结点的子树的个数称为该结点的度数
一颗树的度数是指该树中结点最大度数
边数:
一个结点系列,k1…ki 满足ki是ki+1的父节点,就称为一条从k1到ki的路径,路径的长度为i-1,即路径的边数
层数
结点的层数等于父节点的层数加一,根节点的层数为1,树种结点层数的最大值称为该树的高度或者深度。
终端节点(叶子结点):
度数为零的节点称为树叶或终端节点,度数不为零的节点称为分支节 点,除根节点外的分支节点称为内部节点
在这里插入图片描述

树的逻辑结构:
树种任何结点都可以有多个或者0个直接后继结点(子节点)
但是至多只能有一个直接前驱(父节点),根节点没有前驱结点
叶子饥饿点没有后继结点。

2.2 二叉树

如果树种的每一个结点最多有两个,那么将这个树称为二叉树。
二叉树与普通树不同,二叉树严格区分左孩子和右孩子,即使只有一个子节点也要区分左右

满二叉树 :深度为k(k≥1)时有2^k-1个节点的二叉树。
完全二叉树 :只有最下面两层有度数小于2的节点,且最下面一层的
叶节点集中在最左边的若干位置上。

2.2.1 二叉树的性质

性质1:在二叉树的第i层至多有2^(i -1)个结点。
性质2:深度为K的二叉树最多有2^k - 1个结点。
性质3:对任何一个二叉树,如果其终端结点数为n,度为2的结点数为m,则:
n = m+1;
性质4:具有n个结点的完全二叉树,其深度为(log2n)+1或『log2(n+1)。
性质5
如果对1棵有n个饥饿点的二叉树的饥饿点按层序编号,对任何一个结点i
(1)如果i= 1,则结点i是二叉树的根,无双亲,如果,如果i > 1,则其双亲结点为 i/2 。
(2)如果2i > n,则结点无左孩子,否则,其左孩子为2i。
(3)如果2i+ 1 > n,则结点无右孩子,否则,其右孩子为2i+1.

2.2.2 二叉树的存储

完全二叉树的结点编号方法是从上到下,从左往右,根节点为1号结点
设完全二叉树的节点数为n,某结点编号为I
2i <=n,有左孩子,其编号为2i,否则没有左孩子,本身是叶子结点
2i+1 <=n,有右孩子,其编号为2i + 1,否则没有右孩子

2.3二叉树的存储方式

2.3.1 二叉树的顺序存储

因为无法保存当前二叉树是一个满二叉树或者完全二叉树,所以,顺序存储时,需要将不存在的结点预留位置,这样做就会浪费空间,所以一般不使用顺序存储

在这里插入图片描述

2.3.2 二叉树的链式存储

二叉树使用链式存储时,每一个结点需要定义一个结构体,里面至少有三个成员,分别是一个数据域和两个指针域组成,数据域保存数据,指针域分别保存左右子树的地址。
在这里插入图片描述

typedef int DataType;
typedef struct node   //定义二叉树的结点结构体
{
  DataType data;
  struct node *left;    //指向左孩子的指针
  struct node *right;    //指向右孩子的指针
}bitree_t;

2.4 二叉树的遍历

2.4.1 二叉树的遍历:

先序遍历:先访问树根,再访问左子树,最后访问右子树 (根左右)
中序遍历:先访问左子树,再访问树根,最后访问右子树(左根右)
后序遍历:先访问左子树,再访问右子树,最后访问树根(左右根)
在这里插入图片描述

反推:中序必须有,先序或者后序有一个就可以

在这里插入图片描述

在这里插入图片描述

2.4.2 代码

#ifndef _TREE_H_
#define _TREE_H_
#include <stdio.h>
#include <stdlib.h>


typedef int DataType;
typedef struct node   //定义二叉树的结点结构体
{
  DataType data;
  struct node *left;    //指向左孩子的指针
  struct node *right;    //指向右孩子的指针
}bitree_t;
//创建一棵二叉树
bitree_t *CreateTree(DataType *a,int length);
//先序遍历
void PreOrder(bitree_t *root);
//中序遍历
void MidOrder(bitree_t *root);
//后序遍历
void PostOrder(bitree_t *root);
#endif
#include "tree.h"
//创建一棵二叉树
bitree_t *CreateTree(DataType *a,int length)
{
    bitree_t *array[11] ={0};
    int i;
    for(i = 0; i < length;i++)
    {
        array[i] = (bitree_t *)malloc(sizeof(bitree_t));
        if(NULL == array[i])
        {
            return NULL;
        }
        array[i]->data = a[i];
        array[i]->left = NULL;
        array[i]->right = NULL;
    }
    for(i = 0 ; i < length /2;i++)
    {
        array[i]->left = array[2*i + 1];
        array[i]->right = array[2*i + 2];
    }
    return array[0];
}
//先序遍历
void PreOrder(bitree_t *root)
{
    if(NULL == root)
    {
        return;
    }
    printf("%d ",root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}
//中序遍历
void MidOrder(bitree_t *root)
{
    if(NULL == root)
    {
        return;
    }
    MidOrder(root->left);
    printf("%d ",root->data);
    MidOrder(root->right);
}
//后序遍历
void PostOrder(bitree_t *root)
{
    if(NULL == root)
    {
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ",root->data);
}
#include "tree.h"
int main(int argc, char const *argv[])
{
    DataType array[10] ={1,2,3,4,5,6,7,8,9,10};
    bitree_t *root1 = CreateTree(array,sizeof(array)/sizeof(array[0]));
    PreOrder(root1);
    putchar(10);
    MidOrder(root1);
    putchar(10);
    PostOrder(root1);
    putchar(10);
    return 0;
}

2.5 二叉搜索树

定义:二叉查找树,又被称为二叉搜索树,其特点,左孩子比父节点小,右孩子比父节点大。

//二叉搜索树
bitree_t* CreateBSTree(bitree_t *root,DataType num)
{
    if(NULL == root)
    {
        root = (bitree_t *)malloc(sizeof(bitree_t) * 1);
        if(NULL == root)
        {
            return NULL;
        }
        root->data = num;
        root->left = NULL;
        root->right = NULL;
    }
    else
    {
        if(root->data > num)
        {
            root->left = CreateBSTree(root->left,num);
        }
        else
        {
            root->right = CreateBSTree(root->right,num);
        }
    }
    return root;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的C语言万年历的实训报告,供您参考: 一、实训目的 本次实训的主要目的是通过编写一个简单的C语言万年历程序,巩固C语言基本语法和编程思想,提高编程能力和解决问题的能力。 二、实训要求 1、能够使用C语言基本语法和控制语句编写程序。 2、能够使用数组来存储和操作数据。 3、能够使用函数来实现模块化编程。 4、能够使用指针来操作内存中的数据。 5、能够使用文件来读取和写入数据。 三、实训内容 1、程序功能 本程序实现了一个简单的万年历功能,可以通过输入年份和月份来显示该月的日历。 2、程序实现 以下是本程序的主要代码实现: ```c #include <stdio.h> // 判断是否为闰年 int is_leap_year(int year) { if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { return 1; } else { return 0; } } // 获取某个月份的总天数 int get_days(int year, int month) { int days[] = {31, 28 + is_leap_year(year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return days[month - 1]; } // 获取某个日期是星期几 int get_weekday(int year, int month, int day) { if (month == 1 || month == 2) { year--; month += 12; } int c = year / 100; int y = year % 100; int w = y + y / 4 + c / 4 - 2 * c + 26 * (month + 1) / 10 + day - 1; w = (w % 7 + 7) % 7; return w; } // 显示日历 void show_calendar(int year, int month) { int days = get_days(year, month); int weekday = get_weekday(year, month, 1); printf(" 日 一 二 三 四 五 六\n"); int i; for (i = 0; i < weekday; i++) { printf(" "); } for (i = 1; i <= days; i++) { printf("%2d ", i); if ((weekday + i) % 7 == 0) { printf("\n"); } } if ((weekday + days) % 7 != 0) { printf("\n"); } } int main() { int year, month; printf("请输入年份:"); scanf("%d", &year); printf("请输入月份:"); scanf("%d", &month); if (month < 1 || month > 12) { printf("月份输入错误!\n"); return 1; } printf(" %d年%d月\n", year, month); show_calendar(year, month); return 0; } ``` 四、实训总结 通过本次实训,我学会了如何使用C语言来编写一个简单的万年历程序,巩固了C语言基本语法和编程思想,加强了对函数、数组、指针、文件等概念和用法的理解,提高了编程能力和解决问题的能力。同时,我也意识到在编程过程中需要注重代码的规范、可读性和可维护性,这对于日后的开发工作非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周末不下雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值