在3D阵列上“扩散”递减值的算法

时间:2014-02-27 03:14:51

标签: c# arrays algorithm

我需要找到一个有效的算法:

byte[,] initialArray

0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0
0   0   0   0   5   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0
0   0   5   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0

对此:

byte[,] resultArray

0   0   0   1   2   1   0   0   0   0
0   0   1   2   3   2   1   0   0   0
0   1   2   3   4   3   2   1   0   0
1   2   3   4   5   4   3   2   1   0
0   1   2   3   4   3   2   1   0   0
0   1   2   2   3   2   1   0   0   0
1   2   3   2   2   1   0   0   0   0
2   3   4   3   2   1   0   0   0   0
3   4   5   4   3   2   1   0   0   0
2   3   4   3   2   1   0   0   0   0

发生了什么

初始数组有两个单元格设置为初始值,其他单元格设置为0.算法需要将该值“扩散”到相邻单元格(无对角线,只有上/下/左/右) 。每次该值传播到新单元格时,该值将减1并再次以递归方式传播。当值达到0时,它会停止。

如果值正在扩展到值> gt的单元格0,应保留两个值中最大的一个,而不是简单地覆盖。

该示例显示了一个2D数组,但我实际上正在使用3D数组。

我的尝试

我已经设法在C#中创建一个简单的递归算法。它有效,但必须非常低效。这是一个减慢大型3D阵列的方法,其中4个初始单元的值> = 10.必须有一个更好的方法来做到这一点(对于那些熟悉的人来说,这是Minecraft游戏用来确定光强度的方法。游戏中的每个单元格.Minecraft级别阵列很大,可以包含许多光源)

我正在寻找最有效的方法。这是我对3D阵列的C#实现:

main ... {

    List<int[]> toCheck = new List<int[]>(); 

    // This list will keep a record of the initial cells that have a value > 0
    // For loop over each cell to find those with initial value > 0

    for (int x=0; x<worldX; x++){
        for (int y=0; y<worldY; y++){
            for (int z=0; z<worldZ; z++){

                if (data[x,y,z].light > 0)
                    toCheck.Add (new int[] {x,y,z});

            }
        }
    }

    // For each cell w/ initial value > 0, spread the light
    foreach (int[] i in toCheck) 
        SpreadLight(i[0],i[1],i[2],(byte)(data[i[0],i[1],i[2]].light - 1));


}


void SpreadLight(int x, int y, int z, byte light) {

    try {
        // Make sure this cells current value is smaller than the value we want to assign to it
        if (data[x,y,z].light < light) {
            data[x,y,z].light = (byte)light;
        }

        // If the value at this cell is > 0, get adjacent cells and spread the light to each of them
        if (light > 0) {
            int[][] adjBlocks = GetAdjacentBlocks(x,y,z);
            for (int i = 0; i < 6; i++) {
                SpreadLight(adjBlocks[i][0], adjBlocks[i][1], adjBlocks[i][2],(byte)(light-1));
            }
        }
    }
    catch { return; }  // I'm not proud if this, it's the easiest way I found to avoid out of array bounds error

}


// This method simply returns an array with the 3D coordinates of each adjacent cell
int[][] GetAdjacentBlocks(int x, int y, int z) {
    int[][] result = new int[6][];

    // Top
    result[0] = new int[] {x, y+1, z};
    // North
    result[1] = new int[] {x, y, z+1};
    // East
    result[2] = new int[] {x+1, y, z};
    // South
    result[3] = new int[] {x, y, z-1};
    // West
    result[4] = new int[] {x-1, y, z};
    // Bottom
    result[5] = new int[] {x, y-1, z};

    return result;
}

3 个答案:

答案 0 :(得分:3)

试试这个。根据我的经验,1D阵列的工作速度比2D阵列快得多。还实现了几个计算快捷方式。

2D版

class Program
{
    static void Main(string[] args)
    {
        Area A=new Area(10, 10);
        A[3, 4]=5;
        A[8, 2]=5;

        Console.WriteLine(A);
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   0   0   5   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        //0   0   5   0   0   0   0   0   0   0  
        //0   0   0   0   0   0   0   0   0   0  
        bool spread1=A.CheckSpread();

        Console.WriteLine();
        Console.WriteLine("Spreading...");
        A.Spread();
        bool spread2=A.CheckSpread();

        Console.WriteLine(A);
        //0   0   0   1   2   1   0   0   0   0  
        //0   0   1   2   3   2   1   0   0   0  
        //0   1   2   3   4   3   2   1   0   0  
        //1   2   3   4   5   4   3   2   1   0  
        //0   1   2   3   4   3   2   1   0   0  
        //0   1   2   2   3   2   1   0   0   0  
        //1   2   3   2   2   1   0   0   0   0  
        //2   3   4   3   2   1   0   0   0   0  
        //3   4   5   4   3   2   1   0   0   0  
        //2   3   4   3   2   1   0   0   0   0  
    }
}

public struct Area
{
    byte[] map;
    int rows, columns;

    public Area(int rows, int columns)
    {
        this.map=new byte[rows*columns];
        this.columns=columns;
        this.rows=rows;
    }
    public Area(Area other)
        : this(other.rows, other.columns)
    {
        Array.Copy(other.map, this.map, other.map.Length);
    }
    public Area(byte[,] array)
    {
        this.rows=array.GetLength(0);
        this.columns=array.GetLength(1);
        this.map=new byte[rows*columns];
        for (int i=0; i<rows; i++)
        {
            for (int j=0; j<columns; j++)
            {
                this.map[i*columns+j]=array[i, j];
            }
        }
    }

    public int Rows { get { return rows; } }
    public int Columns { get { return columns; } }
    public byte[] Map { get { return map; } }

    public byte this[int index]
    {
        get { return map[index]; }
        set { map[index]=value; }
    }
    public byte this[int row, int column]
    {
        get { return map[row*columns+column]; }
        set { map[row*columns+column]=value; }
    }

    public byte[,] ToArray2()
    {
        byte[,] array=new byte[rows, columns];
        for (int i=0; i<rows; i++)
        {
            for (int j=0; j<columns; j++)
            {
                array[i, j]=map[i*columns+j];
            }
        }
        return array;
    }

    public void Spread()
    {
        bool changed;
        do // CAUTION: This is not guaranteed to exit. Or is it?
        {
            changed=false;

            for (int k=0; k<map.Length; k++)
            {
                byte x=map[k];
                if (x<=1) continue; // cannot affect neighbors

                int i=k/columns;
                int j=k%columns;

                int k_N=i>0?(i-1)*columns+j:-1;
                int k_S=i<rows-1?(i+1)*columns+j:-1;
                int k_E=j<columns-1?i*columns+j+1:-1;
                int k_W=j>0?i*columns+j-1:-1;

                if (k_N>=0&&map[k_N]+1<x) { map[k_N]=(byte)(x-1); changed=true; }
                if (k_S>=0&&map[k_S]+1<x) { map[k_S]=(byte)(x-1); changed=true; }
                if (k_E>=0&&map[k_E]+1<x) { map[k_E]=(byte)(x-1); changed=true; }
                if (k_W>=0&&map[k_W]+1<x) { map[k_W]=(byte)(x-1); changed=true; }
            }

        } while (changed); 
    }

    public bool CheckSpread()
    {
        for (int k=0; k<map.Length; k++)
        {
            byte x=map[k];
            if (x<=1) continue; // cannot affect neighbors

            int i=k/columns;
            int j=k%columns;

            int k_N=i>0?(i-1)*columns+j:-1;
            int k_S=i<rows-1?(i+1)*columns+j:-1;
            int k_E=j<columns-1?i*columns+j+1:-1;
            int k_W=j>0?i*columns+j-1:-1;

            if (k_N>=0&&map[k_N]+1<x) return false;
            if (k_S>=0&&map[k_S]+1<x) return false;
            if (k_E>=0&&map[k_E]+1<x) return false;
            if (k_W>=0&&map[k_W]+1<x) return false;
        }
        return true;
    }

    public override string ToString()
    {
        string[] table=new string[rows];
        for (int i=0; i<rows; i++)
        {
            string[] row=new string[columns];
            for (int j=0; j<columns; j++)
            {
                row[j]= string.Format("{0,-3}", map[i*columns+j]);
            }
            table[i]= string.Join(" ", row);
        }
        return string.Join(Environment.NewLine, table);
    }
}

3D版

public struct Area3
{
    byte[] map;
    int rows, columns, pages;
    public Area3(int rows, int columns, int pages)
    {
        this.map=new byte[rows*columns*pages];
        this.columns=columns;
        this.rows=rows;
        this.pages=pages;
    }
    public Area3(Area3 other)
        : this(other.rows, other.columns, other.pages)
    {
        Array.Copy(other.map, this.map, other.map.Length);
    }
    public Area3(byte[, ,] array)
    {
        this.rows=array.GetLength(0);
        this.columns=array.GetLength(1);
        this.pages=array.GetLength(2);
        this.map=new byte[rows*columns*pages];
        for (int i=0; i<rows; i++)
        {
            for (int j=0; j<columns; j++)
            {
                for (int l=0; l<pages; l++)
                {
                    this.map[(l*rows+i)*columns+j]=array[i, j, l];
                }
            }
        }
    }
    public int Rows { get { return rows; } }
    public int Columns { get { return columns; } }
    public int Pages { get { return pages; } }
    public byte[] Map { get { return map; } }

    public byte this[int index]
    {
        get { return map[index]; }
        set { map[index]=value; }
    }
    public byte this[int row, int column, int page]
    {
        get { return map[(page*rows+row)*columns+column]; }
        set { map[(page*rows+row)*columns+column]=value; }
    }
    public byte[, ,] ToArray3()
    {
        byte[, ,] array=new byte[rows, columns, pages];
        for (int i=0; i<rows; i++)
        {
            for (int j=0; j<columns; j++)
            {
                for (int l=0; l<pages; l++)
                {
                    array[i, j, l]=map[(l*rows+i)*columns+j];
                }
            }
        }
        return array;
    }
    public void Spread()
    {
        bool changed;
        do
        {
            changed=false;

            for (int k=0; k<map.Length; k++)
            {
                byte x=map[k];
                if (x<=1) continue; // cannot affect neighbors

                int l=k/(rows*columns);
                int i=(k%(rows*columns))/columns;
                int j=(k%(rows*columns))%columns;

                int k_N=i>0?(l*rows+i-1)*columns+j:-1;
                int k_S=i<rows-1?(l*rows+i+1)*columns+j:-1;
                int k_E=j<columns-1?(l*rows+i)*columns+j+1:-1;
                int k_W=j>0?(l*rows+i)*columns+j-1:-1;
                int k_U=l<pages-1?((l+1)*rows+i)*columns+j:-1;
                int k_D=l>0?((l-1)*rows+i)*columns+j:-1;

                if (k_N>=0&&map[k_N]+1<x) { map[k_N]=(byte)(x-1); changed=true; }
                if (k_S>=0&&map[k_S]+1<x) { map[k_S]=(byte)(x-1); changed=true; }
                if (k_E>=0&&map[k_E]+1<x) { map[k_E]=(byte)(x-1); changed=true; }
                if (k_W>=0&&map[k_W]+1<x) { map[k_W]=(byte)(x-1); changed=true; }
                if (k_U>=0&&map[k_U]+1<x) { map[k_U]=(byte)(x-1); changed=true; }
                if (k_D>=0&&map[k_D]+1<x) { map[k_D]=(byte)(x-1); changed=true; }
            }

        } while (changed);
    }

}

没有可视化编码为3D。

答案 1 :(得分:2)

以下实现(2D)应该更快 - 它将对象创建和不必要的函数调用保持在最低限度。扩展到3D将非常简单:

class Program
{
    class ArrayPoint { public int x; public int y;}

    private static byte[,] startArray =
    {
        {0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,5,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,5,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0}
    };

    private static int rows = startArray.GetLength(0);
    private static int cols = startArray.GetLength(1);

    static void Main(string[] args)
    {
        Spread(startArray);
    }

    static void Spread(byte[,] array)
    {
        var points = GetStartPoints(array);

        foreach (var point in points.ToList())
            SpreadPoint(array, point.x, point.y);

        Display(array);
    }

    static void SpreadPoint(byte[,] array, int x, int y)
    {
        for (var i = x-1; i < x+2; i++)
            for (var j = y-1; j < y+2; j++)
                if (  (i==x || j==y) && !(i==x && j==y) && (i >= 0 && i < rows && j >= 0 && j < cols)
                    && array[i, j] + 1 < array[x, y])
                {
                    array[i, j] = (byte)(array[x, y] - 1);
                    SpreadPoint(array, i, j);
                }
    }

    static void Display(byte[,] array)
    {
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
                Console.Write("{0} ",array[i,j]);
            Console.WriteLine();
        }
        Console.WriteLine();
    }


    static IEnumerable<ArrayPoint> GetStartPoints(byte[,] array)
    {
        for (var i = 0; i < rows; i++)
            for (var j = 0; j < cols; j++)
                if (array[i, j] != 0)
                    yield return new ArrayPoint {x = i, y = j};
    }
}

输出是:

0 0 0 1 2 1 0 0 0 0
0 0 1 2 3 2 1 0 0 0
0 1 2 3 4 3 2 1 0 0
1 2 3 4 5 4 3 2 1 0
0 1 2 3 4 3 2 1 0 0
0 1 2 2 3 2 1 0 0 0
1 2 3 2 2 1 0 0 0 0
2 3 4 3 2 1 0 0 0 0
3 4 5 4 3 2 1 0 0 0
2 3 4 3 2 1 0 0 0 0

答案 2 :(得分:0)

我的两分钱:考虑在波前同时扩大积分。因此,当比其他人更早地完成时,可以终止个别波前。

一个简化的JavaScript示例:

var s = new Array(81)
for (var i=0; i<81; i++){
    s[i] = i == 40 ? 5 : 0
}

var max = 5, m = 9, n = 9

function expand(wavefront){ 
    while (wavefront.val > 0) {

        //Southwest
        var tmpLoc = wavefront.loc--
        for (var i=0; i<max - 1 - wavefront.val; i++){
            s[tmpLoc] = wavefront.val
            tmpLoc += n + 1
        }
        //Southeast...
        //...
        wavefront.val--
    }
}

expand({loc: 49, val: 4})     

s现在看起来像这样:

        [0,0,0,0,0,0,0,0,0
        ,0,0,0,0,0,0,0,0,0
        ,0,0,0,0,0,0,0,0,0
        ,0,0,0,0,0,0,0,0,0
        ,0,0,0,0,5,0,0,0,0
        ,0,1,2,3,0,0,0,0,0
        ,0,0,1,2,0,0,0,0,0
        ,0,0,0,1,0,0,0,0,0
        ,0,0,0,0,0,0,0,0,0]