อัลกอริทึมของฉันที่แยกกล่องที่ใหญ่ที่สุดที่สามารถสร้างได้จากกล่องที่เล็กกว่านั้นช้าเกินไป


9

ลองจินตนาการถึงโลกตามก้อน (เช่น Minecraft, Trove หรือ Cube โลก) ที่ทุกอย่างถูกสร้างขึ้นจากก้อนขนาดเดียวกันและก้อนทั้งหมดเป็นของเดียวกันชนิด

เป้าหมายคือการเป็นตัวแทนของโลกที่มีกล่องสี่เหลี่ยมจำนวนน้อยที่สุด (โดยรวมก้อน แต่ยังคงรูปร่างนูน (aka รูปร่างกล่องสี่เหลี่ยม)) อัลกอริทึมของฉันประสบความสำเร็จในสิ่งนี้ แต่ประสิทธิภาพของมันช้าเกินไปสำหรับวัตถุประสงค์ที่ตั้งใจไว้ มีอัลกอริทึมเร็วขึ้นหรือไม่

หลอก -C # -code สำหรับอัลกอริทึมของฉันเป็นพื้น:

struct Coordinate { int x,y,z; }; //<-- integer based grid
HashSet<Coordinate> world; // <-- contains all the cubes

//width, height, and length represent how many cubes it spans
struct RectangularBox { Coordinate coord; int width,height,length; }

void Begin()
{
    List<RectangularBox> fewestBoxes = new List<RectangularBox>();
    while(world.Count > 0)
    {
         RectangularBox currentLargest = ExtractLargest();
         fewestBoxes.Add(currentLargest);
         world.RemoveRange(currentLargest.ContainedCubes());
    }
    //done; `fewestBoxes` contains the fewest rectangular boxes needed.
}

private RectangularBox ExtractLargest()
{
    RectangularBox largestBox = new RectangularBox();
    foreach (Coordinate origin in world)
    {
        RectangularBox box = FindMaximumSpan(origin);
        if (box.CalculateVolume() >= largestBox.CalculateVolume())
            largestBox = box;
    }
    return largestBox;
}

private RectangularBox FindMaximumSpan(Coordinate origin)
{
    int maxX, maxY,maxZ;
    while (world.Contains(origin.Offset(maxX, 0, 0))) maxX++;
    while (world.Contains(origin.Offset(0, maxY, 0))) maxY++;
    while (world.Contains(origin.Offset(0, 0, maxZ))) maxZ++;

    RectangularBox largestBox;
    for (int extentX = 0; extentX <= maxX; extentX++)
        for (int extentY = 0; extentY <= maxY; extentY++)
            for (int extentZ = 0; extentZ <= maxZ; extentZ++)
            {
                int lengthX = extentX + 1;
                int lengthY = extentY + 1;
                int lengthZ = extentZ + 1;
                if (BoxIsFilledWithCubes(origin, lengthX, lengthY, lengthZ))
                {
                    int totalVolume = lengthX * lengthY * lengthZ;
                    if (totalVolume >= largestBox.ComputeVolume())
                        largestBox = new RectangularBox(origin, lengthX, lengthY, lengthZ);
                }
                else
                    break;
            }
    return largestBox;
}

private bool BoxIsFilledWithCubes(Coordinate coord, 
    int lengthX, int lengthY, int lengthZ)
{
    for (int gX = 0; gX < lengthX; gX++)
        for (int gY = 0; gY < lengthY; gY++)
            for (int gZ = 0; gZ < lengthZ; gZ++)
                if (!world.Contains(coord.Offset(gX, gY, gZ)))
                    return false;
    return true;
}

โดยพื้นฐานแล้วสำหรับทุก ๆ บล็อกในโลกนั้นจะค้นพบว่ามีบล็อกที่ขัดแย้งกันจำนวนมากในแต่ละมิติสามมิติ (+ X, + Y, + Z) จากนั้นก็เติมน้ำท่วมปริมาณและตรวจสอบซึ่งเป็นบรรจุที่ใหญ่ที่สุดที่ไม่ได้หายไปบล็อกใด ๆ


อัปเดต: เนื่องจากฉันดูเหมือนจะบอกเป็นนัย ๆ ว่านี่เป็นของเอ็นจิ้นการเรนเดอร์เกมฉันแค่ต้องการชี้แจงนี่ไม่ใช่สำหรับเอ็นจิ้นการเรนเดอร์ของเกม มันเป็นตัวแปลงไฟล์ ไม่มี GUI


2
อาจเหมาะสมกว่าสำหรับ codereview.stackexchange.com
Rotem

4
@Remem บางที แต่จริง ๆ แล้วฉันกำลังมองหาอัลกอริทึมทางเลือกมากกว่าการตรวจสอบรหัสของฉัน ฉันให้รหัสของฉันเป็นนิสัย
นายสมิ ธ

แน่นอนว่าเหมาะสม
Rotem

คำถามอัลกอริทึมที่มีความเหมาะสมมากขึ้นไปยังเว็บไซต์ SE เช่นวิทยาการคอมพิวเตอร์ ...
Bakuriu

นอกจากนี้ยังขึ้นอยู่กับความถี่ที่คุณเรียกใช้เมธอด ถ้าคุณเรียกมันว่าทุก ๆ เฟรมหรือเฉพาะเมื่อบล็อกมีการเปลี่ยนแปลง เกมเหล่านี้มักจะมีชิ้นส่วน (สี่เหลี่ยมผืนผ้าขนาดเฉพาะเช่นบล็อก 64x64x32) ค่าแคชให้มากที่สุดและคำนวณได้เฉพาะก้อนเท่านั้น และคำนวณเฉพาะค่าเหล่านี้บนบล็อคที่มองเห็นได้
the_lotus

คำตอบ:


6

คุณสามารถใช้ประโยชน์จากความจริงที่ว่าเมื่อใด

 BoxIsFilledWithCubes(c,x,y,z)

คืนค่าเป็นจริงดังนั้นจึงไม่จำเป็นต้องตรวจสอบBoxIsFilledWithCubes(c,x+1,y,z)ลูกบาศก์เหล่านั้นทั้งหมดในช่วงพิกัด "(c, x, y, z)" อีกครั้ง คุณต้องตรวจสอบลูกบาศก์เหล่านั้นด้วยพิกัด x ใหม่ c.x + (x+1)เท่านั้น (เช่นเดียวกันสำหรับy+1หรือz+1) โดยทั่วไปแล้วการแบ่งกล่องออกเป็นสองกล่องเล็ก ๆ (ซึ่งคุณอาจรู้อยู่แล้วว่าทั้งสองเต็มไปด้วยลูกบาศก์หรือไม่เติมทั้งคู่) คุณสามารถใช้เทคนิคการหารและพิชิตได้ที่นี่ซึ่งเร็วกว่าของคุณ วิธีการดั้งเดิมเมื่อคุณแคชผลลัพธ์ระดับกลาง

ในการทำสิ่งนี้ให้สำเร็จคุณจะเริ่มนำไปใช้BoxIsFilledWithCubes(c,x,y,z)ซ้ำเช่น:

 bool BoxIsFilledWithCubes(coord,lx,ly,lz)
 {
     if(lx==0|| ly==0 || lz==0)
        return true;
     if(lx==1 && ly==1 && lz==1)
          return world.Contains(coord);
     if(lx>=ly && lx>=lz)  // if lx is the maximum of lx,ly,lz ....
         return BoxIsFilledWithCubes(coord,lx/2,ly,lz) 
             && BoxIsFilledWithCubes(coord.Offset(lx/2,0,0), lx-lx/2, ly, lz);
     else if(ly>=lz && ly>=lx)  
         // ... analogously when ly or lz is the maximum

 }

จากนั้นใช้การบันทึกช่วยจำ (เหมือนที่อธิบายไว้ที่นี่ ) เพื่อหลีกเลี่ยงการโทรซ้ำ ๆBoxIsFilledWithCubesด้วยพารามิเตอร์เดียวกัน หมายเหตุ: คุณจะต้องล้างแคช memoization เมื่อคุณใช้การเปลี่ยนแปลงที่คุณภาชนะเช่นโดยworld world.RemoveRangeอย่างไรก็ตามฉันเดาว่านี่จะทำให้โปรแกรมของคุณเร็วขึ้น


5

สร้างoctreeด้วยขนาดโหนดใบ aabb ขนาดของกล่องของคุณ ในขณะที่กำลังเดินผ่าน octree คุณสามารถรวมโหนดได้อย่างง่าย โหนดที่ถูกเติมโดยสมบูรณ์นั้นมีความสำคัญที่จะผสาน (new box = parent aabb) ในขณะที่สำหรับโหนดที่ถูกเติมบางส่วนคุณสามารถใช้ตัวแปรของอัลกอริทึมปัจจุบันของคุณเพื่อตรวจสอบความสามารถในการผสาน


การเติมสองโหนดนั้นไม่ได้หมายความว่าควรรวมเข้าด้วยกัน มันไม่ใช่ขั้นตอนในการค้นหากล่องที่ใหญ่ที่สุด หากคุณรวมพวกเขาอาจจะต้องแยกพวกเขาอีกครั้งในภายหลังเมื่อพบกล่องที่ใหญ่ที่สุด ฉันไม่เห็นว่า octree ช่วยในสถานการณ์นี้ได้อย่างไร
นายสมิ ธ

โหนดแบบเต็มสามารถผสานได้ในแง่ที่ว่าเด็กทั้ง 8 คนสามารถเป็นส่วนหนึ่งของกล่องที่มีขนาดใหญ่กว่ากล่องเดียว กล่องขนาดใหญ่นี้ไม่จำเป็นต้องแยกในภายหลัง แต่มันอาจจะถูกผสาน ลำดับชั้นช่วยให้คุณสามารถรวมกล่องจำนวนมากได้อย่างรวดเร็วโหนดที่เติมอย่างสมบูรณ์ช่วยให้คุณสามารถกระโดดขึ้นไปบนระดับหนึ่งโดยไม่ต้องทดสอบเพิ่มเติม
Wilbert

1

คุณดูเหมือนอย่างน้อย O (n ^ 2) (ดูสัญกรณ์ O ขนาดใหญ่ ) ขณะที่คุณวนรอบกล่องทั้งหมดในโลกใน“ Begin ()” จากนั้นสำหรับแต่ละกล่องคุณวนรอบกล่องทั้งหมดในโลกใน ExtractLargest ( ) ดังนั้นโลกที่มีกล่องที่ไม่เกี่ยวข้อง 10 กล่องจะใช้เวลานานกว่าโลกถึง 4 เท่าด้วยกล่องที่ไม่เกี่ยวข้อง 5 กล่อง

ดังนั้นคุณต้อง จำกัด จำนวนกล่องที่ ExtractLargest () ต้องดูเพื่อที่คุณจะต้องใช้การค้นหาเชิงพื้นที่บางประเภทเนื่องจากคุณทำงานในรูปแบบ 3 มิติคุณอาจต้องค้นหาเชิงพื้นที่ 3 มิติ อย่างไรก็ตามเริ่มแรกโดยการทำความเข้าใจการค้นหาเชิงพื้นที่ 2d

จากนั้นพิจารณาว่าคุณจะมีกล่องจำนวนมากอยู่ด้านบนของกันและกันหรือไม่ถ้าไม่ใช่แล้วการค้นหาอวกาศ 2d ที่ครอบคลุม x, y อาจเพียงพอที่จะลดการวนซ้ำของคุณ

octree / ควอดทรีเป็นหนึ่งในตัวเลือก แต่มีตัวเลือกอื่น ๆ อีกมากมายสำหรับการแบ่งพื้นที่ ,

แต่คุณอาจจะสามารถใช้รายการอาร์เรย์ 2 มิติ ( ดัชนีดัชนีพื้นที่ตาราง ) ซึ่งกล่องทั้งหมดที่ครอบคลุมจุด (a, b) อยู่ในอาร์เรย์ [a, b] .list แต่ส่วนใหญ่ที่ขี้เกียจที่จะนำไปสู่อาร์เรย์ที่มีขนาดใหญ่เกินไปดังนั้นสิ่งที่เกี่ยวกับอาร์เรย์ [mod (a, 100), mob (b, 100)] รายการ? ทั้งหมดนี้ขึ้นอยู่กับข้อมูลของคุณเป็นเหมือน

(ฉันเห็นว่าโซลูชันกริดทำงานได้ดีมากในชีวิตจริง)

หรือทำในสิ่งที่วิลเบิร์ตพูดกับอ็อตทรีที่มีขนาดโหนดของใบขนาดของกล่องของคุณ แต่หลังจากนั้นคุณอาจต้องหากล่องที่เมาส์ของผู้ใช้ชี้ไปที่ ฯลฯ อีกครั้งเป็นกรณีของการค้นหาเชิงพื้นที่

( คุณต้องตัดสินใจว่าคุณแค่พยายามทำให้ซอฟต์แวร์นี้ใช้งานได้หรือถ้าคุณพยายามที่จะเข้าใจวิธีการเป็นโปรแกรมเมอร์ที่ดีขึ้นและด้วยเหตุนี้จึงมีความสนใจในการเรียนรู้มากกว่าจึงเป็นวิธีแก้ปัญหาที่รวดเร็ว )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.