初阶数据结构学习记录——여덟 二叉树

news2025/7/8 10:18:55

顾名思义,结构即为树,由一个根节点分出多个节点,这几个节点再继续往下连接其他节点形成一个个子树。不过这棵树是根朝上,叶朝下的。一个根不限制连接多少个节点,把第二层的几个节点也看成根节点,最终形成一整个树结构。形象的图可以搜到,这里就不写了。

来点形式主义

概念 

树是一种非线性的数据结构,,它是由n(n >= 0)个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一颗倒挂的数,也就是说它是根朝上,而叶朝下的。

~ 有一个特殊的节点,称为根节点,根节点没有前驱节点。

~ 除根节点外,其余节点被分成M(M > 0)个互不相交的集合T1,T2....TM, 其中每一个集合Ti(1 <= i <= M)又是一棵结构与树类似的子树。每棵子树的根节点有且只有一个前驱,可以有0个或多个后继节点。

~ 因此,树是递归定义的。

接下来要看一下树的一些定义

 

节点的度:一个节点含有的子树的个数称为该节点的度,如上图:A的为6

叶节点或终端节点:度为0的节点称为叶节点,如上图:B、C、H、I.....等节点为叶节点

非终端节点或分支节点:度不为0的节点;如上图:D、E、F、G....等节点为分支节点

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。如上图, A是B的节点

孩子节点或者子节点:一个节点含有的子树的根节点称为该节点的子节点。如上图:B是A的子节点

兄弟节点:具有相同父节点的节点互称为兄弟节点。如上图:B、C是兄弟节点

树的度:一棵树中,最大的节点的度称为树的度。如上图:树的度为6

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。

树的高度或深度:树中节点的最大层次。如上图,树的高度为4

堂兄弟节点:双亲在同一层的节点互为堂兄弟。如上图:H、I互为堂兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点、如上图:A是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:由m(m > 0)棵互不相交的树的集合称为森林

这些概念是树中常用名词,无论是为了做题还是为了工作,都应当记住。

二叉树

二叉树的每个节点最多有两个子节点,分为左子树和右子树。二叉树中又有满二叉树和完全二叉树。

满二叉树是每一层的节点数都达到最大。所以如果高度为h的满二叉树,它的节点数应当是2 ^ h - 1。

完全二叉树:完全二叉树是一个效率比较高的结构,由满二叉树变种而来。要求除去最后一层外其他层节点数达到最大,最后一层节点数至少为1,且节点必须连续,比如A为根节点,BC作为第二层节点,B和C下各一个左子树或者右子树就是不连续,就不是完全二叉树。

二叉树的特点

1、若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2 ^ (i - 1)个节点。

2、若规定根节点的层数为1,则深度为h的二叉树的最大节点数是2 ^ h - 1。

3、对任何一棵二叉树,如果度为0,其叶节点个数为n0,度为2的分支节点个数为n2,则有n0 = n2 + 1.(度为0总比度为2的多一个

4、若规定根节点的层数为1,具有n个节点的满二叉树的深度,h = log2(N + 1)。

具体推算过程就不写了。

这篇重点在于堆的实现

二叉树有大堆和小堆之分,大堆就是父节点大于等于子节点,小堆就是父节点小于等于子节点。这篇写大堆。

该用数组还是链表体现二叉树?

假设是链表,插入第一个后,需要用一个指针指向第二个。根节点如果好几个子节点,那就得需要多个指针,可以在创建结构体时,里面放入多个指针,也可以简单点,建立一个指针数组,大小就是树的度。不过这样或许还是不够好,另有一个方法,创建结构体后,里面放入一个child和brother指针,child指向自己的子节点,brother则指向兄弟节点。比如A为祖先,下有3个子节点,child指向节点B,B的brother指向C,C的brother指向d。BCD三个节点如果有子节点,那就用child指向子节点,这样就方便了。

现在想一下push功能,这是不是相当于尾插功能?所以我们需要第三个指针来指向尾部。插入后新数字需要和之前的数字比较。可是他该如何和其他元素比较?插入的新元素应当是brother或者child指向的数字,想访问指向新数字的节点,这里就得需要一个prev指针指向前面了吧?

所以可以发现一个问题,我没办法随意的访问其他节点元素。push或者pop时就总会有些麻烦,那么现在转向数组

假设是数组,数组可以随意访问其他元素,只要找到下标之间的规律即可。不过数组又该如何体现二叉树结构?这个并不是难事,我们需要脱离树状结构这个臆想图,真正在电脑里面存储时都是一块块空间,我们只需要使空间里的数字符合规律,可以简单地访问其他元素即可。现在以数组来完成二叉树。

调用整个树之前先创建好空间,所以初始化函数里就不写malloc了。

头文件里

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* a;
    HPDataType size;
    HPDataType capacity;
}HP;
 

Heap.c

void HeapInit(HP* php)
{
    assert(php);
    php->a = NULL;
    php->size = php->capacity = 0;
}

void HeapDestroy(HP* php)
{
    assert(php);
    free(php->a);
    php->a = NULL;
    php->size = php->capacity = 0;
}

先放上这两个函数。然后开始push和pop的代码。

现在的情况是已经存入了一些数据,然后再继续存入。

该怎么体现树状结构?我们确定了要使用数组,数组为空的时候,push一个就是放在下标0位置处,push第二个就需要和数组里的元素进行比较来确定谁当祖先。之后一个个存入,我们都需要比较,放好位置,所以搞清楚这个做法也就完成了push函数的思路。进行比较的时候,应该跟谁比较啊?怎么去访问啊?如果是链表,我们可以通过指针,不过数组就需要找规律。先前已经说过,抛掉两个树状结构的臆想图,一个根节点只能访问两个子节点,那么就把两个子节点放在根节点之后,通过下标转换来找到子节点。比如根节点下标为0,两个子节点下标分别是1和2,1这个节点继续分支,占据了3和4下标,2这个节点继续分支,占据5和6下标,这样找到规律即可随机访问。那么我们开始写push和pop函数。

插入之前先判断为不空

void HeapPush(HP* php, HPDataType x)
{
    assert(php);
    if (php->size == php->capacity)
    {
        int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
        HPDataType* tmp = (HPDataType*)realloc(sizeof(HPDataType) * newCapacity);
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        php->a = tmp;
        php->capacity = newCapacity;
    }
    php->a[php->size] = x;
    php->size++;

}

先写出来这些代码。现在已经在尾部插入一个数据了,那么开始判断大小吧。兄弟节点没必要比较,如果新数字比父节点大,那么自然也就比兄弟节点大;如果小,那就不需要动位置。之后一步步往上挪,整个过程都不需要跟兄弟节点比较,只跟父节点比较,大于就交换位置,直到祖先节点,如果比根节点大,那么新插入的数据就称为新的祖先。和父节点交换位置,父节点也不需要再跟其他节点进行比较。这是向上比较法。现在把它整理成函数调用即可。

void Swap(HPDataType* s1, HPDataType* s2)
{
    HPDataType tmp = *s1;
    *s1 = *s2;
    *s2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
    int parent = (child - 1) / 2;
    while (child)
    {
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

void HeapPush(HP* php, HPDataType x)
{
    assert(php);
    if (php->size == php->capacity)
    {
        int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
       HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        php->a = tmp;
        php->capacity = newCapacity;
    }
    php->a[php->size] = x;
    php->size++;
    AdjustUp(php->a, php->size - 1);
}

向上比较函数里,判断条件是child > 0。为什么不能是 >= 0?当最后和祖先节点比较时,如果还是更大,那么child就变成下标0位置了,这时候整个过程应当结束,如果是>= 0,while还会继续,就会越界访问了。

那么数组为空时,这个push函数有没有效?还是可以的,因为child为0,循环进不去,UP函数就会break了。再插入一个数据,进入函数,就进行比较,排好位置,会发现整个结构会按照大堆方向排列。写一个测试代码

void TestHeap1()
{
    int arr[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
    HP hp;
    HeapInit(&hp);
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        HeapPush(&hp, arr[i]);
    }
    HeapPrint(&hp);
    HeapDestroy(&hp);
}

int main()
{
    TestHeap1();
    return 0;
}

然后补上print函数

void HeapPrint(HP* php)
{
    assert(php);
    for (int i = 0; i < php->size; i++)
    {
        printf("%d ", php->a[i]);
    }
    printf("\n");
}

结果就是

所以没问题

接下来看push函数。

删除根部元素如何删除?尾部元素很好,就是尾删功能,但是根部元素的删除确实有点麻烦。能不能挪动覆盖?把49之后的数据往前挪一次,这样头部就删除了,但是挪动数据时间复杂度为O(N), 且这样挪动的话,节点之间的关系就变了,所以不能挪动。那如果是把49覆盖到65上,然后再往后找数值放到49这个位置呢?其实也不好做,画图仔细想想,会发现挪动的每个数据的下标不好找规律。

现在有另一个方法,尽量增加挪动的效率。把最后一个数字和第一个数字调换一下,这两个下标很好找,尾删一下,这时候size--了,根部位置的数字变成了尾部数字。要改成正确的顺序其实就可以参照push函数的做法,不过这里是向下比较法。换过来后,和第二层的数字比较,找出大的那个,让它来做新的祖先,然后一层层向下探索,上代码

void AdjustDown(HPDataType* a, int n, int parent)
{
    int child = parent * 2 + 1;
    while (child < n)
    {
        //确认child指向大的那个孩子并且child要小于size
        if (child + 1 < n && a[child + 1] > a[child])
        {
            ++child;
        }
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void HeapPop(HP* php)
{
    assert(php);
    assert(php->size > 0);
    Swap(&php->a[0], &php->a[php->size - 1]);
    php->size--;
    AdjustDown(php->a, php->size, 0);
}

在之前的测试代码再加上几行

void TestHeap1()
{
    int arr[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
    HP hp;
    HeapInit(&hp);
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        HeapPush(&hp, arr[i]);
    }
    HeapPrint(&hp);

    int k = 5;
    while (k--)
    {
        printf("%d ", HeapTop(&hp));
        HeapPop(&hp);
    }
    HeapDestroy(&hp);
}

难题攻克了,我们把剩下的点一一写完

HPDataType HeapTop(HP* php)
{
    assert(php);
    assert(php->size > 0);
    return php->a[0];
}

HPDataType HeapSize(HP* php)
{
    assert(php);
    return php->size;
}

bool HeapEmpty(HP* php)
{
    assert(php);
    return php->size == 0;
}

最后,还有一个问题,有的时候给的接口里面会有创建堆这个函数,关于这个之后再写。先放上所有的代码

Heap.h

#pragma once
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
    HPDataType* a;
    HPDataType size;
    HPDataType capacity;
}HP;

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);

HPDataType HeapTop(HP* php);
HPDataType HeapSize(HP* hp);
bool HeapEmpty(HP* hp);

 

 

Heap.c

#include "Heap.h"

void HeapInit(HP* php)
{
    assert(php);
    php->a = NULL;
    php->size = php->capacity = 0;
}

void HeapDestroy(HP* php)
{
    assert(php);
    free(php->a);
    php->a = NULL;
    php->size = php->capacity = 0;
}

void HeapPrint(HP* php)
{
    assert(php);
    for (int i = 0; i < php->size; i++)
    {
        printf("%d ", php->a[i]);
    }
    printf("\n");
}

void Swap(HPDataType* s1, HPDataType* s2)
{
    HPDataType tmp = *s1;
    *s1 = *s2;
    *s2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
    int parent = (child - 1) / 2;
    while (child)
    {
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

void HeapPush(HP* php, HPDataType x)
{
    assert(php);
    if (php->size == php->capacity)
    {
        int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
        HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        php->a = tmp;
        php->capacity = newCapacity;
    }
    php->a[php->size] = x;
    php->size++;
    AdjustUp(php->a, php->size - 1);
}

void AdjustDown(HPDataType* a, int n, int parent)
{
    int child = parent * 2 + 1;
    while (child < n)
    {
        //确认child指向大的那个孩子并且child要小于size
        if (child + 1 < n && a[child + 1] > a[child])
        {
            ++child;
        }
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void HeapPop(HP* php)
{
    assert(php);
    assert(php->size > 0);
    Swap(&php->a[0], &php->a[php->size - 1]);
    php->size--;
    AdjustDown(php->a, php->size, 0);
}

HPDataType HeapTop(HP* php)
{
    assert(php);
    assert(php->size > 0);
    return php->a[0];
}

HPDataType HeapSize(HP* php)
{
    assert(php);
    return php->size;
}

bool HeapEmpty(HP* php)
{
    assert(php);
    return php->size == 0;
}

Test.c

#include "Heap.h"
/*void TestHeap1()
{
    int arr[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
    HP hp;
    HeapInit(&hp);
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        HeapPush(&hp, arr[i]);
    }
    HeapPrint(&hp);

    int k = 5;
    while (k--)
    {
        printf("%d ", HeapTop(&hp));
        HeapPop(&hp);
    }
    HeapDestroy(&hp);
}*/

void TestHeap2()
{
    int arr[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
    HP hp;
    HeapInit(&hp);
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        HeapPush(&hp, arr[i]);
    }
    HeapPrint(&hp);
    while (!HeapEmpty(&hp))
    {
        printf("%d ", HeapTop(&hp));
        HeapPop(&hp);
    }
    HeapDestroy(&hp);
}

int main()
{
    //TestHeap1();
    TestHeap2();
    return 0;
}

结束。下一篇再补上创建堆的接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/18553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2011年408大题总结

2011年408大题第41题第42题第43题第44题第45题第46题第47题第41题 关键信息&#xff1a;有向带权、上三角、行为主序 就可以解决第一二小问 关键路径&#xff1a;最长 0 1 2 3 5&#xff0c;长度为16 第42题 一如既往的暴力 最简单的思路&#xff0c;合并取中位数 所以用数组就…

[附源码]java毕业设计拾穗在线培训考试系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Android App开发动画特效中帧动画和电影淡入淡出动画的讲解及实战(附源码和演示视频 简单易懂)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、帧动画 Android的动画分为三类&#xff0c;帧动画&#xff0c;补间动画和属性动画。其中帧动画是实现原理最简单的一种&#xff0c;跟现实生活中的电影胶卷类似&#xff0c;都是在短时间内连续播放多张图片&#xff0c;从而…

Request和Response

目录 1、Request和Response的概述 2、Request对象 2.1、Request继承体系 2.2、Request获取请求数据 2.2.1 获取请求行数据 2.2.2 获取请求头数据 2.2.3 获取请求体数据 2.2.4、获取请求参数的通用方式 2.3 IDEA快速创建Servlet 2.4、请求参数中文乱码问题 2.4.1、POS…

认识Spring

1.1 Spring的历程 早期的 Java EE 使用 EJB 为核心的开发方式,但是这种开发方式在实际开发环境中存在诸多问题: 使用复杂, 代码臃肿, 移植性差等.于是"Spring 之父" Rod Johnson 在其畅销书《Expert One-on-One J2EE Design and Development》中使用一个3万行代码的…

MySQL8.0优化 - 锁 - 按加锁的方式划分:显示锁、隐式锁

文章目录学习资料锁的不同角度分类锁的分类图如下按加锁的方式划分&#xff1a;显示锁、隐式锁隐式锁显式锁学习资料 【MySQL数据库教程天花板&#xff0c;mysql安装到mysql高级&#xff0c;强&#xff01;硬&#xff01;-哔哩哔哩】 【阿里巴巴Java开发手册】https://www.w3…

[附源码]计算机毕业设计JAVA基于Java的快递驿站管理系统

[附源码]计算机毕业设计JAVA基于Java的快递驿站管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; S…

车联网解决方案-最新全套文件

车联网解决方案-最新全套文件一、建设背景面临的挑战1、平台难以支撑高并发接入2、海量数据难以挖掘价值3、缺乏使能套件&#xff0c;开发效率低4、车联网的安全难以保证二、建设架构三、建设方案四、获取 - 车联网全套最新解决方案合集一、建设背景 面临的挑战 1、平台难以支…

Teams Tab App 代码深入浅出 - 配置页面

上一篇文章我们使用Teams Toolkit 来创建、运行 tab app。这篇文章我们深入来分析看一下tab app 的代码。 先打开代码目录&#xff0c;可以看到在 src 目录下有入口文件 index.tsx&#xff0c;然后在 components 目录下有更多的一些 tsx 文件&#xff0c;tsx 是 typescript的一…

实战十二:基于FM算法针对用户商品购买和浏览记录预测用户的行为 代码+数据

1.案例知识点 推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)准确的预测出用户未来的行为;好的推荐系统不仅如此,而且能够拓展用户的视野,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的用户。FM推荐方法…

Qt5开发从入门到精通——第十一篇二节(Qt5 事件处理及实例——键盘事件及实例)

提示&#xff1a;欢迎小伙伴的点评✨✨&#xff0c;相互学习c/c应用开发。&#x1f373;&#x1f373;&#x1f373; 博主&#x1f9d1;&#x1f9d1; 本着开源的精神交流Qt开发的经验、将持续更新续章&#xff0c;为社区贡献博主自身的开源精神&#x1f469;‍&#x1f680; 文…

基于DJYOS的UART驱动编写指导手册

1.概述 DJYOS设计通用的串口驱动模型&#xff0c;在此模型的基础上&#xff0c;移植到不同硬件平台时&#xff0c;只需提供若干硬件操作函数&#xff0c;即可完成串口驱动开发&#xff0c;使开发工作变得简单而快速执行效率高。 DJYOS源代码都有特定的存放位置&#xff0c; 建…

记录为小电机安装一个5012编码器(多摩川协议)的过程

目录 1. 编码器接口板介绍 2. 编码器接口板安装 3. 角度读取软件实现 4.总结 为了能得到更精确的角度&#xff0c;于是想要通过在测试电机上安装一个编码器来获取更精确的角度&#xff0c;方便日后调试或者校验使用&#xff0c;这里记录下操作的步骤。 1. 编码器接口板介绍…

Github Actions 自动同步到 Gitee

目录引言具体方案引言 平时开源代码一般已Github为主&#xff0c;但是会遇到网不好的情况&#xff0c;于是考虑将Github仓库自动同步到Gitee上&#xff0c;作为备份。考虑不能手动做这个事情&#xff0c;于是想到了Github Actions 自动化同步到Gitee中 具体方案 假设Github已…

Mybatis Plus一对多联表查询及分页解决方案

文章目录需求需求分析分页问题说明分页问题原因一对多场景一一对多场景二性能优化优化解决方案需求 查询用户信息列表&#xff0c;其中包含用户对应角色信息&#xff0c;页面检索条件有根据角色名称查询用户列表&#xff1b; 需求分析 一个用户对应多个角色&#xff0c;用户…

java计算机毕业设计ssm党支部在线学习

项目介绍 本党支部在线学习是针对目前学习的实际需求,从实际工作出发,对过去的学习系统存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概念、模型、原…

【深度学习】torch.utils.data.DataLoader相关用法 | dataloader数据加载器 | pytorch

文章目录前言一、DataLoader介绍二、DataLoader的子方法&#xff08;可调用方法&#xff09;前言 dataloader数据加载器属于是深度学习里面非常基础的一个概念了&#xff0c;基本所有的图像项目都会用上&#xff0c;这篇博客就把它的相关用法总结一下。 之所以要写这篇&#x…

1-1 开源许可证GPL, BSD, MIT, Mozilla, Apache, LGPL的介绍

文章目录前言开源许可证由来开源许可证类型开源许可证介绍GPL协议BSD协议MIT协议Mozilla许可Apache协议LGPL开源协议前言 开源许可证&#xff08;Open source licenses&#xff09;&#xff0c;广义来讲是指一种被用于计算机软件或其他产品的&#xff0c;允许在指定的条款内使…

zMemif: go语言高性能网络库

简介 开发zMemif的主要动机是go有很高的处理能力&#xff0c;但是内置的udp库的确有些寒酸&#xff0c; 纯c开发效率又有些低&#xff0c;虽然可以用nff-go来实现go和dpdk的融合&#xff0c;但是cgo编译的确有点烦人&#xff0c;而且这个项目似乎也死了。然后考虑到容器的场景…

单点登录以及实现(前后端分离和前后端不分离方式)

本文主要使用springSecurity来实现&#xff0c;其他实现请参照其原理自行研究。 一&#xff0c;单系统登录机制 1、http无状态协议 web应用采用browser/server架构&#xff0c;http作为通信协议。http是无状态协议&#xff0c;浏览器的每一次请求&#xff0c;服务器会独立处…