这篇文章的起因是和朋友聊天的时候聊到关于社团内的技术分享到底在分享什么的问题,在这个搜索引擎和AI超绝发达的时代,其实在大多数情况下对具体算法或者说技术栈的实现方法的分享已经缺乏必要了,对有需求的人他们自己搜就能学会,轮不到专门的技术分享,而不知道的人不会想到用相应的工具/算法来解决他们遇到的问题。
一
想象一个需求,简单的说,你要检查一堆括号组成的字符串是否合法,换句话说,每个括号是否关闭。
(){}[] # 合法([{}])({}[]) # 合法()[]{ # 不行([)]{} # 不行提取一下合法字符串的特征,其实有以下几个规律:
- 对每种括号,一个左括号总是对应一个右括号
- 一个关闭的括号里的所有括号应该都是关闭的
如此,我们可以简单的写一个程序,来先后判断这两个条件。每次踹掉相邻的关闭括号,直到没有东西可以删。
static Boolean isSymbolValidNaive(String input) { int lastLength; do { lastLength = input.length(); // 不断尝试替换掉最简单的相邻对 input = input.replace("()", "") .replace("[]", "") .replace("{}", "");
// 如果替换前后的长度一样,说明再也找不到可以消除的括号了 } while (input.length() < lastLength);
// 看看最后是不是全消掉了 return input.length() == 0;}但是这样有些麻烦(而且时间复杂度挺高),但是如果我们换一个方法思考,这个问题也可以这么理解:
- 每个左括号都会新建一个待关闭的完整括号
- 每个右括号(理论上)都会关闭最后新建的待关闭括号
那么,如果满足这么几个条件,我们就能断言括号串是合法的:
- 字符串结束的时候待关闭缓冲区是空的
- 每个右括号都成功关闭了一个括号
- 每个右括号都关闭了相同种类的左括号
如此,总结一些规律:
- 每个左括号都推入一个未关闭括号到缓冲区
- 最后进入缓冲区的左括号需要最先被关闭
- 每个右括号消除一个缓冲区的未关闭括号
不难看出这个问题的后进后出特性,我们可以据此适用一个栈的结构
static Boolean isSymbolValid(String input) { Stack<Character> stack = new Stack<>();
for (char c : input.toCharArray()) { if (c == '(' || c == '[' || c == '{') { stack.push(c); } else { if (stack.isEmpty()) return false;
char top = stack.pop(); if (!isMatch(top, c)) return false; } } return stack.isEmpty();}
private static boolean isMatch(char left, char right) { if (left == '(') return right == ')'; if (left == '[') return right == ']'; if (left == '{') return right == '}'; return false;}相比 naive 方法,这是一个典型的:
通过正确建模,把问题从“字符串操作”降维到“数据结构操作”的行为
二 延伸
既然是给游戏开发社团写的,不妨来聊聊体积云。
想象一下在傍晚坐飞机经过城市上空,从舷窗往下看,能看到云,看到云层下的城市,能看见霓虹在云层上的眩光。
如果需要实现这种效果,要怎么做呢?
首先一定要有云,对这种随机多变的东西,我们可以用各种噪声算法来生成噪声地图,基于采样值的高低来模拟云层的薄厚。
很显然噪声图是2d的,但是云是有厚度的,一般来说,能见度(或者透明度)越低,云就越厚,基于这种思考我们就可以进行一个2d到3d的转化,把采样值映射成厚度。采样值越高,密度越大。为了增加层次感,我们可以叠合多层不同频率的噪声(FBM),模拟云朵那种边缘细碎的质感。
直觉翻译成数学语言就是:
云 = 一个连续的三维密度函数 ρ(x, y, z)
云完成之后就要考虑光照和遮挡的关系。我看向云层时,视野里的颜色取决于这一条视线上所有云分子的贡献总和,加上云后面的部分提供的背景信息。翻译成算法实现,其实就是从你的观察点发射一条射线,来计算沿射线上云密度对光线的衰减关系。
眩光则是城市灯光向上照射 -> 撞击云分子 -> 发生散射 -> 进入眼睛这样的过程,那么对这个部分,可以是在射线的每一个步进点上,再往“光源(城市)”的方向打一条射线(Shadow Ray)。如果这条射线穿过的云层很薄,说明城市灯光能照到这个点,那么这个步进点就会被灯光染色。
一个简单的伪代码
从相机发射一条射线while 没走出云层: 采样当前点密度 累积透射率 累积散射光三
回到最初的问题:技术分享到底在分享什么?
我觉得是一种语义对齐和抽象的能力。先不去思考代码,用人话描述规律,之后再去寻找数学/逻辑的原型,然后去考虑计算的代价和工程的权衡之类的问题。