数据结构------C语言经典题目(7)

1.系统栈和数据结构中的栈有什么区别?

        1.本质:

                系统栈:由程序运行时由操作系统自动分配的一块连续内存区域,用于存储函数调用过程中的临时数据(参数、局部变量、返回地址),是程序运行的底层机制,遵循先进后出原则。

                数据结构中的栈:是一种线性数据结构,定义了一组操作(压栈、弹栈、查看栈顶),也遵循 先进后出 原则。

        2.功能与用途:

                系统栈:

                   存储函数调用时的 局部变量,保存函数的返回地址。传递函数调用的 参数。支持递归调用。

示例:当函数A调用函数B时,系统会将A的当前状态(局部变量值、返回地址)压入栈,然后为B分配栈帧;B执行完毕后,栈会弹出A的状态,恢复执行A。

                数据结构中的栈:

                     表达式求值、括号匹配、回溯算法等。

        3.操作方式:

                系统栈:

                        操作是隐式的,函数调用、返回、局部变量创建/销毁等操作会自动触发栈的压入/弹出。

                数据结构中的栈:操作是显式的。需编写实现代码。

// 定义栈的最大容量
#define MAX_STACK_SIZE 100// 用数组实现栈(底层存储结构)
// stack数组存储栈元素,下标0为栈底,下标top为栈顶
int stack[MAX_STACK_SIZE];// top变量表示栈顶元素的索引:
// - 初始值-1表示栈为空(无有效元素)
// - 当压入元素时,top先自增再赋值
// - 当弹出元素时,先返回top位置元素,再自减
int top = -1;// 压栈操作:将元素value添加到栈顶
// 参数:value - 待压入栈的整数值
// 说明:若栈已满(top == MAX_STACK_SIZE - 1),则不执行压栈操作
void push(int value) 
{// 检查栈是否未满(栈最大索引为MAX_STACK_SIZE-1)if (top < MAX_STACK_SIZE - 1) {// 先将栈顶指针上移一位,再存储新元素(栈顶索引始终指向最后一个元素)stack[++top] = value;}
}// 弹栈操作:从栈顶取出并返回元素
// 返回值:栈顶元素(int类型),若栈空则返回-1(需根据业务调整错误处理)
// 说明:执行弹栈前需确保栈非空(调用前检查top >= 0)
int pop() 
{// 检查栈是否为空(栈空时top == -1)if (top >= 0) {// 先返回栈顶元素,再将栈顶指针下移一位return stack[top--];}// 栈空时返回-1(可根据实际需求定义错误码)return -1; 
}

2.如何实现二叉树的深度优先遍历算法和广度优先遍历算法?

数据结构:

        使用结构体TreeNode定义二叉树节点,包含数据域和左右子节点指针。

深度优先遍历:

        前序遍历:先访问根节点,再递归遍历左子树,最后递归遍历右子树。

        中序遍历:左            根                右

        后续遍历:左            右                根

广度优先遍历:

        使用数组模拟队列实现层序遍历。

        根节点入队----》循环取出队首节点并访问----》将其左右子节点依次入队

        测试构建的二叉树结构:

    1/ \2   3/ \
4   5

示例代码:

// ====================== 数据结构定义 ======================
/*** 二叉树节点结构体定义* 包含:* - data: 节点存储的整数值* - left: 指向左子节点的指针(NULL表示无左子节点)* - right: 指向右子节点的指针(NULL表示无右子节点)*/
typedef struct TreeNode {int data;               // 节点数据域struct TreeNode *left;  // 左子节点指针struct TreeNode *right; // 右子节点指针
} TreeNode;// ====================== 节点操作函数 ======================
/*** 创建二叉树节点* @param data 节点存储的整数值* @return 新创建的节点指针(动态分配内存)*/
TreeNode* createNode(int data) 
{// 分配节点内存空间TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));if (newNode == NULL)  // 内存分配失败处理(实际项目中需增强错误处理){ exit(EXIT_FAILURE);}newNode->data = data;       // 初始化数据域newNode->left = NULL;       // 初始化左子节点指针为NULLnewNode->right = NULL;      // 初始化右子节点指针为NULLreturn newNode;             // 返回新节点指针
}// ====================== 内存释放函数 ======================
/*** 递归释放二叉树所有节点内存* 遵循后序遍历顺序:先释放左子树 → 释放右子树 → 释放当前节点* @param root 待释放的二叉树树根节点(NULL时直接返回)*/
void freeTree(TreeNode* root) 
{if (root == NULL) return; // 终止条件:空节点freeTree(root->left);  // 递归释放左子树freeTree(root->right); // 递归释放右子树free(root);            // 释放当前节点内存root = NULL;           // 防止野指针(虽然此处即将离开作用域)
}// ====================== 深度优先遍历(递归实现) ======================
/*** 前序遍历(根左右)* 访问顺序:根节点 → 左子树 → 右子树* @param root 当前遍历的子树树根节点(NULL表示空树)*/
void preOrderTraversal(TreeNode* root) 
{if (root == NULL)         // 递归终止条件:遇到空节点{return;}printf("%d ", root->data); // 先访问根节点preOrderTraversal(root->left); // 递归遍历左子树preOrderTraversal(root->right); // 递归遍历右子树
}/*** 中序遍历(左根右)* 访问顺序:左子树 → 根节点 → 右子树* @param root 当前遍历的子树树根节点(NULL表示空树)*/
void inOrderTraversal(TreeNode* root) 
{if (root == NULL)         // 递归终止条件{    return;}inOrderTraversal(root->left); // 先递归遍历左子树printf("%d ", root->data); // 再访问根节点inOrderTraversal(root->right); // 最后递归遍历右子树
}/*** 后序遍历(左右根)* 访问顺序:左子树 → 右子树 → 根节点* @param root 当前遍历的子树树根节点(NULL表示空树)*/
void postOrderTraversal(TreeNode* root) 
{if (root == NULL)         // 递归终止条件{return;}postOrderTraversal(root->left); // 先递归遍历左子树postOrderTraversal(root->right); // 再递归遍历右子树printf("%d ", root->data); // 最后访问根节点
}// ====================== 广度优先遍历(队列实现) ======================
/*** 层序遍历(广度优先遍历)* 访问顺序:按层次从左到右依次访问节点(第1层→第2层→...)* @param root 二叉树的根节点(NULL表示空树)*/
void levelOrderTraversal(TreeNode* root) 
{if (root == NULL)         // 空树处理{return;}// 使用数组模拟队列(固定大小,适用于小规模树,大规模建议用链表实现)TreeNode* queue[100];       // 队列存储节点指针int front = 0;              // 队头指针(指向当前出队节点位置)int rear = 0;               // 队尾指针(指向当前入队位置的下一个位置)queue[rear++] = root;       // 根节点入队(初始队列包含根节点)while (front < rear)       // 队列不为空时循环{    TreeNode* current = queue[front++]; // 取出队首节点(先取值,再front自增)printf("%d ", current->data); // 访问当前节点// 左子节点非空则入队(保证层次顺序)if (current->left != NULL) {    queue[rear++] = current->left;}// 右子节点非空则入队(保证同层节点左到右顺序)if (current->right != NULL) {queue[rear++] = current->right;}}
}// ====================== 测试用例 ======================
int main() 
{TreeNode* root = createNode(1);          // 根节点(值1)root->left = createNode(2);              // 根节点左子节点(值2)root->right = createNode(3);             // 根节点右子节点(值3)root->left->left = createNode(4);        // 左子节点的左子节点(值4)root->left->right = createNode(5);       // 左子节点的右子节点(值5)printf("前序遍历(根左右)结果:");preOrderTraversal(root);                 // 输出:1 2 4 5 3printf("\n");printf("中序遍历(左根右)结果:");inOrderTraversal(root);                  // 输出:4 2 5 1 3printf("\n");printf("后序遍历(左右根)结果:");postOrderTraversal(root);                // 输出:4 5 2 3 1printf("\n");printf("层序遍历(广度优先)结果:");levelOrderTraversal(root);               // 输出:1 2 3 4 5printf("\n");printf("正在释放二叉树内存...\n");freeTree(root); // 调用释放函数,递归释放所有节点root = NULL;    // 清空根指针,确保不再访问已释放内存return 0;
}

3.满二叉树第K层有多少个节点?总共有多少个节点?

        满二叉树的每一层节点数都达到最大值。第K层的节点数为_2{_{}^{k-1}}^{}个。

        将满二叉树的高度设为H(即共有H层),则总节点数为2_{}^{k} -1个。

4.冒泡排序、插入排序、选择排序、快速排序如何实现?

冒泡排序:  

/*** 冒泡排序函数* @param arr 待排序的整数数组* @param n 数组元素个数* 算法思想:重复走访数组,比较相邻元素,将较大的元素逐步后移* 时间复杂度:O(n²),稳定排序算法*/void bubble_sort(int arr[], int n) 
{int i = 0;    //外层循环计数器(控制排序轮数),int j = 0;     //内层循环计数器(控制每轮比较次数)//int temp = 0;  //临时变量用于交换元素// 外层循环:执行n-1轮排序(每轮确定一个最大元素到末尾)for (i = 0; i < n-1; i++) {// 内层循环:每轮比较相邻元素,将最大元素逐步后移// 每轮结束后,第n-i-1个位置是当前未排序部分的最大元素for (j = 0; j < n-i-1; j++) {// 如果当前元素大于下一个元素,则交换位置if (arr[j] > arr[j+1]) {temp = arr[j];        // 保存当前元素值arr[j] = arr[j+1];    // 下一个元素移到当前位置arr[j+1] = temp;      // 当前元素移到下一个位置}}}
}

插入排序:

/*** 插入排序函数* @param arr 待排序的整数数组* @param n 数组元素个数* 算法思想:将未排序元素插入到已排序部分的合适位置,使已排序部分始终有序* 时间复杂度:O(n²),稳定排序算法*/
void insertion_sort(int arr[], int n) 
{int i = 0;                    //未排序部分起始索引   int key = 0;                  //待插入的当前元素int j = 0;                    //已排序部分反向遍历索引// 外层循环:从第二个元素开始(假设第一个元素已排序)for (i = 1; i < n; i++) {key = arr[i]; // 保存当前待插入的元素j = i - 1;    // 已排序部分的最后一个元素索引// 内层循环:将已排序部分中大于key的元素后移,为key腾出位置// 当j>=0且当前元素大于key时,继续后移while (j >= 0 && arr[j] > key) {arr[j+1] = arr[j]; // 元素后移一位j--;               // 向前移动一位继续比较}arr[j+1] = key; // 将key插入到正确位置(j+1是因为循环结束时arr[j] <= key)}
}

选择排序:

/*** 选择排序函数* @param arr 待排序的整数数组* @param n 数组元素个数* 算法思想:每轮找到未排序部分的最小元素,与当前位置元素交换* 时间复杂度:O(n²),不稳定排序算法*/
void selection_sort(int arr[], int n) 
{int i = 0;                      //已排序部分末尾索引  int j = 0;                      //未排序部分遍历索引int min_idx = 0;                //最小元素索引int temp = 0;                   //临时变量用于交换// 外层循环:控制已排序部分的末尾位置(从0到n-2)for (i = 0; i < n-1; i++) {min_idx = i; // 初始化最小元素索引为当前位置// 内层循环:在未排序部分(i+1到n-1)寻找最小元素索引for (j = i+1; j < n; j++) {if (arr[j] < arr[min_idx])  // 如果找到更小的元素,更新最小索引{   min_idx = j;}}// 交换最小元素与当前位置元素(将最小元素放到已排序部分末尾)temp = arr[min_idx];arr[min_idx] = arr[i];arr[i] = temp;}
}

快速排序:

/*** 快速排序辅助函数:分区操作* @param arr 待分区的整数数组* @param low 分区起始索引* @param high 分区结束索引* @return 分区点索引(基准元素的最终位置)* 算法思想:选择基准元素,将数组分为两部分,左边<=基准,右边>=基准*/
int partition(int arr[], int low, int high) 
{int pivot = arr[high];  // 选择最后一个元素作为基准值int i = (low - 1);     // 初始化最小元素索引为low-1(表示已处理部分的末尾)// 遍历low到high-1的元素,将小于等于基准的元素移到左边for (int j = low; j <= high - 1; j++) {// 如果当前元素小于等于基准值if (arr[j] <= pivot) {    i++;  // 最小元素索引递增(指向当前处理的左边区域的下一个位置)// 交换arr[i]和arr[j],将较小元素移到左边int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}// 将基准元素放到正确的位置(左边区域的末尾+1),完成分区int temp = arr[i+1];arr[i+1] = arr[high];arr[high] = temp;return (i + 1); // 返回基准元素的最终位置
}/*** 快速排序主函数* @param arr 待排序的整数数组* @param low 排序起始索引* @param high 排序结束索引* 算法思想:分治策略,通过分区将数组分为两部分,递归排序子数组* 平均时间复杂度:O(n log n),最坏O(n²),不稳定排序算法*/
void quick_sort(int arr[], int low, int high) 
{if (low < high)  // 终止条件:当子数组长度>=2时继续处理{// 获取分区点,将数组分为左右两部分int pi = partition(arr, low, high);// 递归排序左子数组(low到pi-1)和右子数组(pi到high)quick_sort(arr, low, pi - 1);quick_sort(arr, pi, high);}
}

主函数:

/*** 打印数组函数* @param arr 待打印的整数数组* @param size 数组元素个数*/
void print_array(int arr[], int size) 
{int i = 0;for (i = 0; i < size; i++) {printf("%d ", arr[i]); // 逐个打印数组元素}printf("\n"); // 换行
}int main() 
{int arr[] = {64, 34, 25, 12, 22, 11, 90}; // 测试用数组int n = sizeof(arr) / sizeof(arr[0]);    // 计算数组元素个数printf("原始数组: ");print_array(arr, n); // 打印排序前的数组// 选择要测试的排序算法(取消注释对应函数调用)// bubble_sort(arr, n);        // 冒泡排序// selection_sort(arr, n);     // 选择排序// insertion_sort(arr, n);     // 插入排序quick_sort(arr, 0, n-1);       // 快速排序(注意快速排序需要传入low=0和high=n-1)printf("排序后的数组: ");print_array(arr, n); // 打印排序后的数组return 0;
}

5.什么是时间复杂度,常见的时间复杂度有哪些?

        时间复杂度是衡量算法执行时间随输入数据规模增长而变化的趋势的指标。它关注的是算法执行时间的“增长率”,而不是具体的执行时间,用于评估算法的效率和优化。

常用的时间复杂度:

        O(1)常数时间:

                算法的执行时间与输入规模无关,始终是一个固定值。

                比如数组元素的直接访问、简单的算术运算。

        O(log n)对数时间:

                算法每执行一次操作,输入规模以固定倍数(通常是2)减少,常见于二分法相关操作。

        O(n)线性时间:

                算法的执行时间与输入规模成正比,需要遍历数据一次。

                数组求和、查找最大值/最小值。

        O(n  log  n)线性对数时间:

                常见于分治算法,归并排序、快速排序。

        O(n^{_{2}^{}})平方时间:

                算法需要两层嵌套循环,执行时间与输入规模的平方成正比。

                冒泡排序、选择排序、双重循环遍历。

6.冒泡排序、选择排序、插入排序、快速排序的时间复杂度分别是什么?是否稳定?

冒泡排序:

        最好情况:当数组已有序时,只需遍历一次,时间复杂度为O(n)。

        最坏情况:当数组逆序时,需要进行 n  -  1轮比较,每轮比较n  -  i次,时间复杂度为O(n_{}^{2}

        稳定的。

选择排序:

        无论数组是否有序,均需进行n - 1轮选择和交换。时间复杂度为O(n_{}^{2}

        不稳定。例如,数组 [5,5,3]排序时,第一个5可能与后面的3交换,导致两个5的相对顺序发生改变。

插入排序:

        最好情况:数组已有序时,每轮插入只需比较一次,时间复杂度为O(n)。

        最坏情况:数组逆序时,每次插入需移动 i 个元素,时间复杂度为O(n_{}^{2})。

        稳定。      

快速排序:

        通过分治策略,每次分区平衡,时间复杂度为O(n log n)。

        不稳定。

7.二分查找的前提条件是什么?时间复杂度?

前提条件:

        1.数据有序:

                待查找的数组必须是有序的(升序或降序)。二分查找通过不断比较中间元素与目标值,将搜索范围减半,因此无序数据无法使用该算法。

        2.顺序存储:

                数据需存储在顺序存储结构(如数组)中。支持随机访问(通过下标直接访问中间元素)。链表等链式存储结构由于无法快速定位中间元素,不适合直接使用二分查找。

时间复杂度为O(log  n):

        每次查找将搜索范围缩小一半,最多需要log_{2}^{n} 次比较操作(n为数组元素个数)。

二分查找法的核心优势是高效,但严格依赖数据的有序性和顺序存储特性。使用前需确保数组已排序,否则需先排序(通过快速排序等算法)再进行查找。

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

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

相关文章

【Redis】一、redis的下载与安装

目录 一、redis下载 二、启动服务 三、测试服务 四、可视化界面 五、设置reids密码 今天起准备对redis进行学习&#xff0c;目标是掌握实际开发项目中如何应用redis等操作。首先在这里讲将如何下载redis&#xff0c;方便以后查阅。 一、redis下载 可以去官网&#xff08…

vue3中nextTick的作用及示例

在Vue 3中&#xff0c;nextTick是一个用于处理DOM异步更新的工具函数&#xff0c;确保在数据变化后操作最新的DOM。以下是其作用的详细解析&#xff1a; 核心作用 延迟回调到DOM更新后&#xff1a;Vue的响应式系统会将数据变更批量处理&#xff0c;异步更新DOM。nextTick允许你…

拆解大模型“越狱”攻击:对抗样本如何撕开AI安全护栏?

该文章首发于奇安信攻防社区:https://forum.butian.net/share/4254 引言 随着大规模语言模型(LLMs)在内容生成、智能交互等领域的广泛应用,其安全性和可控性成为学界和产业界关注的焦点。尽管主流模型通过道德对齐机制建立了安全护栏,但研究者发现,通过精心设计的"…

Ubuntu主机上通过WiFi转有线为其他设备提供网络连接

以下是在Ubuntu主机上通过WiFi转有线为Jetson设备提供网络连接的步骤&#xff1a; ​​1. 确认网络接口名称​​ 在Ubuntu主机上执行以下命令&#xff0c;查看WiFi和有线接口名称&#xff1a; ip a WiFi接口通常类似 wlp2s0 或 wlan0有线接口通常类似 enp0s25 或 eth0 记下…

通讯录完善版本(详细讲解+源码)

目录 前言 一、使通讯可以动态更新内存 1、contact.h 2、contact.c 存信息&#xff1a; 删除联系人&#xff0c;并试一个不存在的人的信息&#xff0c;看看会不会把其他人删了 ​编辑 修改&#xff1a; ​编辑 排序&#xff1a; ​编辑 销毁&#xff1a; ​编辑 ​…

Linux操作系统复习

Linux操作系统复习 一. Linux的权限和shell原理1. Linux从广义上讲是什么 从狭义上讲是什么&#xff1f;2. shell是什么&#xff1f;3. 为什么要设置一个shell外壳而不是直接和linux 内核沟通4. shell的原理是什么5. Linux中权限的概念6. 如何提升当前操作的权限7. 文件访问者的…

Spring AI 快速入门:从环境搭建到核心组件集成

Spring AI 快速入门&#xff1a;从环境搭建到核心组件集成 一、前言&#xff1a;Java开发者的AI开发捷径 对于Java生态的开发者来说&#xff0c;将人工智能技术融入企业级应用往往面临技术栈割裂、依赖管理复杂、多模型适配困难等挑战。Spring AI的出现彻底改变了这一局面——…

C++11介绍

目录 一、C11的两个小点 1.1、decltype 1.2、nullptr 二、列表初始化 2.1、C98传统的{} 2.2、C11中的{} 2.3、C11中的std::initializer_list 三、右值引用和移动语义 3.1、左值和右值 3.2、左值引用和右值引用 3.3、引用延长生命周期 3.4、左值和右值的参数匹配 3…

基于机器学习的网络钓鱼邮件智能检测与防护系统

phishingDP 介绍 phishingDP 是一个基于机器学习的网络钓鱼邮件智能检测与防护系统&#xff0c;旨在通过深度学习技术识别潜在的钓鱼邮件&#xff0c;保护用户免受网络诈骗威胁。该系统集成了数据预处理、模型训练、实时预测和结果可视化功能&#xff0c;提供用户友好的Web界…

OpenAI 推出「轻量级」Deep Research,免费用户同享

刚刚&#xff0c;OpenAI 正式上线了面向所有用户的「轻量级」Deep Research 版本&#xff0c;意味着即便没有付费订阅&#xff0c;也能体验这一强大工具的核心功能。 核心差异&#xff1a;o4-mini vs. o3 模型迭代 传统的深度研究功能基于更大规模的 o3 模型。轻量级版本则改以…

什么是优质的静态IP?以及如何选择优质的静态IP?

在如今的大数据生态中&#xff0c;静态IP的使用频率和重要性不断提升。但是&#xff0c;我们常听到业界提到“优质的静态IP”&#xff0c;那么什么样的静态IP能够称之为优质&#xff1f;如何判断这些IP能否满足我们的需求&#xff1f;今天这篇文章&#xff0c;将为您揭开优质静…

Hadoop生态圈框架部署 - Windows上部署Hadoop

文章目录 前言一、下载Hadoop安装包及bin目录1. 下载Hadoop安装包2. 下载Hadoop的bin目录 二、安装Hadoop1. 解压Hadoop安装包2. 解压Hadoop的Windows工具包 三、配置Hadoop1. 配置Hadoop环境变量1.1 打开系统属性设置1.2 配置环境变量1.3 验证环境变量是否配置成功 2. 修改Had…

搜广推校招面经八十一

OPPO搜广推一面面经 一、介绍一下PLE模型 在多任务学习&#xff08;Multi-Task Learning, MTL&#xff09;中&#xff0c;多个任务共享部分模型结构&#xff0c;以提升整体效果。然而&#xff0c;不同任务间存在 任务冲突&#xff08;Task Conflict&#xff09; 问题&#xf…

LangChain 中主流的 RAG 实现方式

文章目录 **一、基础流程实现**1. **全自动索引构建&#xff08;VectorstoreIndexCreator&#xff09;**2. **标准问答链&#xff08;RetrievalQA&#xff09;**3. **Document Chain 手动检索**4. **load_qa_chain&#xff08;传统方式&#xff09;** **二、高级定制化实现**1…

解决:springmvc工程 响应时,将实体类对象 转换成json格式数据

问题&#xff1a;一直无法将user对象转成json格式 按理来说&#xff0c;我在类上使用RestController注解&#xff0c;就可以实现将实体类对象写入响应体中&#xff0c;并作为json格式传递到客户端&#xff0c;但现实是没有生效&#xff0c;并且出现404&#xff0c;406&#xf…

【踩坑记录】stm32 jlink程序烧录不进去

最近通过Jlink给STM32烧写程序时一直报错&#xff0c;但是换一个其他工程就可以烧录&#xff0c;对比了一下jink配置&#xff0c;发现是速率选太高了“SW Device”&#xff0c;将烧录速率调整到10MHz以下就可以了

运维打铁:Mysql 分区监控以及管理

文章目录 一、简介二、设计逻辑1、配置文件检查2、创建逻辑3、 删除逻辑4、重建表分区逻辑5、recognize maxvalue分区表逻辑6、创建多个未来分区逻辑7、定时检测分区是否创建成功&#xff0c;否则发送告警邮件。 三、解决的问题四、配置例子与介绍 一、简介 操作数据库&#xf…

Appium自动化开发环境搭建

自动化 文章目录 自动化前言 前言 Appium是一款开源工具&#xff0c;用于自动化iOS、Android和Windows桌面平台上的本地、移动web和混合应用程序。原生应用是指那些使用iOS、Android或Windows sdk编写的应用。移动网页应用是通过移动浏览器访问的网页应用(appum支持iOS和Chrom…

《R语言SCI期刊论文绘图专题计划》大纲

今天开始&#xff0c;我将和大家分享系统且详细的《R语言SCI期刊绘图专题教程》&#xff0c;内容会从基础到高阶应用&#xff0c;从配色美学到顶刊风格复现&#xff0c;确保大家可以学到高质量内容&#xff01;下面是大纲。 &#x1f4da;《R语言SCI期刊论文绘图专题计划》 第…

STUN协议 与 TURN协议

STUN&#xff08;Session Traversal Utilities for NAT&#xff0c;NAT会话穿越应用程序&#xff09;是一种网络协议&#xff0c; STUN&#xff08;Simple Traversal of User Datagram Protocol through Network Address Translators (NATs)&#xff0c;NAT的UDP简单穿越&#…