0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

简述游戏中常用的两种随机算法(上)

jf_78858299 来源:labuladong 作者:labuladong 2023-04-12 11:43 次阅读

没事儿的时候我喜欢玩玩那些经典的 2D 网页小游戏,我发现很多游戏都要涉及地图的随机生成,比如扫雷游戏中地雷的位置应该是随机分布的:

图片

再比如经典炸弹人游戏,障碍物的位置也是有一定随机性的:

图片

这些 2D 游戏相较现在的大型 3D 游戏虽然看起来有些简陋,但依然用到很多有趣算法技巧,本文就来深入研究一下地图的随机生成算法。

2D 游戏的地图肯定可以抽象成一个二维矩阵,就拿扫雷举例吧,我们可以用下面这个类表示扫雷的棋盘:

class Game {
    int m, n;
    // 大小为 m * n 的二维棋盘
    // 值为 true 的地方代表有雷,false 代表没有雷
    boolean[][] board;
}

如果你想在棋盘中随机生成k个地雷,也就是说你需要在board中生成k个不同的(x, y)坐标,且这里面x, y都是随机生成的。

对于这个需求, 首先一个优化就是对二维矩阵进行「降维打击」,把二维数组转化成一维数组

class Game {
    int m, n;
    // 长度为 m * n 的一维棋盘
    // 值为 true 的地方代表有雷,false 代表没有雷
    boolean[] board;

    // 将二维数组中的坐标 (x, y) 转化为一维数组中的索引
    int encode(int x, int y) {
        return x * n + y;
    }

    // 将一维数组中的索引转化为二维数组中的坐标 (x, y)
    int[] decode(int index) {
        return new int[] {index / n, index % n};
    }
}

这样,我们只要在[0, m * n)中选取一个随机数,就相当于在二维数组中随机选取了一个元素。

但问题是,我们现在需要随机选出k不同的位置放地雷。你可能说,那在[0, m * n)中选出来k个随机数不就行了?

是的,但实际操作起来有些麻烦,因为你很难保证随机数不重复。如果出现重复的随机数,你就得再随机选一次,直到找到k个不同的随机数。

如果k比较小m * n比较大,那出现重复随机数的概率还比较低,但如果km * n的大小接近,那么出现重复随机数的概率非常高,算法的效率就会大幅下降。

那么,我们有没有更好的办法能够在线性的时间复杂度解决这个问题?其实是有的,而且有很多种解决方案。

洗牌算法

第一个解决方案,我们可以换个思路,避开「在数组中随机选择k个元素」这个问题,把问题转化成「如何随机打乱一个数组」

现在想随机初始化k颗地雷的位置,你可以先把这k颗地雷放在board开头,然后把board数组随机打乱,这样地雷不就随机分布到board数组的各个地方了吗?

洗牌算法,或者叫随机乱置算法就是专门解决这个问题的,我们可以看下力扣第 384 题「打乱数组」:

图片

这个shuffle函数是算法的关键,直接看解法代码吧:

class Solution {
    private int[] nums;
    private Random rand = new Random();
    
    public Solution(int[] nums) {
        this.nums = nums;
    }
    
    public int[] reset() {
        return nums;
    }
    
    // 洗牌算法
    public int[] shuffle() {
        int n = nums.length;
        int[] copy =  Arrays.copyOf(nums, n);
        for (int i = 0 ; i < n; i++) {
            // 生成一个 [i, n-1] 区间内的随机数
            int r = i + rand.nextInt(n - i);
            // 交换 nums[i] 和 nums[r]
            swap(copy, i, r);
        }
        return copy;
    }
    
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

洗牌算法的时间复杂度是 O(N),而且逻辑很简单,关键在于让你证明为什么这样做是正确的。排序算法的结果是唯一可以很容易检验的,但随机乱置算法不一样,乱可以有很多种,你怎么能证明你的算法是「真的乱」呢?

分析洗牌算法正确性的准则:产生的结果必须有n!种可能 。这个很好解释,因为一个长度为n的数组的全排列就有n!种,也就是说打乱结果总共有n!种。算法必须能够反映这个事实,才是正确的。

有了这个原则再看代码应该就容易理解了:

对于nums[0],我们把它随机换到了索引[0, n)上,共有n种可能性;

对于nums[1],我们把它随机换到了索引[1, n)上,共有n - 1种可能性;

对于nums[2],我们把它随机换到了索引[2, n)上,共有n - 2种可能性;

以此类推,该算法可以生成n!种可能的结果,所以这个算法是正确的,能够保证随机性。

水塘抽样算法

学会了洗牌算法,扫雷游戏的地雷随机初始化问题就解决了。不过别忘了,洗牌算法只是一个取巧方案,我们还是得面对「在若干元素中随机选择k个元素」这个终极问题。

要知道洗牌算法能够生效的前提是你使用数组这种数据结构,如果让你在一条链表中随机选择k个元素,肯定不能再用洗牌算法来蒙混过关了。

再比如,假设我们的扫雷游戏中棋盘的长和宽非常大,已经不能在内存中装下一个大小为m * nboard数组了,我们只能维护一个大小为k的数组记录地雷的位置:

class Game {
    // 棋盘的行数和列数(非常大)
    int m, n;
    // 长度为 k 的数组,记录 k 个地雷的一维索引
    int[] mines;

    // 将二维数组中的坐标 (x, y) 转化为一维数组中的索引
    int encode(int x, int y) {
        return x * n + y;
    }

    // 将一维数组中的索引转化为二维数组中的坐标 (x, y)
    int[] decode(int index) {
        return new int[] {index / n, index % n};
    }
}

这样的话,我们必须想办法在[0, m*n)中随机选取k个不同的数字了。

这就是常见的随机抽样场景,常用的解法是水塘抽样算法(Reservoir Sampling) 。水塘抽样算法是一种随机概率算法,会者不难,难者不会。

我第一次见到这个算法问题是谷歌的一道算法题:给你一个未知长度的单链表,请你设计一个算法, 只能遍历一次 ,随机地返回链表中的一个节点。

这里说的随机是均匀随机(uniform random),也就是说,如果有n个元素,每个元素被选中的概率都是1/n,不可以有统计意义上的偏差。

一般的想法就是,我先遍历一遍链表,得到链表的总长度n,再生成一个[0,n-1)之间的随机数为索引,然后找到索引对应的节点。但这不符合只能遍历一次链表的要求。

这个问题的难点在于随机选择是「动态」的,比如说你现在你已经遍历了 5 个元素,你已经随机选取了其中的某个元素a作为结果,但是现在再给你一个新元素b,你应该留着a还是将b作为结果呢?以什么逻辑做出的选择,才能保证你的选择方法在概率上是公平的呢?

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 算法
    +关注

    关注

    23

    文章

    4599

    浏览量

    92618
  • 游戏
    +关注

    关注

    2

    文章

    736

    浏览量

    26276
收藏 人收藏

    评论

    相关推荐

    简述两种示波器测量眼图的差别

    简述两种示波器测量眼图的差别 中心议题: 力科示波器进行眼图测量 新旧款软件包使用方法不同 力科示波器捕获了50MS的数据,并一次性地
    发表于 04-21 16:52 3890次阅读
    <b class='flag-5'>简述</b><b class='flag-5'>两种</b>示波器测量眼图的差别

    电容式感应在电玩游戏中的应用

    。由于游戏杆具备的卓越模拟功能性,让其在最新的控制器重现生机。电容式感测技术(电容式触控感应)是最新的接口技术,能提高游戏控制器的可用性,以及最炫的机械设计。   电容式感测技术概观   电容式感测最
    发表于 09-12 16:43

    两种典型的ADRC算法介绍

    前言  上篇中详细阐述了经典的自抗扰控制算法的原理,本篇将围绕两种ADRC算法展开,针对扩张状态观测器的参数整定问题进行详解,同时,对跟踪微分器的几个重要应用进行介绍。两种典型的ADR
    发表于 09-07 08:02

    智能插座常用两种通信协议是什么?

    智能插座常用两种通信协议是什么?
    发表于 09-26 09:18

    网络中常用的队列管理方法比较

    本文主要介绍了网络中常用两种队列管理方法:先进先出(FIFO)和随机提前检测(RED),并且通过实验比较了这两种队列管理方法在解决网络拥塞控制方面的表现,体现了研究
    发表于 05-25 11:24 9次下载

    基于游戏中NPC路径规划的混合算法

    路径规划是游戏人工智能领域的核心问题,如何建立一高效的路径规划方法仍是研究的热点之一。针对游戏中NPC的路径规划问题,将A*算法与改进的人工势场法相结合,提出了一
    发表于 11-14 14:55 7次下载

    帕塞瓦定理的两种常见形式

    帕塞瓦定理的两种常见形式, 在我的《随机信号分析》里面作为附录4, 即帕塞瓦定理的两种常见形式, 第三形式即不常用的形式, 明天再给读者介
    的头像 发表于 04-02 11:13 9770次阅读

    Wincc如何与PLC进行通讯两种常用的方式介绍

    西门子WINCC与SiemensPLC通讯连接有多种方式,下面介绍两种常用的通讯方式。
    的头像 发表于 02-17 09:27 3w次阅读
    Wincc如何与PLC进行通讯<b class='flag-5'>两种</b><b class='flag-5'>常用</b>的方式介绍

    单片机常用两种延时控制方式

    单片机中常用的延时控制方式有两种。一是采用编程的方式达到延时的目的,另一方法则是通过单片机中的个定时器T0和T1进行计时达到延时的目的
    发表于 07-17 10:22 5881次阅读
    单片机<b class='flag-5'>常用</b>的<b class='flag-5'>两种</b>延时控制方式

    常用的hdl语言有哪两种

    Verilog HDL和VHDL是目前两种常用的硬件描述语言,同时也都是IEEE标准化的HDL语言。
    发表于 08-25 09:14 9203次阅读

    说透游戏中常用两种随机算法

    这些 2D 游戏相较现在的大型 3D 游戏虽然看起来有些简陋,但依然用到很多有趣算法技巧,本文就来深入研究一下地图的随机生成算法
    的头像 发表于 11-09 11:17 1076次阅读

    详解PMSM中常用两种坐标变换

    期介绍了Clarke的Park变化的基本原理,但是经过这两种变换后会存在两种系数,相信大家都很迷惑,这是什么原因? 主要原因是存在两种遵循的方式:1、变换前后电流所产生的旋转磁场等
    的头像 发表于 01-19 15:52 2356次阅读
    详解PMSM<b class='flag-5'>中常用</b>的<b class='flag-5'>两种</b>坐标变换

    简述游戏中常用两种随机算法(下)

    没事儿的时候我喜欢玩玩那些经典的 2D 网页小游戏,我发现很多游戏都要涉及地图的随机生成,比如扫雷游戏中地雷的位置应该是随机分布的:
    的头像 发表于 04-12 11:43 661次阅读
    <b class='flag-5'>简述</b><b class='flag-5'>游戏中常用</b>的<b class='flag-5'>两种</b><b class='flag-5'>随机</b><b class='flag-5'>算法</b>(下)

    基于Python实现随机森林算法

    机器学习算法是数据挖掘、数据能力分析和数学建模必不可少的一部分,而随机森林算法和决策树算法是其中较为常用
    的头像 发表于 09-21 11:17 1163次阅读
    基于Python实现<b class='flag-5'>随机</b>森林<b class='flag-5'>算法</b>

    PCBA加工中常见的两种焊接方式详解

    ,在PCBA行业中经常被使用。接下来深圳PCBA加工厂家为大家详细介绍PCBA加工手工焊接的两种方式,为您揭秘行业内的技术细节。 PCBA加工过程中常用焊接方式 第一方式是传统手工焊接。这种方式主要依靠技术工人的手动操作进行焊
    的头像 发表于 06-14 09:18 495次阅读