首页 > 编程知识 正文

最优二叉树算法详解,二叉树常见算法

时间:2023-05-04 16:40:25 阅读:158719 作者:4972

简介:鲤鱼炮、网名海洋、资深计划、IT资深讲师、CSDN社区专家、CSDN特邀编辑、畅销书作者、出版书籍《手把手教你架构3D游戏引擎》、《Unity3D实战核心技术详解》等。

笔者将于1月4日在CSDN学院开设公开课《算法与游戏实战》。 在此,向读者阐明一部分课程内容。 首先对二叉树的算法进行说明,二叉树在IT领域有着非常广泛的应用,不仅是游戏开发,在目前炙手可热的人工智能中也得到了广泛的应用。 作为使用者,首先要弄清楚二叉树的特性。 它是n(n )个节点的有限集合,孩子的节点有两个以上。其扫描有先后顺序、中序、后序; 其记忆结构可分为线性记忆和链式记忆等; 另一种最好的二叉树也称为哈夫曼树,以下开始共享案例。

在游戏开发中,美术会制作很多图像。 这些图像一方面用于UI界面,另一方面是用于模型的材质。 大多数网络游戏使用的图像数量非常多,要查看图像,必须先将其加载到内存中。 内存大小有限制,不仅要加载图像,还必须加载数据和模型。 跟随玩家的摄影机在场景中移动时,场景会随着摄影机的移动而一个接一个地显示。 因此,需要不断地将不同的场景添加到内存中。 这无疑会增加内存吞吐量的负担。 通过将图像分类为较大的图像,可以提高效率,而无需每次将其添加到内存中时都频繁加载。

现在,大家都在用Unity开发,或者用幻象开发。 它既可以实现自己制作写真集的功能,也可以使用TexturePack工具打包成写真集。 虽然我们看不到它们的代码实现,但是我们自己可以用二叉树把它打包成图集,向读者展示用二叉树实现的UI作为图集的效果图:

向读者展示核心代码。 首先制作二叉树。 目的是在二叉树的节点中插入图像。 包括确定二叉树节点是否为空。 在代码中递归执行,代码如下所示。

publicatlasnodeinsert (texture 2d image,int index ) if ) image==null )//Obviously an error! 返回空值; if (神盾局!=null ) {//If this node is not a leaf,tryinsertingintofirstchild.atlasnodenewnode=child [0].insert (image,index ); if (新节点!=null (返回新节点; //No more room in first child,insert into second child! returnchild[1].insert(image,index ); } else {//ifthereisalreadyalightmapinthisnode,earlyoutif(hasimage ) return null; //ifthisnodeistoosmallfortheimage,return if (! imagefits(image,rc ) )返回空值; //If the image is perfect,accept! perfect fit (image,rc ) { hasImage=true; imageRef=image; name=imageRef.name; sort index=索引; 返回this; } //If we made it this far,thisnodemustbesplit.child=newatlasnode [2]; child[0]=new AtlasNode (; child[1]=new AtlasNode (; //decidewhichwaytosplitimagefloatdeltaw=RC.width-image.width; float deltah=RC.height-image.height; if(deltawdeltah ) { child[0].rc=new Rect (

rc.xMin, rc.yMin, image.width, rc.height); child[1].rc = new Rect(rc.xMin + image.width + TEXTURE_PADDING, rc.yMin, rc.width - (image.width + TEXTURE_PADDING), rc.height); } else { child[0].rc = new Rect(rc.xMin, rc.yMin, rc.width, image.height); child[1].rc = new Rect(rc.xMin, rc.yMin + image.height + TEXTURE_PADDING, rc.width, rc.height - (image.height + TEXTURE_PADDING)); } // Lets try inserting into first child, eh? return child[0].Insert(image, index); } }

最后一步就是创建图集了,核心代码如下所示:

public static Atlas[] CreateAtlas(string name, Texture2D[] textures, Atlas startWith = null) { List<Texture2D> toProcess = new List<Texture2D>(); toProcess.AddRange(textures); int index = toProcess.Count - 1; toProcess.Reverse(); // Because we index backwards List<Atlas> result = new List<Atlas>(); int insertIndex = 0; if (startWith != null) { insertIndex = startWith.root.sortIndex; } while(index >= 0) { Atlas _atlas = startWith; if (_atlas == null) { _atlas = new Atlas(); _atlas.texture = new Texture2D(AtlasSize, AtlasSize, TextureFormat.RGBA32, false); _atlas.root = new AtlasNode(); _atlas.root.rc = new Rect(0, 0, AtlasSize, AtlasSize); } startWith = null; while (index >= 0 && (_atlas.root.Contains(toProcess[index].name) || _atlas.root.Insert(toProcess[index], insertIndex++) != null)) { index -= 1; } result.Add(_atlas); _atlas.root.sortIndex = insertIndex; insertIndex = 0; _atlas = null; } foreach(Atlas atlas in result) { atlas.root.Build(atlas.texture); List<AtlasNode> nodes = new List<AtlasNode>(); atlas.root.GetBounds(ref nodes); nodes.Sort(delegate (AtlasNode x, AtlasNode y) { if (x.sortIndex == y.sortIndex) return 0; if (y.sortIndex > x.sortIndex) return -1; return 1; }); List<Rect> rects = new List<Rect>(); foreach(AtlasNode node in nodes) { Rect normalized = new Rect(node.rc.xMin / atlas.root.rc.width, node.rc.yMin / atlas.root.rc.height, node.rc.width / atlas.root.rc.width, node.rc.height / atlas.root.rc.height); // bunp everything over by half a pixel to avoid floating errors normalized.x += 0.5f / atlas.root.rc.width; normalized.width -= 1.0f / atlas.root.rc.width; normalized.y += 0.5f / atlas.root.rc.height; normalized.height -= 1.0f / atlas.root.rc.height; rects.Add(normalized); } atlas.uvRects = new AtlasDescriptor[rects.Count]; for (int i = 0; i < rects.Count; i++) { atlas.uvRects[i] = new AtlasDescriptor(); atlas.uvRects[i].width = (int)nodes[i].rc.width; atlas.uvRects[i].height = (int)nodes[i].rc.height; atlas.uvRects[i].name = nodes[i].name; atlas.uvRects[i].uvRect = rects[i]; } atlas.root.Clear(); #if DEBUG_ATLASES atlas.texture.Apply(false, false); SaveAtlas(atlas, name); #else if (atlas != result[result.Count - 1]) atlas.texture.Apply(false, true); else atlas.texture.Apply(false, false); #endif } return result.ToArray(); }

当然这种技术也可以使用3D模型材质的处理,只是在制作的过程中要保存其图片的UV值也就是图片在图集中的坐标,这样程序在加载时可以“对号入座”,避免模型的材质出现“张冠李戴”。
二叉树另一种形式是-哈夫曼树,哈夫曼树定义:在权为wl,w2,…,wn的n个叶子所构成的所有二叉树中,带权路径长度最小(即代价最小)的二叉树称为最优二叉树或哈夫曼树。我们利用哈夫曼树的特性可以帮助我们优化程序代码,特别适用于游戏中怪物面对玩家的AI表现,在网上比较流行的案例,游戏中也会使用到:设主角的生命值d,在省略其他条件后,有这样的条件判定:当怪物碰到主角后,怪物的反应遵从下规则:

根据条件,我们可以用如下普通算法来判定怪物的反应:

if(d<100) state=嘲笑,单挑;  else if(d<200) state=单挑;    else if(d<300) state=嗜血魔法;      else if(d<400) state=呼唤同伴;        else state=逃跑;

上面的算法适用大多数情况,但其时间性能不高,我们可以通过判定树来提高其时间性能。首先,分析主角生命值通常的特点,即预测出每种条件占总条件的百分比,将这些比值作为权值来构造最优二叉树(哈夫曼树),作为判定树来设定算法。假设这些百分比为:

构造好的哈夫曼树为:

对应算法如下:

 if(d>=200)&&(d<300) state=嗜血魔法;  else if(d>=300)&&(d<500) state=呼唤同伴;    else if(d>=100)&&(d<200) state=单挑;      else if(d<100) state=嘲笑,单挑;        else state=逃跑;

通过计算,两种算法的效率大约是2:3,很明显,改进的算法在时间性能上提高不少。这种改进也可以归结到代码重构或者说是优化程序,它虽然没有使用二叉树的存储节点,但是我们可以使用二叉树的思想解决问题。
在人工智能中,二叉树使用也是非常广泛的,不同的分支指令对应的是不同的动作等等,在遇到AI方面的问题时可以优先考虑二叉树算法。

总结

在使用算法解决问题时,并不是照搬硬套,其思想是最重要的,代码只是编程工具,语言不是重点,思路才是最重要的,万变不离其宗。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。