遥感图像变换检测实践上手(TensorRT+UNet)

目录

简介

分析PyTorch示例

onnx模型转engine

编写TensorRT推理代码

main.cpp测试代码

小结


简介

这里通过TensorRT+UNet,在Linux下实现对遥感图像的变化检测,示例如下:

可以先拉去代码:RemoteChangeDetection

分析PyTorch示例

在目录PyFiles中,unet.py存放UNet网络定义,可以使用test_infer.py脚本进行推理并导出onnx模型,可以简单分析一下test_infer.py中的关键代码。

(1)加载处理图像

import torch
import numpy as np
from PIL import Image
from torchvision import transforms
from unet import UNet
import onnx
import onnxsim# 读取变换前后的代码
img1 = Image.open("./A/val_20.png")
img2 = Image.open("./B/val_20.png")# 输出的图像名称
output_image_path = "result.png"# PIL图像转Tensor张量
transform = transforms.Compose([transforms.ToTensor()
])# 分别取两幅图像的第一个通道图像,因为PIL读取的图像是RGB的,注意和OpenCV图像区别
img1_data = np.array(img1)
img1_data = img1_data[:, :, 0]img2_data = np.array(img2)
img2_data = img2_data[:, :, 0]# 这里合并输入图像: shape ==> [height, width, 2]
input_image = np.stack([img1_data, img2_data], axis=2)# 转换为模型输入,大致流程:
# 1. transform: 图像从[0, 255] 映射到 [0, 1]; 交换通道图像[h, w, 2] => [2, h, w]
# 2. unsqueeze(0),增加第一个维度:[2, h, w] => [1, 2, h, w]
# 3. unit8 转 float32类型,并放置在GPU上
input_image_tensor = transform(input_image).unsqueeze(0).type(torch.float32).to(device)

(2)推理并导出为onnx

def export_norm_onnx(model, file, input):torch.onnx.export(model         = model, args          = (input,),f             = file,input_names   = ["input0"],output_names  = ["output0"],opset_version = 9)print("Finished normal onnx export")model_onnx = onnx.load(file)onnx.checker.check_model(model_onnx)# 使用onnx-simplifier来进行onnx的简化。print(f"Simplifying with onnx-simplifier {onnxsim.__version__}...")model_onnx, check = onnxsim.simplify(model_onnx)assert check, "assert check failed"onnx.save(model_onnx, file)

这里定义了一个导出onnx函数,model为PyTorch模型,file是输出文件路径,input是模型的输入。

with torch.no_grad():net = UNet(2).to(device)net.eval()load_models = torch.load(weights)net.load_state_dict(torch.load(weights))out_image = net(input_image_tensor)_out_image = out_image[0][0].round().detach().cpu().numpy()_out_image = (_out_image * 255).astype(np.uint8)result_image = Image.fromarray(_out_image)result_image.save(output_image_path)export_norm_onnx(net, "./unet_simple.onnx", input_image_tensor)

这里是推理(为了测试.pth模型)并导出onnx。这里注意对输出图像的后处理过程,在编写c++接口时要留意。

使用onnx可视化工具查看导出的onnx模型:

onnx模型转engine

如果你已经按照了TensorRT,并且配置好了环境变量后,可以直接使用bin下的trtexec命令将onnx模型进行转换,假如你的TensorRT安装路径如下:

环境变量的配置:

使用如下命令进行转换:

trtexec --onnx=dncnn_color_blind.onnx --saveEngine=dncnn_color_engine_intro.engine  --explicitBatch
// *.onnx是输入的模型,*.engine是保存的模型

上边只是举个例子,把文件名换成自己的就可以了。

编写TensorRT推理代码

(1)运行环境搭建

我的运行环境目录大致如下:

RemoteChangeDetection
3rdparty|------- opencv-3.4.10|-------- include|-------- lib|------- TensorRT-8.5.2.2|-------- include|-------- lib...

首先修改CMakeLists.txt中的三方库路径:

那么你应该修改CUDA,CUDNN,OpenCV以及TensorRT的路径。

在src/路径下是核心代码,trt_logger包含了TensorRT推理时依赖的logger,以及CUDA函数运行时的检查宏:

#ifndef __LOGGER_H__
#define __LOGGER_H__#include <string>
#include <stdarg.h>
#include <memory>
#include <cuda_runtime.h>
#include <system_error>
#include "NvInfer.h"#define CUDA_CHECK(call)             __cudaCheck(call, __FILE__, __LINE__)
#define LAST_KERNEL_CHECK(call)      __kernelCheck(__FILE__, __LINE__)static void __cudaCheck(cudaError_t err, const char* file, const int line) {if (err != cudaSuccess) {printf("ERROR: %s:%d, ", file, line);printf("code:%s, reason:%s\n", cudaGetErrorName(err), cudaGetErrorString(err));exit(1);}
}static void __kernelCheck(const char* file, const int line) {cudaError_t err = cudaPeekAtLastError();if (err != cudaSuccess) {printf("ERROR: %s:%d, ", file, line);printf("code:%s, reason:%s\n", cudaGetErrorName(err), cudaGetErrorString(err));exit(1);}
}#define LOGF(...) logger::Logger::__log_info(logger::Level::FATAL, __VA_ARGS__)
#define LOGE(...) logger::Logger::__log_info(logger::Level::ERROR, __VA_ARGS__)
#define LOGW(...) logger::Logger::__log_info(logger::Level::WARN,  __VA_ARGS__)
#define LOG(...)  logger::Logger::__log_info(logger::Level::INFO,  __VA_ARGS__)
#define LOGV(...) logger::Logger::__log_info(logger::Level::VERB,  __VA_ARGS__)
#define LOGD(...) logger::Logger::__log_info(logger::Level::DEBUG, __VA_ARGS__)

最重要的是UNetTrt部分,在UNetTrt.h:

#ifndef UNET_TRT_H_
#define UNET_TRT_H_#include <iostream>
#include <memory>
#include <opencv2/opencv.hpp>
#include <cuda_runtime.h>// 前置定义
namespace nvinfer1
{class IRuntime;class ICudaEngine;class IExecutionContext;
}class UNet
{
public:UNet() {};~UNet();// 加载engine文件bool loadTrtModel(const std::string model_path);// 推理,input_mat1: 变换前;input_mat2: 变换后;output是变量引用bool trt_infer(cv::Mat &input_mat1, cv::Mat &input_mat2, cv::Mat &output);          // input_mat1: before, input_mat2: afterprivate:// runtime_, engine_, context_等成员是TensorRT推理时最重要的几个成员变量// 为了放置内存泄露,用智能指针管理std::shared_ptr<nvinfer1::IRuntime>               runtime_;        std::shared_ptr<nvinfer1::ICudaEngine>            engine_;std::shared_ptr<nvinfer1::IExecutionContext>      context_;cudaStream_t                                      stream_;int input_index_;            // 索引输入int output_index_;           // 索引输出const char                  *INPUT_NAME         = "input0";            // 输入名称,和onnx导入时保持一致const char                  *OUTPUT_NAME        = "output0";           // 和上边保持一致const int                    BATCH_SIZE         = 1;                   // 一般都保持为1void                        *buffers_[2];                              // 存放TensorRT输入输出float                       *input_float_       = nullptr;             // 存放Host端输入,c11允许.h中初始化float                       *output_float_      = nullptr;             // Host端计算结果
};#endif

在.cpp中,给出一些核心实现:

#include "UNetTrt.h"
#include <fstream>
#include <cmath>
#include "trt_logger.h"
#include "NvInfer.h"
#include "NvOnnxParser.h"#define INPUT_WIDTH         1024
#define INPUT_HEIGHT        1024bool UNet::loadTrtModel(const std::string model_path)
{char *trt_stream = nullptr;size_t size = 0;// load trt modelstd::ifstream file(model_path, std::ios::binary);if (file.good()) {file.seekg(0, file.end);size = file.tellg();file.seekg(0, file.beg);trt_stream = new char[size];if(!trt_stream)return false;file.read(trt_stream, size);file.close();} else {return false;}logger::Logger trt_logger(logger::Level::INFO);runtime_.reset(nvinfer1::createInferRuntime(trt_logger));if(!runtime_)return false;engine_.reset(runtime_->deserializeCudaEngine(trt_stream, size, nullptr));if(!engine_)return false;context_.reset(engine_->createExecutionContext());if(!context_)return false;const nvinfer1::ICudaEngine& trtEngine = context_->getEngine();input_index_ = trtEngine.getBindingIndex(INPUT_NAME);output_index_ = trtEngine.getBindingIndex(OUTPUT_NAME);CUDA_CHECK(cudaMalloc(&buffers_[input_index_], BATCH_SIZE * 2 * INPUT_WIDTH * INPUT_HEIGHT * sizeof(float)));CUDA_CHECK(cudaMalloc(&buffers_[output_index_], BATCH_SIZE * 1 * INPUT_WIDTH * INPUT_HEIGHT * sizeof(float)));input_float_ = new float[BATCH_SIZE * 2 * INPUT_WIDTH * INPUT_HEIGHT];output_float_ = new float[BATCH_SIZE * 1 * INPUT_WIDTH * INPUT_HEIGHT];delete []trt_stream;return true;
}

首先,输入大小是固定的,所以在宏里写死了输入大小1024x1024;loadTrtModel根据路径加载engine文件,并对一些推理时用到的成员变量依次初始化,同时分配好输入输出空间。

推理代码如下

bool UNet::trt_infer(cv::Mat &input_mat1, cv::Mat &input_mat2, cv::Mat &output)
{if(input_mat1.empty() || input_mat2.empty())return false;if(input_mat1.rows != input_mat2.rows || input_mat1.cols != input_mat2.cols)return false;if(input_mat1.channels() <= 1 && input_mat2.channels() <= 1) return false;int pre_width = input_mat1.cols;int pre_height = input_mat1.rows;cv::resize(input_mat1, input_mat1, cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::INTER_CUBIC);cv::resize(input_mat2, input_mat2, cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::INTER_CUBIC);std::vector<cv::Mat> input_mat1_channels;cv::split(input_mat1, input_mat1_channels);std::vector<cv::Mat> input_mat2_channels;cv::split(input_mat2, input_mat2_channels);// [H, W, C] => [C, H, W] && [0.0, 0.1]for(int i = 0; i < INPUT_WIDTH; i++) {for(int j = 0; j < INPUT_HEIGHT; j++) {int idx_c1 = j * INPUT_WIDTH + i;int idx_c2 = idx_c1 + INPUT_WIDTH * INPUT_HEIGHT;input_float_[idx_c1] = (float)input_mat1_channels[2].data[idx_c1] / 255.0f;input_float_[idx_c2] = (float)input_mat2_channels[2].data[idx_c1] / 255.0f;}}memset(output_float_, 0, BATCH_SIZE * 1 * INPUT_WIDTH * INPUT_HEIGHT);CUDA_CHECK(cudaStreamCreate(&stream_));CUDA_CHECK(cudaMemcpyAsync(buffers_[input_index_], input_float_, BATCH_SIZE * 2 * INPUT_WIDTH * INPUT_HEIGHT * sizeof(float), cudaMemcpyHostToDevice, stream_));context_->enqueueV2(buffers_, stream_, nullptr);CUDA_CHECK(cudaMemcpyAsync(output_float_, buffers_[output_index_], BATCH_SIZE * 1 * INPUT_WIDTH * INPUT_HEIGHT * sizeof(float), cudaMemcpyDeviceToHost, stream_));cudaStreamSynchronize(stream_);// roundfor(int i = 0; i < INPUT_WIDTH; i++) {for(int j = 0; j < INPUT_HEIGHT; j++) {int index = j * INPUT_WIDTH + i;output_float_[index] = std::round(output_float_[index]);}}output = cv::Mat(INPUT_HEIGHT, INPUT_WIDTH, CV_32F, output_float_);output *= 255.0;output.convertTo(output, CV_8U);cv::resize(output, output, cv::Size(pre_width, pre_height), cv::INTER_CUBIC);return true;
}

这里依次讲解一下,首先你可能要把代码放入工程,那么应该尽量做好判断,比如图像是否为空;图像大小、通道是否一致,以防万一可以同时进行Resize;

cv::split对3通道图像进行剥离,放入vector中,然后开始进行通道转换与归一化。这里可以稍微理解一下不同图像在内存中的存放方式,一般的RGB图像或者BGR图像(height, width, channel)应该是这样:

B G R B G R B G R B G R B G R B G R
B G R B G R B G R B G R B G R B G R
B G R B G R B G R B G R B G R B G R
B G R B G R B G R B G R B G R B G R

互相交错存放,但是网络输入一般是(channel, height, width),那么存放方式是如下这样:

R R R R R R R R
R R R R R R R R
R R R R R R R RG G G G G G G G
G G G G G G G G
G G G G G G G GB B B B B B B B
B B B B B B B B
B B B B B B B B

那么就可以很容易写出通道转换与归一化代码:

// [H, W, C] => [C, H, W] && [0.0, 0.1]for(int i = 0; i < INPUT_WIDTH; i++) {for(int j = 0; j < INPUT_HEIGHT; j++) {int idx_c1 = j * INPUT_WIDTH + i;int idx_c2 = idx_c1 + INPUT_WIDTH * INPUT_HEIGHT;input_float_[idx_c1] = (float)input_mat1_channels[2].data[idx_c1] / 255.0f;input_float_[idx_c2] = (float)input_mat2_channels[2].data[idx_c1] / 255.0f;}}

每次推理前把输出结果清空置为0:

memset(output_float_, 0, BATCH_SIZE * 1 * INPUT_WIDTH * INPUT_HEIGHT);

重新分配cudaStream_t,cudaMemcpyAsync分配显存,context_->enqueueV2推理,cudaMemcpyAsync再将结果从显存拷贝到Host端。

CUDA_CHECK(cudaStreamCreate(&stream_));CUDA_CHECK(cudaMemcpyAsync(buffers_[input_index_], input_float_, BATCH_SIZE * 2 * INPUT_WIDTH * INPUT_HEIGHT * sizeof(float), cudaMemcpyHostToDevice, stream_));context_->enqueueV2(buffers_, stream_, nullptr);CUDA_CHECK(cudaMemcpyAsync(output_float_, buffers_[output_index_], BATCH_SIZE * 1 * INPUT_WIDTH * INPUT_HEIGHT * sizeof(float), cudaMemcpyDeviceToHost, stream_));cudaStreamSynchronize(stream_);

后处理过程中,也遇到了一些坑,总体而言,还是要一一对照python那部分后处理代码仔细分析:

_out_image = out_image[0][0].round().detach().cpu().numpy()
_out_image = (_out_image * 255).astype(np.uint8)
result_image = Image.fromarray(_out_image)
result_image.save(output_image_path)
    // roundfor(int i = 0; i < INPUT_WIDTH; i++) {for(int j = 0; j < INPUT_HEIGHT; j++) {int index = j * INPUT_WIDTH + i;output_float_[index] = std::round(output_float_[index]);}}output = cv::Mat(INPUT_HEIGHT, INPUT_WIDTH, CV_32F, output_float_);output *= 255.0;output.convertTo(output, CV_8U);cv::resize(output, output, cv::Size(pre_width, pre_height), cv::INTER_CUBIC);

因为图像是缩放过一次的,最后给缩放回去。

main.cpp测试代码

在main.cpp编写测试示例,一般是建议将类用智能指针管理:

std::shared_ptr<UNet> unet_infer = std::make_shared<UNet>();std::string model_path = "./weights/unet_simple_trt.engine";if(unet_infer) {if(unet_infer->loadTrtModel(model_path))std::cout << "UNet Init Successful! \n";else std::cout << "UNet Init Failed! \n";
}

推理:

cv::Mat img1 = cv::imread("./test_images/val_20_A.png");
cv::Mat img2 = cv::imread("./test_images/val_20_B.png");
cv::Mat result;if(unet_infer->trt_infer(img1, img2, result)) {std::cout << "UNet Infer Successfully! \n";
} else {std::cout << "UNet Infer Failed! \n";
}

当然,最后可以测试一下推理速度以及输出是不是一致:

int count = 100;
int cost = 0;for(int i = 0; i < count; i++) {    auto start = std::chrono::high_resolution_clock::now();    bool success = unet_infer->trt_infer(img1, img2, result);auto end = std::chrono::high_resolution_clock::now();cost += std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}std::cout << "duration: " << (float)(cost) / count << " ms" << std::endl; if(!result.empty()) {cv::imwrite("./result.png", result);
}

显存占用:

对比了一下,1024x1024的输入,大概会消耗1G的显存,如果你缩小图像后再计算,效果会差一些。

计算耗时:

大概是80~90ms一张吧。

小结

上边只是初步实现了变换检测的推理,但是图像预处理与后处理还是有很多可以优化改进的地方,后边有时间再补上吧。

参考资料

ESCNet

ChangeDetection_GUI

TensorRT

tensorrtx

基于CUDA的并行计算技术

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

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

相关文章

网络攻击DDOoS的原理、攻击手段及防范措施详解

一、DDoS的原理 1. 原理 DDoS&#xff08;分布式拒绝服务攻击&#xff09;是利用大量的僵尸主机对受害者发起攻击&#xff0c;从而造成受害者的资源被耗尽无法为合法用户提供服务。DDoS一般采用三级结构&#xff0c;包括&#xff1a; 攻击者&#xff1a;攻击指令的发起方僵尸…

【鸿蒙HarmonyOS NEXT】数据存储之分布式键值数据库

【鸿蒙HarmonyOS NEXT】数据存储之分布式键值数据库 一、环境说明二、分布式键值数据库介绍三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、分布式键值数据库介绍 KVStore简介&#xff1a; 分布式键值数据库…

OpenHarmony(鸿蒙南向)——平台驱动指南【DAC】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 DAC&#xff08;Digital to Analog Converter&…

Python办公自动化之Word

在现代办公环境中&#xff0c;自动化无疑是提升工作效率的关键。特别是处理文档的工作&#xff0c;很多人可能花费大量时间在重复性任务上。那么&#xff0c;有没有一种方法可以让我们用 Python 来自动化 Word 文档的操作呢&#xff1f;今天&#xff0c;我们来聊聊如何用 Pytho…

AI Agent应用出路到底在哪?

1 Agent/Function Call 的定义 Overview of a LLM-powered autonomous agent system&#xff1a; Agent学会调用外部应用程序接口&#xff0c;以获取模型权重中缺失的额外信息&#xff08;预训练后通常难以更改&#xff09;&#xff0c;包括当前信息、代码执行能力、专有信息源…

Docker安装与应用

前言 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言开发。Docker 可以让开发者打包他们的应用以及依赖包到一个轻 量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互 之间…

大语言模型之LlaMA系列- LlaMA 2及LLaMA2_chat(上)

LlaMA 2是一个经过预训练与微调的基于自回归的transformer的LLMs&#xff0c;参数从7B至70B。同期推出的Llama 2-Chat是Llama 2专门为对话领域微调的模型。 在许多开放的基准测试中Llama 2-Chat优于其他开源的聊天模型&#xff0c;此外Llama 2-Chat还做了可用性与安全性评估。 …

物联网系统中基于IIC通信的数字温度传感器测温方案

01 物联网系统中为什么要使用数字式温度传感器芯片 物联网系统中使用数字式温度传感器芯片的原因主要有以下几点&#xff1a; 高精度与稳定性 高精度测量&#xff1a;数字式温度传感器芯片&#xff0c;如DS18B20&#xff0c;采用芯片集成技术&#xff0c;能够有效抑制外界不…

2024-9-28 QT登录框基础练习

1.头文件 #ifndef LOGINWINDOW_H #define LOGINWINDOW_H#include <QWidget> #include <QPushButton> #include <QLineEdit> #include <QVBoxLayout>class LoginWindow : public QWidget {Q_OBJECTpublic:// 构造函数LoginWindow(QWidget *parent nul…

打造备份一体机,群晖科技平台化战略再进阶

数字经济时代&#xff0c;海量数据不断涌现&#xff0c;并成为核心生产要素&#xff0c;驱动着企业生产方式和商业模式发生深刻变革。 与其他生产要素不同&#xff0c;数据要素具有非稀缺性、非竞争性等特征&#xff0c;且只有在具体业务场景中才能充分释放其价值。尤其是近年…

Element-Plus中上传文件upload取消提示按钮与文字

去除提示按钮与文字 添加样式&#xff0c;让这个div进行隐藏 .el-upload__input {display: none !important; }

物联网系统中高精度压力检测方案_压力变送器

01 物联网系统中为什么要使用压力变送器 在物联网系统中使用压力变送器的原因主要基于以下几个方面&#xff1a; 感知层的核心作用 物联网系统主要由感知层、传输层、平台层和应用层组成。感知层作为物联网的“排头兵”&#xff0c;负责收集物理世界中的各种信息。压力变送…

在二维平面中,利用时差定位(TDOA)技术,结合N个锚点,通过三边法进行精确定位,采用MATLAB实现

文章目录 主程序程序代码运行结果 主程序 主程序代码如下&#xff1a; % TDOA测距定位&#xff0c;二维平面&#xff0c; 4个锚节点的情况 % author:Evand&#xff08;VX&#xff1a;matlabfilter&#xff0c;除前期达成一致外&#xff0c;讲解需付费&#xff09; % 2024年9月…

node-rtsp-stream、jsmpeg.min.js实现rtsp视频在web端播放

1. 服务地址&#xff08;私有&#xff09;&#xff1a;https://gitee.com/nnlss/video-node-server 2.node-rtsp-stream 需要安装FFMPEG&#xff1b; 3.给推拉流做了开关&#xff0c;可借助http请求&#xff0c;有更好方式可联系&#xff1b; 4.存在问题&#xff1a; 1&…

单点登录(SSO)基础

单点登录&#xff08;SSO, Single Sign-On&#xff09; 是一种身份认证机制&#xff0c;允许用户在多个独立的应用系统中只进行一次登录操作&#xff0c;即可访问所有授权的应用或服务&#xff0c;而无需每次切换应用时都进行登录。SSO 提高了用户体验的便捷性&#xff0c;同时…

基于springboot的书店图书销售管理系统的设计与实现 (含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于springboot的书店图书销售管理系统拥有三个角色 管理员&#xff1a;用户管理、角色管理、权限管理、店铺管理等商家&#xff1a;图书管理、上架图书、访问量统计、销售总额统计、订单…

【C++】类和对象(基本概念)

类的引入 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成。 C语言结构体中只能定…

【最新】微信小程序连接onenet——stm32+esp8266+onenet实现查看温湿度,控制单片机

微信小程序——stm32esp8266onenet实现查看温湿度&#xff0c;控制单片机 &#xff08;最新已验证&#xff09;stm32 新版 onenet dht11esp8266/01s mqtt物联网上报温湿度和控制单片机(保姆级教程) &#xff1a;↓↓&#x1f447; &#x1f447; &#x1f447; &#x1f447…

如何用好通义灵码企业知识库问答能力?

通义灵码企业版&#xff1a;通义灵码企业标准版快速入门_智能编码助手_AI编程_智能编码助手通义灵码(Lingma)-阿里云帮助中心 通义灵码提供了基于企业知识库的问答检索增强的能力&#xff0c;在开发者使用通义灵码 IDE 插件时&#xff0c;可以结合企业知识库内上传的文档、文件…

面试中顺序表常考的十大题目解析

在数据结构与算法的面试中&#xff0c;顺序表是一个常见的考点。它作为一种基础的数据结构&#xff0c;涵盖了多种操作和概念&#xff0c;以下将详细介绍面试中关于顺序表常考的十大题目。 &#x1f49d;&#x1f49d;&#x1f49d;如果你对顺序表的概念与理解还存在疑惑&#…