Heath's Blog

There is no end, it is just the beginning! - A Game Developer's Notes

算法优化——递归到循环

     递归通常很直白地描述了一个求解过程,因此也是最容易被想到和实现的算法。循环其实和递归具有相同的特性(即:做重复任务),但有时,使用循环的算法并不会那么清晰地描述解决问题步骤。单从算法设计上看,递归和循环并无优劣之别。然而,在实际开发中,因为函数调用的开销,递归常常会带来性能问题,特别是在求解规模不确定的情况下。而循环因为没有函数调用开销,所以效率会比递归高。除少数编程语言对递归进行了优化外,大部分语言在实现递归算法时还是十分笨拙,由此带来了如何将递归算法转换为循环算法的问题。算法转换应当建立在对求解过程充分理解的基础上,有时甚至需要另辟蹊径。

     前段时间遇到过这样的问题:已知一2D地图格子的长宽(w、h)及每个格子的边长(a,格子为正方形),给定物体的2D坐标(pos[x , y])及半径(r),求解物体在2D地图格子中所占的格子,仅考虑n*n的情况。大概的求解过程如下:

1)根据半径,确定n*n中的n。假定计算公式为:n = Round(2*r / a)

2)根据2D坐标得到物体的“中心格子”。根据n的奇偶,计算公式不同,如下图所示。

image                  image

n为偶数[1]                                                                    n为奇数[2]

[1]:grid(x , y) = Round(pos / a)

[2]:grid(x , y) = Floor(pos / a)

其中,格子坐标x >= 0 , y >= 0。

3)以“中心格子”为基础,求出物体占据的其他格子。这样的描述,让人容易想到递归,就像用深度优先方法遍历树那样,伪代码算法如下:

if n is even
{
    get the index of 'center grid' (row , col)
    ExtendHeldGrid(row , col , n)
    ExtendHeldGrid(row - 1 , col , n)
    ExtendHeldGrid(row - 1 , col - 1 , n)
    ExtendHeldGrid(row , col - 1 , n)
}
else
{
    get the index of 'center grid' (row , col)
    ExtendHeldGrid(row , col , n)
}

function ExtendHeldGrid(row , col , level)
{
    if(level <= 0)
        return

    if((row >= 0 and row < MaxGridWidth) and (col >= 0 and col < MaxGridHeight))
    {
        mark the grid(row , col)
        ExtendHeldGrid(row , col , level - 2)
        if(level - 2 > 0)
        {
            ExtendHeldGrid(row + 1 , col , level - 2)
            ExtendHeldGrid(row - 1 , col , level - 2)
            ExtendHeldGrid(row , col + 1 , level - 2)
            ExtendHeldGrid(row , col - 1 , level - 2)
            ExtendHeldGrid(row + 1 , col + 1 , level - 2)
            ExtendHeldGrid(row - 1 , col + 1 , level - 2)
            ExtendHeldGrid(row + 1 , col - 1 , level - 2)
            ExtendHeldGrid(row - 1 , col - 1 , level - 2)
        }
    }
}

虽然,该算法得到了正确的求解结果,但是由于每个格子都会标记周围的8个格子,所以存在大量的重复,再者如果上面的过程每帧都进行的话,函数调用开销也是相当可观。

循环自然是不可避免的,消除重复便成了优化的目标。分析格子图和n为2和3的情况,试图找出用循环代替递归的方法,我发现了下面一个有趣的规律:

image                image

从“中心格子”出发,顺时针(或逆时针)以上图方式可以走遍所求解的每个格子而不重复。在实现上,每个转角也是有规律的,可以通过一个2*2的转角矩阵来控制:

[1 , 0][0 , -1]

[0 , 1][-1 , 0]

顺时针方式的转角阵

 

矩阵中的每个元素代表从当前格子走到下个格子在row和col上的变化。加之,在转角之间的路长(以格子个数计)有每转两次递增单位1的规律,算法就不难得到了,下面同样以伪代码示:

conerMat = 
{
    {0 , -1} , 
    {-1 , 0} ,
    {0  , 1} ,
    {1  , 0}
}

dir = 0     /// 转角控制,四个转角顺时针0~3
span = 1    /// 转角间的跨度
count = 1   /// 每两次增加一个跨度
rin = 1     /// 下一个转角的循环索引

if n is even
    get the index of 'center grid' (row , col)
else
    get the index of 'center grid' (row , col)

for(i = 0; i < n * n; ++i)
{
    if((row >= 0 and row < MaxGridWidth) and (col >= 0 and col < MaxGridHeight))
        mark the grid(row , col)

    if(i == rin)
    {
        dir = (dir + 1) % 4

        if(count == 2)
        {
            ++span
            count = 1
        }
        else
            ++count

        rin = i + span
    }

    row = row + conerMat[dir][0]
    col = col + conerMat[dir][1]
}

用MFC程序验证了一下算法的正确性,标号展示了循环的路线(注意GDI的坐标系中Y的正方向朝下):

image

posted on 2011-03-05 13:42 Heath 阅读(5198) 评论(4)  编辑 收藏 引用 所属分类: Game Development

Feedback

# re: 算法优化&mdash;&mdash;递归到循环[未登录] 2011-03-06 10:08 vincent

递归与递推吧?  回复  更多评论   

# re: 算法优化&mdash;&mdash;递归到循环 2011-03-06 20:38 Heath

@vincent
呵呵,笑而不语  回复  更多评论   

# re: 算法优化&mdash;&mdash;递归到循环[未登录] 2011-03-06 20:43 vincent

@Heath
呵呵,笑而不语是何解?  回复  更多评论   

# re: 算法优化&mdash;&mdash;递归到循环 2011-04-06 22:40 tianwen

给力,呵呵呵  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理