使用javaScript生成随机迷宫

效果预览

我制作了一个 CodePen,以动画形式展示随机迷宫的创建过程,以便更加直观的观察算法的工作原理。(点击即可访问生成新迷宫)

基本思路

使用javaScript生成随机迷宫的核心思想是使用一个“深度优先搜索”(DFS)算法。该算法可以从一个起点开始,探索未访问过的区域,并通过回溯找到所有可通行的路径。通过这种方式,我们可以在一个预定的网格中随机生成迷宫。

定义约束条件

迷宫有很多不同的类型和生成方法。在开始编写代码之前,首先思考希望得到一个什么样子的迷宫,并考虑如何避免过度复杂化,最后设定了一些基本的约束条件:

  • 迷宫是矩形的。虽然六边形和圆形迷宫更美观,但它们的实现相对也 更复杂。
  • 迷宫中只有一条路径。
  • 这条路径从迷宫的左边缘到右边缘。
  • 迷宫中的每个方格都应该是有可能经过的。

有了这些约束条件,就可以准备开始进行具体的规划。

迷宫游戏规划

把生成迷宫的过程分为三个步骤:

  1. 创建一个矩形网格。
  2. 找到从左侧到右侧的路径。
  3. 从主路径上分支,填充剩余的网格。

在开始编写代码之前,可以通过一些草图来理解这些步骤:

  • 画一个正方形网格。
  • 在这个正方形中画一条锯齿形的路径。
  • 用不同的颜色标出从主路径上分出的其他路径,填充整个方格。

步骤 1:创建矩形网格

首先,需要确定网格的大小。为了简化操作从一个10x10的网格开始。将这些数值定义为变量,以便后续修改时更为方便:

const gridHeight = 10;
const gridWidth = 10;

步骤 2:寻找所有经过的路径

接下来需要找到一条从左边缘到右边缘的路径,可以将路径存储为一系列具有 X 和 Y 坐标的点。上图中的主路径如下所示:

const mainPath = [{x: -1, y: 7},{x: 0, y: 7},{x: 0, y: 6},{x: 0, y: 5},{x: 1, y: 5},/* ... 更多点 ... */{x: 9, y: 1},{x: 10, y: 1}
];

再为迷宫选择一个起点。可以随机选择一个 Y 坐标,并基于这个坐标设置两个点,一个在网格的左边缘,另一个稍微进入网格:

import { randomInt } from 'https://unpkg.com/randomness-helpers@0.0.1/dist/index.js';function mainPathStartPoints() {const yStart = randomInt(0, gridHeight - 1);return [{x: -1, y: yStart},{x: 0, y: yStart}]
}

注:使用名为randomness-helpers的npm 包中的一些辅助函数,用于帮助执行重复任务,如随机选择整数和数组项

接下来构建路径的其余部分,编写一个函数,该函数接受一个坐标点,并返回该点相邻的一个随机点:

import { randomItemInArray } from 'https://unpkg.com/randomness-helpers@0.0.1/dist/index.js';function findNextPoint(point) {// 构建相邻点的数组const potentialPoints = [];// 如果"上"在网格内,添加它作为潜在点if(point.y - 1 >= 0) {potentialPoints.push({y: point.y - 1,x: point.x});}// 如果"下"在网格内,添加它作为潜在点if(point.y + 1 < gridHeight) {potentialPoints.push({y: point.y + 1,x: point.x});}// 如果"左"在网格内,添加它作为潜在点if(point.x - 1 >= 0) {potentialPoints.push({y: point.y,x: point.x - 1});}// 如果"右"在网格内,添加它作为潜在点if(point.x + 1 < gridWidth) {potentialPoints.push({y: point.y,x: point.x + 1});}// 随机选择一个点添加到路径中  return randomItemInArray(potentialPoints);
}

可以通过多次调用此函数来构建路径,直到达到网格的最右端边缘,然后添加一个额外的坐标点,表示位于右边缘外部的终点。

function getMainPathPoints() {const mainPathPoints = mainPathStartPoints();// 一直添加点,直到最后一个点位于右边缘while(mainPathPoints.at(-1).x < gridWidth) {mainPathPoints.push(findNextPoint(mainPathPoints.at(-1)));}// 添加一个点,Y 坐标与最后一个点相同,但位于右边缘外部mainPathPoints.push({x: gridWidth,y: mainPathPoints.at(-1).y});
}

在上面的代码示例中使用数组方法获取数组中的最后点。

按理说实际效果其实已经接近了,但运作之后我发现逻辑上有一个大问题(点击的 在线预览 查看其实际运行效果) 

运行之后总会运行到重复的坐标点 总结下来大概有这几个问题:

  • 迷宫内的路径实在是太多了
  • 从左边缘起点到达右边缘所需时间过长
  • 不知道哪些格子已经走过了,所以无法知道剩下的迷宫该应该走哪些格子。

(这也导致了迷宫显示的问题,不过这些问题解决了其他的就迎经过,确保不会重复走,可以创建一个二维数组,初始时用 0 来表示每个空白格子。如果某个格子被占用,就把 0 改成 1。

const gridData = new Array(gridHeight).fill().map(() => new Array(gridWidth).fill(0)
);

这段代码会创建一个类似这样的数组:

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]

然后可以增加一个函数,用来标记某个格子已经走过了,走过之后直接把 0 改为 1:

function markPointAsTaken(point, value = 1) {gridData[point.y][point.x] = value;
}

例如,假设已经在迷宫中绘制了一条路径并标记了占用的格子,数组就会变成类似于这样:

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],[0, 0, 1, 1, 1, 0, 1, 1, 0, 0],[0, 1, 1, 0, 1, 0, 1, 1, 1, 0],[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],[1, 1, 0, 1, 1, 1, 1, 1, 1, 0],[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],[1, 0, 0, 1, 1, 1, 0, 1, 1, 0],[0, 0, 0, 1, 1, 1, 0, 1, 1, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]

接下来再增加一个辅助函数,检查某个格子是否为空且在规定区域内,修改之前写的 findNextPoint 函数,让它只返回有效的空格坐标:

function isCellEmpty(point) {// 检查格子是否在网格外if (point.x < 0 || point.x >= gridWidth || point.y < 0 || point.y >= gridHeight) {return false;}// 检查格子是否为空return gridData[point.y][point.x] === 0;
}

到目前为止已经有了寻找路径所需的所有函数,但还有一个问题:如果运行中途被“卡住”了该怎么办?(如果所有相邻的格子都已占用,应该走哪里呢?)

最简单的解决办法就是:如果卡住了就直接重置数组!重新开始,点击看看效果

这个办法还算有效,但如果尝试用它来求解更大的迷宫,可能会需要很长时间才能找到有效路径,另一个解决办法就是,可以尝试“后退”几步并从另一个位置继续寻找,从而使逻辑更加智能 点击在线体验

// 导入随机工具库,提供随机整数、随机选择数组元素和随机概率的功能
import { randomInt, randomItemInArray, randomChance } from 'https://unpkg.com/randomness-helpers@0.0.1/dist/index.js';// 获取页面中的 SVG 元素和相关的 DOM 元素
const svgEl = document.querySelector('svg');               // 用于显示迷宫路径的SVG元素
const patternEl = document.querySelector('.pattern');       // 用于渲染迷宫路径的容器
const gridEl = document.querySelector('.grid');             // 用于渲染网格的容器// 定义迷宫的基本配置
const gridWidth = 20;   // 网格的宽度(列数)
const gridHeight = 20;  // 网格的高度(行数)
const scale = 50;       // 每个网格单元的缩放比例(用于计算位置)
const splittingChance = 0.1; // 额外路径的生成概率
const animationSpeed = 20;  // 路径绘制的动画速度(毫秒)
const retryLimit = 30;      // 最大重试次数,如果路径无法继续,重新开始// 初始化变量
let interval;             // 动画定时器
let failedCount = 0;      // 失败计数器,记录路径生成失败的次数
let mainPathPoints = [];  // 主路径的点集合
let otherPaths = [];      // 额外路径的集合// 创建初始的网格数据
let gridData = buildFreshGrid();// 绘制网格的函数,返回 SVG 格式的网格线条
function drawGrid() {let gridMarkup = ''; // 用于存储网格的SVG markup// 绘制横向的网格线for (let y = 0; y <= gridHeight; y++) {gridMarkup += `<line class="grid-line"x1="0" x2="${gridWidth * scale}"y1="${y * scale}"y2="${y * scale}"/>`;}// 绘制纵向的网格线for (let x = 0; x <= gridWidth; x++) {gridMarkup += `<line class="grid-line"y1="0" y2="${gridHeight * scale}"x1="${x * scale}"x2="${x * scale}"/>`;}return gridMarkup; // 返回生成的网格线的HTML字符串
}// 创建新的空网格,初始化所有单元格为0(未占用)
function buildFreshGrid() {return new Array(gridHeight).fill().map(() => new Array(gridWidth).fill(0));
}// 调整迷宫路径点的位置,转换为适应 SVG 坐标系
function adjustMazePoint(point) {return {x: scale / 2 + point.x * scale,y: scale / 2 + point.y * scale};
}// 将路径点数组转换为 SVG 路径数据
function buildPathData(points) {points = points.map(adjustMazePoint); // 调整每个路径点位置const firstPoint = points.shift(); // 移除第一个点作为起始点// SVG路径的M命令表示移动到起始点let commands = [`M ${firstPoint.x}, ${firstPoint.y}`];// 遍历路径点,使用L命令连接每个点points.forEach(point => {commands.push(`L ${point.x}, ${point.y}`);});return commands.join(' '); // 返回完整的SVG路径字符串
}// 绘制路径的函数
function drawLine(points, className = '') {return `<pathclass="maze-path ${className}" d="${buildPathData(points)}"/>`;
}// 生成主路径的起始点,随机选择一个起始位置
function mainPathStartPoints() {const yStart = randomInt(0, gridHeight - 1); // 随机选择一个Y轴上的起始位置return [{ x: -1, y: yStart }, // 主路径的起始点,X为-1表示从网格外开始{ x: 0, y: yStart }   // 第二个起始点,X为0表示网格内的起点];
}// 标记网格中的一个点为已占用,值为1表示占用
function markPointAsTaken(point, value = 1) {gridData[point.y][point.x] = value;
}// 寻找当前点的相邻未占用的点,返回一个随机的相邻点
function findNextPoint(point) {const potentialPoints = [];// 判断上、下、左、右四个方向的相邻点是否未被占用if (gridData[point.y - 1]?.[point.x] === 0) {potentialPoints.push({ y: point.y - 1, x: point.x });}if (gridData[point.y + 1]?.[point.x] === 0) {potentialPoints.push({ y: point.y + 1, x: point.x });}if (gridData[point.y]?.[point.x - 1] === 0) {potentialPoints.push({ y: point.y, x: point.x - 1 });}if (gridData[point.y]?.[point.x + 1] === 0) {potentialPoints.push({ y: point.y, x: point.x + 1 });}// 如果有可用的相邻点,随机返回其中一个,否则返回undefinedreturn potentialPoints.length === 0 ? undefined : randomItemInArray(potentialPoints);
}// 重置迷宫的状态,包括路径、网格数据和失败计数器
function refreshState() {mainPathPoints = mainPathStartPoints();   // 重置主路径的起始点gridData = buildFreshGrid();              // 重置网格数据markPointAsTaken(mainPathPoints.at(-1));  // 标记起始点为占用otherPaths = [];                          // 清空额外路径failedCount = 0;                          // 重置失败计数器
}// 绘制所有的路径(包括主路径和额外路径)
function drawLines() {let markup = '';// 绘制额外路径markup += otherPaths.map(drawLine).join('');// 绘制主路径markup += drawLine(mainPathPoints, 'main');patternEl.innerHTML = markup;  // 更新SVG中的内容
}// 主路径生成的函数,使用随机算法生成迷宫的主路径
function buildMainPath() {refreshState();  // 重置迷宫状态drawLines();  // 绘制当前的路径interval = setInterval(() => {const nextPoint = findNextPoint(mainPathPoints.at(-1));  // 寻找主路径的下一个点// 如果没有可用的下一个点,说明当前路径无法继续if (!nextPoint) {if (failedCount > retryLimit) {  // 如果重试次数超过最大值,重新生成路径refreshState(); } else {failedCount++;for (let i = 0; i < failedCount; i++) {markPointAsTaken(mainPathPoints.pop(), 0);  // 退回路径并清除占用}}} else {mainPathPoints.push(nextPoint);  // 添加下一个路径点markPointAsTaken(nextPoint);     // 标记该点为占用// 如果到达迷宫的右侧边界,停止路径生成if (nextPoint.x === gridWidth - 1) {mainPathPoints.push({ x: nextPoint.x + 1, y: nextPoint.y });  // 添加右边界外的点clearInterval(interval);  // 停止定时器}}drawLines();  // 更新路径显示}, animationSpeed);
}// 随机生成更多的额外路径
function addMorePaths() {gridData.forEach((row, y) => {row.forEach((cell, x) => {// 如果该点已经被占用,并且符合生成额外路径的概率if (cell && randomChance(splittingChance)) {otherPaths.push([{ y, x }]);  // 创建新的路径}});});
}// 判断迷宫是否已经完全生成(所有点都被占用)
function mazeComplete() {return gridData.flat().every(cell => cell === 1);  // 检查所有单元格是否都已被占用
}// 生成额外路径的函数,使用定时器逐步绘制这些路径
function buildOtherPaths() {interval = setInterval(() => {addMorePaths();  // 添加更多路径// 遍历所有额外路径,尝试继续生成otherPaths.forEach((path) => {const nextPoint = findNextPoint(path.at(-1));  // 寻找下一个点if (nextPoint) {path.push(nextPoint);  // 如果找到了点,加入路径markPointAsTaken(nextPoint);  // 标记该点为占用}});drawLines();  // 更新显示路径if (mazeComplete()) {  // 如果迷宫已完成,停止定时器clearInterval(interval);console.log('maze done');}}, animationSpeed);
}// 初始绘制函数
function draw() {gridEl.innerHTML = drawGrid();  // 绘制网格const mazeWidth = gridWidth * scale;   // 计算迷宫宽度const mazeHeight = gridHeight * scale; // 计算迷宫高度// 设置 SVG 的视口大小和尺寸svgEl.setAttribute('viewBox', `0 0 ${mazeWidth} ${mazeHeight}`);svgEl.setAttribute('width', mazeWidth);svgEl.setAttribute('height', mazeHeight);patternEl.innerHTML = '';  // 清空当前的路径clearInterval(interval);  // 清除现有的定时器buildMainPath();  // 开始生成主路径
}// 初始绘制网格
gridEl.innerHTML = drawGrid();// 绑定点击事件,用于重新生成迷宫
document.querySelector('.randomize').addEventListener('click', draw);


 步骤 3:从主路径分支,填充剩余的网格

已经有了主路径,接下来要做的就是填充剩余的网格。首先创建一个辅助函数,检查每个单元格是否已被占用:

function mazeComplete() { // 将二维数组展平成一维数组,然后检查是否所有单元格都被设置为1return gridData.flat().every(cell => cell === 1);
}

接下来开始填充迷宫。每次循环时将执行以下操作:

  1. 遍历每条路径,尽可能为其添加新格子。
  2. 遍历所有路径上的格子,在某些格子上随机添加新路径。
  3. 一直循环,直到网格所有格子完全占用
import { randomChance } from 'https://unpkg.com/randomness-helpers@0.0.1/dist/index.js';const otherPaths = [];function buildOtherPaths() {while(!mazeComplete()) { // 添加一些新路径addMorePaths();// 遍历所有路径otherPaths.forEach((path) => {// 尝试在每条路径上添加一个点const nextPoint = findNextPoint(path.at(-1));if(nextPoint) {path.push(nextPoint);markPointAsTaken(nextPoint);}});}
}function addMorePaths() {// 遍历网格中的所有单元格gridData.forEach((row, y) => {row.forEach((cell, x) => {// 如果该单元格已被占用,则以10%的几率从该单元格开始新路径if(cell && randomChance(0.1)) {otherPaths.push([{y,x,}]);}})})
}

这样就完成了迷宫的生成!可以点击“随机生成” 来生成新的迷宫。

完整代码示例

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

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

相关文章

【ArkTS】列表组件的“下拉刷新”和“上拉加载”

系列文章目录 【ArkTS】关于ForEach的第三个参数键值 【ArkTS】“一篇带你读懂ForEach和LazyForEach” 【小白拓展】 【ArkTS】“一篇带你掌握TaskPool与Worker两种多线程并发方案” 【ArkTS】 一篇带你掌握“语音转文字技术” --内附详细代码 【ArkTS】技能提高–“用户授权”…

Java项目实战II基于微信小程序的消防隐患在线举报系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着城市化进程的加快&…

每日十题八股-2024年12月7日

1.说说hashmap的负载因子 2.Hashmap和Hashtable有什么不一样的&#xff1f;Hashmap一般怎么用&#xff1f; 3.ConcurrentHashMap怎么实现的&#xff1f; 4.分段锁怎么加锁的&#xff1f; 5.分段锁是可重入的吗&#xff1f; 6.已经用了synchronized&#xff0c;为什么还要用CAS呢…

CTF学习24.11.19[音频隐写]

MISC07[音频隐写] 隐写术 隐写术是一门关于信息隐藏的技巧与科学&#xff0c;所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。隐写术的英文叫做Steganography&#xff0c;来源于特里特米乌斯的一本讲述密码学与隐写术的著作Steganograp…

掌握谈判技巧,达成双赢协议

在当今竞争激烈且合作频繁的社会环境中&#xff0c;谈判成为了我们解决分歧、谋求共同发展的重要手段。无论是商业合作、职场交流&#xff0c;还是国际事务协商&#xff0c;掌握谈判技巧以达成双赢协议都具有极其关键的意义。它不仅能够让各方在利益分配上找到平衡点&#xff0…

HTTPS的工作过程

1.HTTPS协议原理 1.1HTTPS协议的由来 HTTP在传输网络数据的时候是明文传输的&#xff0c;信息容易被窃听甚至篡改&#xff0c;因此他是一个不安全的协议&#xff08;但效率高&#xff09;。在如今的网络环境中&#xff0c;数据安全是很重要的&#xff08;比如支付密码又或者各…

鸿蒙UI开发——渐变色效果

1、概 述 ArkTs可以通过颜色渐变接口&#xff0c;设置组件的背景颜色渐变效果&#xff0c;实现在两个或多个指定的颜色之间进行平稳的过渡。 目前提供三种渐变类型&#xff1a;线性渐变、角度渐变、径向渐变。 我们在鸿蒙UI布局实战 —— 个人中心页面开发中&#xff0c;默认…

距离与AoA辅助的三维测距算法(适用于四个基站的情况的单点定位),MATLAB代码

本MATLAB 代码实现了一个基于LOS/NLOS混合环境的单点定位系统&#xff0c;主要用于估计目标物体的单点位 文章目录 代码运行结果源代码代码功能概述主要步骤分析初始化部分 绘图与输出 代码运行结果 定位结果如下&#xff1a; 命令行的坐标和误差输出&#xff1a; 部分代码…

Vue指令(一)--v-html、v-show、v-if、v-else、v-else-if、v-on、v-bind、v-for、v-model

目录 &#xff08;一&#xff09;初识指令和内容渲染指令v-html 1.v-html 案例&#xff1a; 官网的API文档 &#xff08;二&#xff09;条件渲染指令v-show和v-if 1. v-show 2. v-if &#xff08;三&#xff09;条件渲染指令v-else和v-else-if 案例 &#xff08;四…

记一次由docker容器使得服务器cpu占满密码和密钥无法访问bug

Bug场景&#xff1a; 前几天在服务器上部署了一个免费影视网站&#xff0c;这个应用需要四个容器&#xff0c;同时之前的建站软件workpress也是使用docker部署的&#xff0c;也使用了三个容器。在使用workpress之前&#xff0c;我将影视软件的容器全部停止。 再使用workpress…

【JavaEE 进阶(一)】SpringBoot(上)

博主主页: 33的博客 文章专栏分类:JavaEE ??我的代码仓库: 33的代码仓库?? ???关注我带你了解更多进阶知识 目录 1.前言2.Spring3.第一个SpringBoot程序4.Spring MVC 4.1建立连接 4.1.1RequestMapping使用 4.2请求 4.2.1传递单个参数4.2.2传递多个参数4.2.3传递一个对象…

(未更新完)day30-IO-阶段综合案例(带权重的随机每日一记)(笔记完全来源于黑马程序员)

目录 0 目录一、听黑马阿玮的视频记录的笔记1. 制造假数据1.1 如何制造假数据1.2 练习1-生成方式1&#xff1a;爬取姓氏、男生名字、女生名字1.3 练习2-生成方式1&#xff1a;在练习1的基础上&#xff0c;将数据写入本地文件1.4 练习3-生成方式2&#xff1a;利用糊涂包生成假数…

FPGA中所有tile介绍

FPGA中包含的tile类型&#xff0c;以xinlinx 7k为例&#xff0c;可以通过f4pga项目中的原语文件夹查看&#xff0c;主要包含以下这些&#xff1a; 以下是您提到的 Xilinx 7 系列 FPGA 中各种模块的含义及用途&#xff1a; 1. BRAM (Block RAM) BRAM 是 FPGA 中的块存储资源&…

如何解决压测过程中JMeter堆内存溢出问题

如何解决压测过程中JMeter堆内存溢出问题 背景一、为什么会堆内存溢出&#xff1f;二、解决堆内存溢出措施三、堆内存参数应该怎么调整&#xff1f;四、堆内存大小配置建议 背景 Windows环境下使用JMeter压测运行一段时间后&#xff0c;JMeter日志窗口报错“java.lang.OutOfMe…

嵌入式蓝桥杯学习4 lcd移植

cubemx配置 复制前面配置过的文件 打开cubemx&#xff0c;将PB8,PB9配置为GPIO-Output。 点击GENERATE CODE. 文件移植 1.打开比赛提供的文件包&#xff0c;点击Inc文件夹 2.点击Inc文件夹。复制fonts.h和lcd.h&#xff0c;粘贴到我们自己的工程文件夹的bsp中&#xff08…

学习记录,正则表达式, 隐式转换

正则表达式 \\&#xff1a;表示正则表达式 W: 表示一个非字&#xff08;不是一个字&#xff0c;例如&#xff1a;空格&#xff0c;逗号&#xff0c;句号&#xff09; W: 多个非字 基本组成部分 1.字符字面量&#xff1a; 普通字符&#xff1a;在正则表达式中&#xff0c;大…

标书里的“废标雷区”:你踩过几个?

在投标领域&#xff0c;标书的质量不仅决定了中标的可能性&#xff0c;更是体现企业专业度的关键。但即便是经验丰富的投标人&#xff0c;也难免会在标书编制过程中踩中“废标雷区”。这些雷区可能隐藏在技术方案的细节中&#xff0c;也可能是投标文件格式的规范问题。以下&…

k8s-编写CSI插件(3)

1、概述 在 Kubernetes 中&#xff0c;存储插件的开发主要有以下几种方式&#xff1a; CSI插件&#xff1a;Container Storage Interface (CSI) 是 Kubernetes 的标准插件接口&#xff0c;是全新的插件方案&#xff0c;插件和驱动调用通过grpc协议&#xff0c;功能丰富&#x…

R语言机器学习论文(三):特征提取

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据一、数据归一化二、离散型分类变量的编码三、筛选特征四、重要特征五、输出结果六、总结系统信息介绍 在数据分析和机器学习项目中,经常需要对数据进行预…

用 NotePad++ 运行 Java 程序

安装包 网盘链接 下载得到的安装包: 安装步骤 双击安装包开始安装. 安装完成: 配置编码 用 NotePad 写 Java 程序时, 需要设置编码. 在 设置, 首选项, 新建 中进行设置, 可以对每一个新建的文件起作用. 之前写的文件不起作用. 在文件名处右键, 可以快速打开 CMD 窗口, 且路…