当前位置: 首页 > 编程日记 > 正文

在A*寻路中使用二叉堆

在A*寻路中使用二叉堆

作者:Patrick Lester(2003年4月11日更新)
译者:Panic 2005年3月28日

译者序:
    这一篇文章,是“A* Pathfinding for Beginners.”,也就是我翻译的另一篇文章A*寻路初探 GameDev.net的补充,在这篇文章里,作者再一次展现了他阐述复杂话题的非凡能力,用通俗易懂的语句清晰的解释了容易让人迷惑的问题。还是那句话,如果你看了这篇文章仍然无法领会作者的意图,那只能怪我的翻译太蹩脚了。请参考原文做进一步的理解。
   
    这里讲解的二叉堆,其实是以堆的形式存在的二叉树,这个特殊的结构把A*算法对开启列表的排序需求演绎的出神入化,毫无疑问是A*的最佳拍档。
   
    最后,希望这篇文章对你有所帮助。
   
原文链接:http://www.policyalmanac.org/games/binaryHeaps.htm   [已经无法打开]

以下是翻译的正文:


这篇文章是我的主题文章“A* Pathfinding for Beginners.”的补充。在读这篇文章之前,你应该先读那一篇文章,或者对A*做透彻的理解。

A*算法中最缓慢的部分就是在开启列表中寻找F值最低的节点或者方格。取决于地图的大小,你可能有十几,成百甚至上千的节点需要在某个时候使用A*搜索。无需多讲,反复搜索这么大的列表会严重拖慢整个过程。然而,这些时间在极大程度上受你存储列表的方式影响。

有序和无序的开启列表:简单的方法

最简单的方法就是顺序存储每个节点,然后每次需要提取最低耗费元素的时候都遍历整个列表。这提供可快速的插入速度,但是移除速度可能是最慢的,因为你需要检查每个元素才能够确定哪个才是F值最低的。

通常你可以保持你列表处于有序状态来提升效率。这花费了稍微多一点的预处理时间,因为你每次插入新元素都必须把他们放在恰当的位置。不过移除元素倒是很快。你只要移除第一个元素就可以了,它一定是F值最低的。

有很多方法可以保持你的数据有序(选择排序,冒泡排序,快速排序,等等)并且你可以用你最熟悉的搜索引擎找到这方面的文章。不过我们至少可以先提几种想法。最简单的方法可能是,当你需要添加新元素的时候,从列表开始的地方,依次比较每个元素的F值和要插入的F值的大小。一旦找到一个相等或者更高的F值,你就可以把新元素插入到列表中那个元素的前面。取决于你使用的计算机于亚,使用class或者struct实现的链表可能是不错的方法。

这种方法可以通过保持列表中所有元素的平均值来得到改进,使用这个平均值来决定是从头(如上所说)还是从尾开始处理。总的说来,比平均F值低的新元素将被从头开始处理,而比平均F值高的则从末尾开始。这种方法可以节省一半的时间。

复杂一些,但是更快的方法是把这一想法提高到新的层次使用快速排序,它基本上是从比较新元素和列表中间元素的F值开始。如果新元素的F值低,你接着把它和1/4处元素进行比较,如果还是更低你就比较它和1/8处的元素,如此这般,不断的折半你的列表并且比较,直到找到合适的位置。这个描述很简单,你可能会想到网上寻找快速排序的更多资料。这比至此描述的任何方法都快。

二叉堆

二叉堆和刚才说的快速排序很像,经常被那些苛求A*速度的人使用。根据我的经验,二叉堆平均提高寻路速度2-3倍,对于包含大量节点的地图(也就是说100×100节点或者更多)效果更明显。友情提醒,然而二叉堆很难处理,除非你使用含有大量节点的地图,速度至关重要,否则不值得为它头痛。

文章其他的部分深入说明了二叉堆和它在A*算法中的用途。如果你对我的文章存有疑惑,在文章末尾进一步阅读的小节中提供了更多的观点。

仍然有兴趣?好,我们继续。。。

在有序列表中,每个元素都按照由低到高或由高到低的顺序保存在恰当的位置。这很有用,但是还不够。事实上,我们并不关心数字127是否比128在更低的位置上。我们只是想让F值最低的元素能放在列表顶端以便容易访问。列表的其他部分即使是混乱的也不必在意。列表的其他部分只有在我们需要另一个F值最低的元素的时候,才有必要保持有序。

基本上,我们真正需要的是一个“堆”,确切的说,是个二叉堆。二叉堆是一组元素,其中最大或者最小(取决于需要)的元素在堆顶端。既然我们要寻找F值最小的元素,我们就把它放在堆顶端。这个元素有两个子节点,每个的F值等于,或者略高于这个元素。每个子节点又有两个子节点,他们又有和他们相等或略高的子节点。。。依次类推。这里是一个堆可能的样子:

注意,F值最低的元素(10)在最顶端,第二低的元素(20)是它的一个子节点。可是,其后就没有任何疑问了。在这个特定的二叉堆里,第三低的元素是24,它离堆顶有两步的距离,它比30小,但是30却在左侧离堆顶一步之遥的地方。简单的堆放,其他的元素在堆的哪个位置并不重要,每个单独的元素只需要和它的父节点相等或者更高,而和它的两个子节点相比,更低或者相等,这就可以了。这些条件在这里都完全符合,所以这是个有效的二叉堆。

很好,你可能会想,这的确有趣,但是如何把它付诸实施呢?嗯,关于二叉堆的一个有趣的事实是,你可以简单的把它存储在一个一维数组中。

在这个数组中,堆顶端的元素应该是数组的第一个元素(是下标1而不是0)。两个子节点会在2和3的位置。这两个节点的4个子节点应该在4-7的位置。

总的来说,任何元素的两个子节点可以通过把当前元素的位置乘以2(得到第一个子节点)和乘2加1(得到第二个子节点)来得到。就这样,例如堆中第三个元素(数值是20)的两个子节点,可以在位置2*3 = 6和2*3 +1 = 7这两个位置找到。那两个位置上的数字非别是30和24,当你查看堆的时候就能理解。

你其实不必要知道这些,除了表明堆中没有断层之外知道这些没有任何价值。7个元素,就完整的填满了一个三层堆的每一层。然而这并不是必要的。为了让我们的堆有效,我们只需要填充最底层之上的每一行。最底层自身可以是任意数值的元素,同时,新的元素按照从左到右的顺序添加。这篇文章描述的方法就是这样做的,所以你不必多虑。

往堆中添加新元素

当我们实际在寻路算法中使用二叉堆的时候,还需要考虑更多,但是现在我们只是学习一下如何使用二叉堆。我跳过这部分以便更容易理解基本的东西。我会在文章后面的部分给出处理这一切的完整公式,但了解这些细节仍然十分重要。

大致的,为了往堆里添加元素,我们把它放在数组的末尾。然后和它在 当前位置/2 处的父节点比较,分数部分被圆整。如果新元素的F值更低,我们就交换这两个元素。然后我们比较这个元素和它的新父节点,在 (当前位置)/2 ,小数部分圆整,的地方。如果它的F值更低,我们再次交换。我们重复这个过程直到这个元素不再比它的父节点低,或者这个元素已经到达顶端,处于数组的位置1。

我们来看如何把一个F值为17的元素添加到已经存在的堆中。我们的堆里现在有7个元素,新元素将被添加到第8个位置。这就是堆看起来的样子,新元素被加了下划线。

10 30 20 34 38 30 24 17

接下来我们比较它和它的父节点,在 8/2 也就是 4的位置上。位置4当前元素的F值是34。既然17比34低,我们交换两元素的位置。现在我们的堆看起来是这样的:

10 30 20 17 38 30 24 34

然后我们把它和新的父节点比较。因为我们在位置4,我们就把它和 4/2 = 2 这个位置上的元素比较。那个元素的F值是30。因为17比30低,我们再次交换,现在堆看起来是这样的:

10 17 20 30 38 30 24 34

接着我们比较它和新的父节点。现在我们在第二个位置,我们把它和 2/2 = 1,也就是堆顶端的比较。这次,17不比10更低,我们停止,堆保持成现在的样子。

从堆中删除元素

从堆中删除元素是个类似的过程,但是差不多是反过来的。首先,我们删除位置1的元素,现在它空了。然后,我们取堆的最后一个元素,移动到位置1。在堆中,这是结束的条件。以前的末元素被加了下划线。

34 17 20 30 38 30 24

然后我们比较它和两个子节点,它们分别在位置(当前位置*2)和(当前位置* 2 + 1)。如果它比两个子节点的F值都低,就保持原位。反之,就把它和较低的子节点交换。那么,在这里,该元素的两个子节点的位置在 1 * 2 = 2和 1*2 + 1 = 3。显然,34不比任何一个子节点低,所以我们把它和较低的子节点,也就是17,交换。结果看起来是这样:

17 34 20 30 38 30 24

接着我们把它和新的子节点比较,它们在 2*2 = 4,和2*2 + 1 = 5的位置上。它不比任何一个子节点低,所以我们把它和较低的一个子节点交换(位置4上的30)。现在是这样:

17 30 20 34 38 30 24

最后一次,我们比较它和新的子节点。照例,子节点在位置 4*2 = 8和4*2+1 = 9的位置上。但是那些位置上并没有元素,因为列表没那么长。我们已经到达了堆的底端,所以我们停下来。

二叉堆为什么这么快?

现在你知道了堆基本的插入和删除方法,你应该明白为什么它比其他方法,比如说插入排序更快。假设你有个有1000个节点的开启列表,在一格有很多节点的相当大的地图上,这不是不可能(记住,即使是100×100的地图,上面也有10,000个节点)。如果你使用插入排序,从起点开始,到找到新元素恰当的位置,在把新元素插入之前,平均需要做500次比较。

使用二叉堆,你从底端开始,可能只要1-3次比较就能把新元素插入到正确的位置。你还需要9次比较用来从开启列表中移除一个元素,同时保持堆仍然有序。在A*中,你通常每次只需要移除一个元素(F值最低的元素),在任意位置添加0到5个新节点(就像主文章里描述的2D寻路)。这总共花费的时间大约是同样数量节点进行插入排序的1%。差别随你地图的增大(也就是节点更多)呈几何增长。地图越小,就越没优势,这也是为什么你的地图和节点越少,二叉堆的价值就越低的原因。

顺便,使用二叉堆并不意味着你的寻路算法会快100倍。在下面还讲了一些棘手的问题。额外的,A*不仅仅是为开启列表排序。然而,根据我的经验,用二叉堆在大部分场合可以提高2-3倍的速度,更长的路径,速度提高的更多。

创建开启列表数组

现在我们了解了二叉堆,那么如何使用它呢?首先要做的是构造我们的一维数组。为此,我们先要确定它的大小。一般来说,列表大小不会超过地图上的节点总数(在最坏的情况下,我们搜索整个地图寻找路径)。在一个方形二维地图中,就如我的主文章中描述的,我们的节点不超过 地图宽 × 地图高。那么我们的一维数组就是那个大小。在这个例子里,我们叫这个数组 openList()。堆最顶端的元素被存储在openList(1),第二个元素在openList(2),依此类推。

使用指针

现在我们有了正确大小的数组,几乎可以开始用来寻路了。不过,在进一步的添加或者删除操作之前,我们再看看原始的堆。

现在,它只是个F值的列表,而且已经被正确安排。但是我们忽略了一个重要的元素。是的,我们有一系列的F值按顺序保存在堆里,但是我们没有他们代表哪一格的任何线索。基本上,我们只是知道10是堆中最低的F值。但那指的是那个格子?

为了解决这个问题,我们必须改变数组中元素的值。我们不储存排序好的F值,取而代之的是保存关联到地图网格的唯一标志。我的方法是为每个新加入堆的元素创建一个唯一ID叫做squaresChecked。每次往开启列表中添加新元素,我们给squaresChecked增加1,并把它作为列表中新元素的唯一ID。第一个添加进列表的是#1,第二个是#2,依此类推。

最后,我们把具体的F值存储在单独的一维数组中,我把它叫做 Dcost()。和开启列表相同,我们把它的大小定为(地图宽 x 地图高)。我们同时存储节点的x和y坐标在类似的一维数组中,叫做 openX() 和 openY()。看起来就像下面的图:

尽管这看起来有点复杂,但它和前面讲的堆是相同的。只是储存的信息更多了。

#5元素,有最低的F值10,仍然在堆顶,在一维数组的第一列。不过现在我们在堆里存储它的唯一ID 5,而不是它的F值。换句话说,openList(1) = 5。这个唯一数值用来查找元素的F值,以及地图x和y坐标。这个元素的F值是Fcost(5) = 10,x坐标是openX(5) = 12,y坐标是openY(5) = 22。

顶端的元素有两个子节点,数值是2和6,他们的F值是30和20,分别存储在opneList()中2和3的位置,等等。基本上,我们的堆和以前相同,只是多了一些关于元素在地图上的位置,F值是多少,等等的信息。

在堆中添加新元素(第二部分)

好,我们实际的把这种技术用在A*寻路的开启列表排序中。我们使用的技术和先前描述的大体相同。

我们添加到开启列表中的第一个元素,一般是起始节点,被分配了一个数值是1的唯一ID,然后放入开启列表的#1位置。也就是 openList(1) = 1.我们还跟踪开启列表中元素的数量,现在也是1。我们把它保存在名为numberOfOpenListItems的变量里。

当我们往开启列表中添加新元素的时候,首先我们计算G,H和F值,就如在主文章中所述。然后按照前面讲的方法把他们添加进开启列表。

首先我们给新元素分配一格唯一 ID号,也就是squaresChecked变量的用途。每次我们添加一个新节点,就给这个变量加1,然后把它的数值分配给新节点。然后给numberOfOpenListItems加1。然后把它添加到开启列表的底部,所有这些可以翻译成:

squaresChecked = squaresChecked +1
  numberOfOpenListItems = numberOfOpenListItems+1
  openList(numberOfOpenListItems) = squaresChecked

然后我们依次把它和父节点比较直到它到达正确的位置。这是这些操作的代码:

m = numberOfOpenListItems
  While m <> 1 ;While item hasn't bubbled to the top (m=1)

;Check if child is <= parent. If so, swap them.
     If Fcost(openList(m)) <= Fcost(openList(m/2)) Then
        temp = openList(m/2)
        openList(m/2) = openList(m)
        openList(m) = temp
        m = m/2
     Else
        Exit ;exit the while/wend loop
     End If
  Wend

从堆中删除元素(第二部分)

无疑,我们不能只建立堆,当不需要的时候,我们也要从堆中删除元素。特别的,在A*寻路中,我们在检查和切换到关闭列表之后,从堆顶需要删除F值最低的元素。

如前所述,你从把末元素移动到堆顶开始,然后把堆中的元素总数减1。伪代码是这样:

openList(1) = openList(numberOfOpenListItems)
  numberOfOpenListItems = numberOfOpenListItems - 1

接着我们需要依次比较它和两个子节点的数值。如果它的F值更高,我们就把它和更低F值的子节点交换。然后我们把它和新的子节点比较(看它是否更低)。如果它的F值比两个子节点更高,我们把它和较低的一个交换。我们重复这个过程直到找到它的正确位置,这可能会一直持续到堆底,但并不是完全必要。伪代码看起来是这样:

v = 1

;Repeat the following until the item sinks to its proper spot in the binary heap.
Repeat
  u = v
  If 2*u+1 <= numberOfOpenListItems ;if both children exist
    ;Select the lowest of the two children.
    If Fcost(openList(u)) >= Fcost(openList(2*u))then v = 2*u ;SEE NOTE BELOW
    If Fcost(openList(v)) >= Fcost(openList(2*u+1))then v = 2*u+1 ;SEE NOTE BELOW

Else If 2*u <= numberOfOpenListItems ;if only child #1 exists
    ;Check if the F cost is greater than the child
    If Fcost(openList(u)) >= Fcost(openList(2*u))then v = 2*u
  End If

If u <> v then ; If parent's F > one or both of its children, swap them
    temp = openList(u)
    openList(u) = openList(v)
    openList(v) = temp
  Else
    Exit ;if item <= both children, exit repeat/forever loop
  End if
Forever ;Repeat forever

请注意两行代码中粗体(红色)的u和v的数值。在第二行,你应该使用 v而不是u,这不是很显而易见。这确保了你把它和较低的子节点交换。如果做错会造成不完整的堆,完全打乱你的寻路。

对开启列表的元素重排序

就如在主文章中描述的,有时候你会发现现有的开启列表中的元素会改变。这种情况发生的时候,我们不必要取出这个元素重新来过。只要从当前位置开始,用它新的(更低的)F值和它的父节点比较。如果它的F值低到足以替换它的父节点,你就把它替换掉(不然你就会得到一个错误的堆,一切都完了)。一般,你使用和“在堆中添加新元素”的小节中相同的代码,并做额外处理如下:

不幸的是,因为你的数据是在一个庞大,无序的堆里,你需要遍历整个堆查找先有开启列表中的元素。你主要要查找由openX(openList()) 和openY(openList())获取的确切坐标的格子,找到之后,你就可以从那一点开始,像往常那样做必要的比较和交换。

最后的注解

好了,我希望你仍然能读懂,没有被搞昏头。如果你不着急,并且希望在自己的寻路算法中使用二叉堆,那么这就是我的建议。

首先,把二叉堆放一放。把注意力放在你的A*寻路算法上,使用简单点的排序算法,保证算法正常工作没有bug。一开始你并不需要它很快速,你只需要它能工作。

其次,在你把二叉堆添加到代码中之前,试着把二叉堆写成独立的功能,用适当的方法添加和删除元素。确保你写的程序中,可以看到整个过程中每一步的操作,也许可以把结果打印在屏幕上,如果你愿意。你还得包含一些中途退出的代码,以便在必要的时候结束整个流程。如果二叉堆写的不对,你很容易就会陷入到无限循环中。

一旦你确信两个程序都运行无误,备份他们,然后开始把他们结合起来。除非你比我还聪明,否则一开始你难免遇到麻烦。输出都是些错误信息 并且/或者 看到寻路者因为bug走出各种怪异的方向。不过最终你会搞定一切。

进一步阅读

和以往一样,网上有很多其他的关于这个话题的好文章。这里有些入门的。第一篇用图示。第二篇说明了怎么用一个简单的数组(如果我的说明很麻烦的话)实现二叉堆。第三篇探讨了寻路算法中堆的一般用途。

* http://www.onthenet.com.au/~grahamis/int2008/week11/lect11.html
    * http://www.purists.org/pqueue/
* http://theory.stanford.edu/~amitp/GameProgramming/ImplementationNotes.html#S5

最后,你可能想看看我的寻路代码,这里可以找到。它和文中的伪代码相互照应,注释详尽,有C++和Blitz两个版本,Blitz是一个比其他大多数都容易理解的语言。不使用C++的程序员会很容易理解Basic版本的代码。

好,就这么多。

现在,照例,祝你好运。

相关文章:

“Hey Siri” 背后的黑科技大揭秘!

作者 | Vishant Batta译者 | 苏本如&#xff0c;责编 | 伍杏玲出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;以下是译文&#xff1a; 如今苹果手机可随时检测并回答“Hey Siri”命令&#xff0c;有人可能会想&#xff0c;它是不是在随时记录我们的日常生活对话呢…

[ASP.NET4之旅]Circular file references are not allowed

将ASP.NET 2.0的项目升级到ASP.NET 4后&#xff0c;用VS2010编译站点&#xff0c;某些控件出现编译错误“Circular file references are not allowed”&#xff0c;比如&#xff1a; <% Control Language"C#"ClassName"NewsRight"%>解决方法&#xf…

IOS-XMPP

一、即时通讯技术简介 即时通讯技术&#xff08;IM -- Instant Messaging&#xff09;支持用户在线实时交谈。如果要发送一条信息&#xff0c;用户需要打开一个小窗口&#xff0c;以便让用户及其朋友在其中输入信息并让交谈双方都看到交谈的内容有许多的IM系统&#xff0c;如AO…

libcurl使用

官网&#xff1a;http://curl.haxx.se/libcurl/c/libcurl-tutorial.html #curl-config --libs 得到 -lcurl #cc libcurl_test.c -o libcurl_test -lcurl 所有的例子&#xff1a;http://curl.haxx.se/libcurl/c/example.html 例子&#xff1a; /***********************…

RH5.4下samba共享配置实例(3)

一、基于用户名的访问控制实例&#xff1a; 王乾大哥写的比较详细了&#xff0c;我是跟着他的教程学习的&#xff0c;按照他的教程走一边&#xff1b; 要求如下&#xff1a; 1、创建一个公共的交换文件夹&#xff0c;所有人都可以写入删除&#xff0c;但不能删除修改其他人的文…

《评人工智能如何走向新阶段》后记(再续21)

346.中国抗疫十大黑科技&#xff08;以人工智能为主力的黑科技&#xff09; 摘自数邦客&#xff08;2020.3.30发布&#xff09; 负压救护车 人工智能机器人&#xff1a;如送餐机器人、消毒机器人、服务型机器人&#xff0c;及机器人呼叫等呼吸道病毒核配检测试剂盒&#xf…

ssh-keygen

nagios npc安装后状态为off的解决方法

1、检查ndo2db的进程是不是二个 nagios 16825 0.0 0.1 6784 396 ? Ss 19:05 0:00 /usr/local/nagios/bin/ndo2db -c /usr/l nagios 17032 0.0 0.3 6784 1268 ? S 19:09 0:00 /usr/local/nagios/bin/ndo2db -c 2、检查nagios.log日志看…

C语言extern关键字定义外部变量--Redis源码extern使用

在Redis2.8中有networking.c&#xff0c;这个文件没有networking.h networking.c首先引入redis.h这个头文件 #include "redis.h" 在redis.c一开始就声明了全局变量 /* Global vars */ struct redisServer server; networking.c的createClient函数 redisClient *cr…

深度学习面试必备的25个问题

作者 | Tomer Amit译者 | 弯月&#xff0c;编辑 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;在本文中&#xff0c;我将分享有关深度学习的25个问题&#xff0c;希望能够帮助你为面试做好准备。1.为什么必须在神经网络中引入非线性&#xff1f;答&#xf…

一分钟了解阿里云产品:先知计划

一、 概述 阿里云发布了各种各样的产品&#xff0c;今天让我们一起来了解下阿里云先知计划吧。 什么是先知计划呢&#xff1f; 先知计划是一个帮助企业建立私有应急响应中心的平台&#xff08;帮助企业收集漏洞信息&#xff09;。企业加入先知计划后&#xff0c;可…

C语言的HashTable简单实现

原文地址&#xff1a;http://blog.csdn.net/zmxiangde_88/article/details/8025541 HashTable是在实际应用中很重要的一个结构&#xff0c;下面讨论一个简单的实现&#xff0c;虽然简单&#xff0c;但是该有的部分都还是有的。 一&#xff0c;访问接口 创建一个hashtable. h…

GitHub标星2000+,如何用30天啃完TensorFlow2.0?

作者 | 梁云1991来源 | Python与算法之美&#xff08;ID:Python_Ai_Road&#xff09;天下苦tensorflow久矣&#xff01;尽管tensorflow2.0宣称已经为改善用户体验做出了巨大的改进&#xff0c;really easy to use&#xff0c;但大家学得并不轻松。tensorflow2.0官方文档和tenso…

【Struts2学习笔记(1)】Struts2中Action名称的搜索顺序和多个Action共享一个视图--全局result配置...

一、Action名称的搜索顺序 1&#xff0e;获得请求路径的URI&#xff0c;比如url是&#xff1a;http://server/struts2/path1/path2/path3/test.action 2&#xff0e;首先寻找namespace为/path1/path2/path3的package&#xff0c;假设不存在这个package则运行步骤3&#xff1b;假…

大话卷积神经网络CNN,小白也能看懂的深度学习算法教程,全程干货建议收藏!...

来源 | 程序员管小亮本文创作的主要目的&#xff0c;是对时下最火最流行的深度学习算法的基础知识做一个简介&#xff0c;作者看过许多教程&#xff0c;感觉对小白不是特别友好&#xff0c;尤其是在踩过好多坑之后&#xff0c;于是便有了写这篇文章的想法。由于文章较长&#x…

频繁分配释放内存导致的性能问题的分析--brk和mmap的实现

&#xfeff;现象1 压力测试过程中&#xff0c;发现被测对象性能不够理想&#xff0c;具体表现为&#xff1a; 进程的系统态CPU消耗20&#xff0c;用户态CPU消耗10&#xff0c;系统idle大约70 2 用ps -o majflt,minflt -C program命令查看&#xff0c;发现majflt每秒增量为0&…

Linux 服务器日志文件查找技巧精粹

用来在日志文件里搜索特定活动事件的工具不下几十种&#xff0c;本文将介绍搜索日志文件时应该采取的策略。然后&#xff0c;通过几个具体示例介绍一些使用grep命令手动搜索日志文件的办法。接下来&#xff0c;我们将看到 logwatch工具和logsurfer工具的用法。最后&#xff0c;…

程序猿面试什么最重要?

程序猿面试一直是社区乐于讨论的热门话题。我自己从06年实习以来。先后经历了4家软件公司。所有是外企。当中有世界500强的通信企业&#xff0c;有从事期权期货交易的欧洲中等规模的金融公司&#xff0c;也有为大型汽车制造商开发Android智能汽车的新兴公司。跨入IT行业以来。我…

open的O_DIRECT选项

http://blog.chinaunix.net/uid-223060-id-2127385.html http://blog.csdn.net/hhtang/article/details/6605951 查看磁盘分区&#xff1a; #df -h #tune2fs -l /dev/mapper/VolGroup-lv_root 或者 #dumpe2fs /dev/mapper/VolGroup-lv_root|grep -i "block size"…

2020 年,AI 芯片内存哪家强?

目前多家公司都在开发网络边缘系统的AI芯片&#xff0c;本文作者详细分析AI边缘芯片遇到的问题和挑战&#xff0c;并给出一些新的内存技术解决方案。作者 | Mark LaPedus译者 | 弯月&#xff0c;责编 | 伍杏玲封图 | CSDN下载自视觉中国出品 | CSDN&#xff08;ID:CSDNnews&…

Excel数字、文本混合列导入SQL Server出现的问题&解决办法

版权声明&#xff1a;转载时请以超链接形式标明文章原始出处和作者信息及本声明http://annie-out.blogbus.com/logs/60276495.htmlExcel文件&#xff1a;序号 姓名 内部电话 住址 1 小李 1234 …… 2 小王5678……3小张2345(国内长途&#xff09;…………………………如上结构的…

ARM 位置无关代码(PIC)的分析理解

2019独角兽企业重金招聘Python工程师标准>>> PIC的特点是&#xff1a; 它被加载到任意地址空间都可以正确的执行。其原理是PIC对常量和函数入口地址的操作都是基于PC偏移量的寻址方式。即使程序被移动&#xff0c;但是PC也变化了&#xff0c;而偏移量是不变的&#…

Linux压缩/解压缩

整合资源&#xff0c;仅供自己参考&#xff1a;&#xff09; TAR 命令名 tar - tar 档案文件管理程序的 GNU 版本。下面将逐个介绍其含义 总览 tar [ - ] A --catenate --concatenate | c --create | d --diff --compare | r --append | t --list | u --update | x -extract -…

为什么校招面试中总被问“线程与进程的区别”?我该如何回答?

作者 | 宇宙之一粟责编 | 徐威龙出品 | AI 科技大本营&#xff08;rgznai100&#xff09;进程与线程&#xff1f;&#xff08;Process vs. Thread&#xff1f;&#xff09;面试官&#xff08;正襟危坐中&#xff09;&#xff1a;给我说说“线程”与“进程”吧。我&#xff08;总…

Linux线程编程

1.编译 undefined reference to pthread_create问题解决 出现如下错误&#xff1a; undefined reference to pthread_create undefined reference to pthread_join 问题原因&#xff1a; pthread 库不是 Linux 系统默认的库&#xff0c;连接时需要使用静态库 libpthread…

PHP引擎php.ini 和fastcti优化

1.1 php引擎缓存优化加速1&#xff09;eaccelerator2&#xff09;Zend3&#xff09;xcache1.2 使用tmpfs作为缓存加速的的文件目录[rootLNMP ~]# mount -t tmpfs /dev/shm -o size256m[rootLNMP ~]# mount -t tmpfs /dev/shm/ /tmp/eaccelerator/提示&#xff1a;1、上传图片缩…

从*p++说指针,数组,结构和函数

说明文中*p和*s都是一个东西&#xff0c;不做字面上的统一了。 因为右结合性&#xff0c;*p 其实就是 *(p) 1.strlen的实现 #include <stdio.h> main(){char str[] "Abcde";printf("\n string %s length %d \n",str,str_length(str)); }int str…

8比特数值也能训练模型?商汤提训练加速新算法丨CVPR 2020

出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;在CVPR 2020上&#xff0c;商汤研究院链接与编译团队、高性能计算团队和北航刘祥龙老师团队合作提出了用于加速卷积神经网络训练过程的INT8训练技术。该工作通过将网络的输入、权重和梯度量化到8比特来加速网络的前向传…

×××作,不知写些什么

博客&#xff0c;老是有写的冲动&#xff0c;不过&#xff0c;没什么韧劲坚持&#xff0c;自己感觉文采一般般啦&#xff0c;有时兴起&#xff0c;挥毫泼墨&#xff0c;蜡笔重唱一番&#xff0c;呵呵&#xff0c;自个爽朗了&#xff0c;呵呵 所以&#xff0c;自己坚持&#xff…

centos7 install 安装mysql

CentOS 7的yum源中貌似没有正常安装mysql时的mysql-sever文件&#xff0c;需要去官网上下载 # wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm # rpm -ivh mysql-community-release-el7-5.noarch.rpm # yum install mysql-community-server成功安装之…