Counting Rooms While Knowing Where Walls Are

梦想与她 提交于 2019-11-28 10:26:04

This is a typical problem of finding the total number of connected components in a graph.

Let me help you visualize the analogy by focusing on following points, keeping in mind that we are dealing with undirected graphs here.

1.In a graph, we have various vertices and two vertices are said to be adjacent to each other, if there is an edge between them. Just like your castle where two cells are adjacent to each other if one cell could lead to another cell.

2. In a graph we have two vertices belong to the same connected component, if there is a path between two vertices using the edges. Just like your castle where two cells belong to the same room number if one cell can by following a path of cells could lead to another.

3. In a graph we have connected components, such that a connected component is made up of vertices such that every two vertices of the connected component have a path between them.Just like your castle where we have rooms, such that every two cells of the same room have a path of cells between them.

Now if you are still wondering how to build the graph, well its easy.

The number of vertices will be NxM(for a castle of size N rows and M columns) which is equal to the number of cells.

Just number the cells sequentially and there is an edge between cell a(meaning vertex a) and cell b(vertex b) if both cells are adjacent.

Now the total number of rooms can be easily counted by applying bfs or dfs algorithm on your graph that you build.

The algorithm is described in the first link I provided.

Jonathan Mee

So honestly I just really wanted to try to solve this. So I'm going to say you've made a valiant effort at this and just go ahead and show you how to do it. I'm going to assume that you can supply the algorithm the following:

  1. Input numbers read in in binary (so "1101" will be read in as the decimal number "13")
  2. All these numbers in a const vector<char> rooms
  3. The width of each row of "rooms" can be put in size_t width (which must be consistent across all rows, we have to be working with a rectangle of rooms)
  4. All exterior "walls" of "rooms" will have a "wall"
  5. There are less than numeric_limits<int>::max() "rooms"

We'll use vector<int> temp to label each room, we'll construct it of the size of rooms and initialize each label to 0. int result will be used to label rooms, and will be initialized to 0. But because all the room labels will not be decremented when a smaller label is replaced, size(set<int>(cbegin(temp), cend(temp))) will be used to find the final label count.

Our solution will be built around a function taking in 2 "rooms" between which there is no wall; such that either:

  1. One room is not yet labeled, in which case it will take on the other room's label
  2. Both rooms share a label in which case no action will take place
  3. One rooms label is smaller in which case all rooms of the larger label will be assigned the smaller label

An important note about this function, I'm using the unary plus operator to construct an R-Value int from an L-Values int& more information here. A clearer solution would probably be to use static_cast<int> but for some reason Visual Studio 2015 doesn't work as expected more information here.

void generate(vector<int>& temp, int& target, const size_t width, const size_t i) {
    const auto replacement = temp[i];

    if (target > replacement) {
        replace(begin(temp), next(begin(temp), min(size(temp), i + width - 1)), target, replacement);
    } else {
        target = replacement;
    }
}

Using this code we are able to:

for (size_t i = 0U; i < size(rooms); ++i) {
    const auto toWest = (rooms[i] & 0b1000) == 0;
    const auto toNorth = (rooms[i] & 0b100) == 0;
    const auto toEast = (rooms[i] & 0b10) == 0;
    const auto toSouth = (rooms[i] & 0b1) == 0;
    const auto west = toWest && temp[i - 1] != 0 ? temp[i - 1] : numeric_limits<int>::max();
    const auto north = toNorth && temp[i - width] != 0 ? temp[i - width] : numeric_limits<int>::max();
    const auto east = toEast && temp[i + 1] != 0 ? temp[i + 1] : numeric_limits<int>::max();

    temp[i] = min({ temp[i] != 0 ? temp[i] : numeric_limits<int>::max(), result + 1, west, north, east });

    if (temp[i] == result + 1) ++result;

    if (toWest) generate(temp, temp[i - 1], width, i);
    if (toNorth) generate(temp, temp[i - width], width, i);
    if (toEast) generate(temp, temp[i + 1], width, i);
    if (toSouth) temp[i + width] = temp[i];
}

Live Example

There are few problems with your code prohibiting proper debug form third side like insufficient info about how it works, undefined variables (m,n,b) array overruns (numerous access to [411] while the size is only 411 instead of 412) discouraging anyone from even start to try (making one wonder if the code is really useful and worthy of time spending). I was also curious so here my simple unoptimized attempt in BDS2006 Turbo C++ (successor to BCB6 so this code should work there as well) for this task.

[rooms.h]

//---------------------------------------------------------------------------
#ifndef _rooms_h
#define _rooms_h
//---------------------------------------------------------------------------
class rooms
    {
public:
    // variables
    int xs,ys;          // map resolution
    DWORD **map;        // map[xs][ys]

    enum
        {
        _W=8,
        _N=4,
        _E=2,
        _S=1
        };

    // internals
    rooms();
    ~rooms();
    void _free();                                       // release map memory

    // inteface
    void resize(int _xs,int _ys);                       // realloc map to new resolution
    void set(AnsiString txt);                           // copy txt to map
    void draw(TCanvas *scr,int x,int y,int sz);         // draw map on Canvas at (x,y) with grid size sz
    int  count();                                       // count rooms
    };
//---------------------------------------------------------------------------
     rooms::rooms()     { map=NULL; xs=0; ys=0; }
     rooms::~rooms()    { _free(); }
//---------------------------------------------------------------------------
void rooms::_free()
    {
    if (map)
        {
        for (int x=0;x<xs;x++)
         if (map[x])
          delete[] map[x];
        delete[] map;
        }
    map=NULL; xs=0; ys=0;
    }
//---------------------------------------------------------------------------
void rooms::resize(int _xs,int _ys)
    {
    if ((xs==_xs)&&(ys==_ys)) return;
    _free();
    xs=_xs; ys=_ys;
    map=new DWORD*[xs];
    for (int x=0;x<xs;x++)
     map[x]=new DWORD[ys];
    }
//---------------------------------------------------------------------------
void rooms::set(AnsiString txt)
    {
    int i,l,x,y,n0,n1;
    l=txt.Length(); if (!l) return;
    // count eof lines (ys)
    for (y=0,i=1;i<=l;i++)
     if ((txt[i]==13)||(txt[i]==10))
        {
        y++;
        if (i<l)
         if ((txt[i+1]==13)||(txt[i+1]==10)) i++;
        }
     if ((txt[l]!=13)&&(txt[l]!=10)) y++;   // handle missing last eof
    // count numbers per line (xs)
    for (n1=0,x=0,i=1;i<=l;i++)
        {
        n0=n1; n1=0;
        if ((txt[i]=='0')||(txt[i]=='1')) n1=1;
        if ((txt[i+1]==13)||(txt[i+1]==10)) break;
        if ((!n0)&&(n1)) x++;
        }
    // copy data
    resize(x,y);
    for (x=0,y=0,i=1;i<=l;)
        {
        // skip spaces
        while ((i<=l)&&(txt[i]!='0')&&(txt[i]!='1')) i++;
        // read 4 bit bin number
        n0=  0; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        n0<<=1; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        n0<<=1; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        n0<<=1; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        map[x][y]=n0;
        x++; if (x>=xs) { x=0; y++; if (y>=ys) break; }
        }
    // clear the rest in case of error in data
    if ((y<ys)&&(x<xs)) for (;;)
        {
        map[x][y]=0;
        x++; if (x>=xs) { x=0; y++; if (y>=ys) break; }
        }
    }
//---------------------------------------------------------------------------
void rooms::draw(TCanvas *scr,int x0,int y0,int sz)
    {
    int x,y,xx,yy;
    DWORD a;

    scr->Brush->Color=clDkGray;
    scr->Brush->Style=bsSolid;
    scr->FillRect(Rect(x0,y0,x0+xs*sz,y0+ys*sz));

    scr->Pen->Color=clBlue;
    scr->Pen->Width=5;
    scr->Font->Color=clBlack;
    scr->Brush->Style=bsClear;
    for (xx=x0,x=0;x<xs;x++,xx+=sz)
     for (yy=y0,y=0;y<ys;y++,yy+=sz)
        {
        a=map[x][y]&15;
        if (DWORD(a&_W)) { scr->MoveTo(xx   ,yy   ); scr->LineTo(xx   ,yy+sz); }
        if (DWORD(a&_N)) { scr->MoveTo(xx   ,yy   ); scr->LineTo(xx+sz,yy   ); }
        if (DWORD(a&_E)) { scr->MoveTo(xx+sz,yy   ); scr->LineTo(xx+sz,yy+sz); }
        if (DWORD(a&_S)) { scr->MoveTo(xx   ,yy+sz); scr->LineTo(xx+sz,yy+sz); }
        scr->TextOutA(xx+(sz>>1),yy+(sz>>1),map[x][y]>>4);
        }
    scr->Brush->Style=bsSolid;
    scr->Pen->Width=1;
    }
//---------------------------------------------------------------------------
int  rooms::count()
    {
    int x,y,i,i0,i1,w0,w1,n,e;
    // each block is a separate room
    for (n=0,x=0;x<xs;x++)
     for (y=0;y<ys;y++,n+=16)
        {
        map[x][y]&=  0x0000000F;    // low 4 bits are walls
        map[x][y]|=n&0xFFFFFFF0;    // rest is room index
        } n>>=4;
    // reindex all indexes i0 to i1
    #define map_reindex(i0,i1)                      \
        for (x=0;x<xs;x++)                          \
         for (y=0;y<ys;y++)                         \
          if (DWORD(map[x][y]&0xFFFFFFF0)==i0)      \
            {                                       \
            map[x][y]&=   0x0000000F;               \
            map[x][y]|=i1&0xFFFFFFF0;               \
            }
    // loop until no change has occured
    for (e=1;e;)
        {
        e=0;
        // merge columns
        for (x=0;x<xs;x++)
         for (y=1;y<ys;y++)
            {
            w0=map[x][y-1]&0x0000000F;
            i0=map[x][y-1]&0xFFFFFFF0;
            w1=map[x][y  ]&0x0000000F;
            i1=map[x][y  ]&0xFFFFFFF0;
            if ((i0!=i1)&&(DWORD(w0&_S)==0)&&(DWORD(w1&_N)==0)) { map_reindex(i0,i1); n--; e=1; }
            }
        // merge rows
        for (y=0;y<ys;y++)
         for (x=1;x<xs;x++)
            {
            w0=map[x-1][y]&0x0000000F;
            i0=map[x-1][y]&0xFFFFFFF0;
            w1=map[x  ][y]&0x0000000F;
            i1=map[x  ][y]&0xFFFFFFF0;
            if ((i0!=i1)&&(DWORD(w0&_E)==0)&&(DWORD(w1&_W)==0)) { map_reindex(i0,i1); n--; e=1; }
            }
        }           

    return n;
    #undef map_reindex
    }
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

[And Single window VCL Form app C++ source without any component]

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "rooms.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *bmp; // back buffer
int xs,ys;              // actual window resolution
rooms map;              // map of rooms
//---------------------------------------------------------------------------
void draw()
    {
    int x,y,sz;
    // clear bachground
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->Brush->Style=bsSolid;
    bmp->Canvas->FillRect(Rect(0,0,xs,ys));
    // compute grid size
    x=(xs-20)/map.xs; sz=x;
    y=(ys-20)/map.ys; if (x>y) sz=y;
    // and map position so it is centered
    x=(xs-(sz*map.xs))>>1;
    y=(ys-(sz*map.ys))>>1;
    // render to backbuffer (avoid flickering)
    map.draw(bmp->Canvas,x,y,sz);
    // render backbuffer to window
    Form1->Canvas->Draw(0,0,bmp);
    }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    // init backbuffer
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    // init map
    map.set("1101 0110 1101 0110 1100 0101 0110\r\n1110 1001 0110 1011 1010 1111 1010\r\n1000 0101 0011 1110 1011 1110 1010\r\n1011 1101 0101 0001 0101 0011 1011\r\n");
    Caption=map.count();    // result count is in the Window Caption
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender) { delete bmp; }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender) { draw(); }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    // get actual window size
    xs=ClientWidth;
    ys=ClientHeight;
    // resize backbufer and force redraw
    bmp->SetSize(xs,ys);
    draw();
    }
//---------------------------------------------------------------------------

The idea behind my solver for this is simple:

  1. ID each grid cell by distinct room number

    remember the cell count as n

  2. merge all neighboring rooms without any wall between them

    so loop through all rooms and if any neighbor cell has no wall in the way and has different room ID re-index its room number so booth rooms has the same one. Also decrement the room counter n.

  3. loop #2 until no re-indexing occurs

[Notes]

Do not forget to create appropriate events in your IDE instead of just copying the code otherwise it will not work.

one easy way would be creating an array with the size of the room and initialize it with "0". After that you should iterate over the array. If you find a "0", you start an BFS from that point and color the array results to the current number.

Information about the BFS

The BFS has to look to the direct neighbor and check if there is a "0" inside this array. If this is true, the BFS has to check if there are no wall between the two boxes. If there is no wall in between, color this box with the current color (at the beginning 1) and call the BFS on the new box.

The BFS stops automaticly when the room is completely colored. then the global color will be increased by 1 and the loop continues and looks for the next "0".

After the loop the count of the rooms is stored inside the global color value.

this algorithm works in O(n)

Small example:

//CountingRooms.h
#include <vector>

class CountingRooms
{
public:
    CountingRooms();
    ~CountingRooms();
    int Count();

    void TestFill();
    void Print();
private:
    struct Point
    {
        int x = 0;
        int y = 0;
    };
    unsigned int* m_arrFieldColors = nullptr;
    unsigned int* m_arrFieldWalls = nullptr;
    int m_nSizeX = 0;
    int m_nSizeY = 0;
    int m_nCurrentColor = 0;

    unsigned int GetValue(unsigned int* field, int x, int y);
    void SetValue(unsigned int* field, int x, int y, unsigned int value);

    bool CanPass(int x1, int y1, int x2, int y2);
    void DFS(int posX, int posY);
    bool IsInsideArray(int x1, int y1); 
};



//CountingRooms.cpp
#include "stdafx.h"
#include "CountingRooms.h"
#include <iostream>

CountingRooms::CountingRooms()
{
}


CountingRooms::~CountingRooms()
{
    if (m_arrFieldColors)
    {
        delete[]m_arrFieldColors;
    }
    if (m_arrFieldWalls)
    {
        delete[]m_arrFieldWalls;
    }
}

bool CountingRooms::IsInsideArray(int x, int y)
{
    return x >= 0 && y >= 0 && x < m_nSizeX && y < m_nSizeY;
}

bool CountingRooms::CanPass(int x1, int y1, int x2, int y2)
{
    if (IsInsideArray(x1, y1) && IsInsideArray(x2, y2)) //inside the array range
    {
        if (x2 - x1 == 1 && y2 - y1 == 0) // right
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 2) && !(GetValue(m_arrFieldWalls, x2, y2) & 8)) { return true; }
        }
        if (x2 - x1 == 0 && y2 - y1 == -1) // up
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 4) && !(GetValue(m_arrFieldWalls, x2, y2) & 1)) { return true; }
        }
        if (x2 - x1 == -1 && y2 - y1 == 0) // left
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 8) && !(GetValue(m_arrFieldWalls, x2, y2) & 2)) { return true; }
        }
        if (x2 - x1 == 0 && y2 - y1 == 1) // down
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 1) && !(GetValue(m_arrFieldWalls, x2, y2) & 4)) { return true; }
        }
    }
    return false;
}

void CountingRooms::DFS(int posX, int posY)
{
    if (GetValue(m_arrFieldColors, posX, posY)) // check if the field is already colored
    {
        return;
    }
    Point sStart;
    sStart.x = posX;
    sStart.y = posY;
    std::vector<Point> vecList;
    vecList.push_back(sStart);

    m_nCurrentColor++;

    while (vecList.size()) // as long as something is inside the list
    {
        Point sTemp = vecList[vecList.size()-1]; //get out the last element
        vecList.pop_back();

        if (IsInsideArray(sTemp.x, sTemp.y))
        {
            if (!GetValue(m_arrFieldColors, sTemp.x, sTemp.y)) // is field not colored
            {
                SetValue(m_arrFieldColors, sTemp.x, sTemp.y, m_nCurrentColor);

                if (CanPass(sTemp.x, sTemp.y, sTemp.x + 1, sTemp.y)) /* right*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x + 1;
                    newPoint.y = sTemp.y;
                    vecList.push_back(newPoint);
                }
                if (CanPass(sTemp.x, sTemp.y, sTemp.x - 1, sTemp.y)) /* left*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x - 1;
                    newPoint.y = sTemp.y;
                    vecList.push_back(newPoint);
                }
                if (CanPass(sTemp.x, sTemp.y, sTemp.x, sTemp.y - 1)) /* up*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x;
                    newPoint.y = sTemp.y - 1;
                    vecList.push_back(newPoint);
                }
                if (CanPass(sTemp.x, sTemp.y, sTemp.x, sTemp.y + 1)) /* down*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x;
                    newPoint.y = sTemp.y + 1;
                    vecList.push_back(newPoint);
                }

            }
        }
    }
}

int CountingRooms::Count()
{
    m_nCurrentColor = 0;
    for (int i = 0; i < m_nSizeY; ++i)
    {
        for (int j = 0; j < m_nSizeX; ++j)
        {
            DFS(j, i);
        }
    }
    return m_nCurrentColor;
}

void CountingRooms::TestFill()
{
    m_arrFieldWalls = new unsigned int[42]{13, 6,13, 6,12, 5, 6,
                                           14, 9, 6,11,10,15,10,
                                            8, 5, 3,14,11,14,10,
                                           11,13, 5, 1, 5, 3,11};
    m_arrFieldColors = new unsigned int[42];
    for (int i = 0; i < 42;i++)
    {
        m_arrFieldColors[i] = 0;
    }
    m_nSizeX = 7;
    m_nSizeY = 4;

}

unsigned int CountingRooms::GetValue(unsigned int* field, int x, int y)
{
    if (IsInsideArray(x, y))
    {
        return field[x + m_nSizeX*y];
    }
    return -1;
}

void CountingRooms::SetValue(unsigned int* field, int x, int y, unsigned int value)
{
    if (IsInsideArray(x, y))
    {
        field[x + m_nSizeX*y] = value;
    }
}

void CountingRooms::Print()
{
    std::cout << "Walls:" << std::endl;
    for (int j = 0; j < m_nSizeY;++j)
    {
        for (int i = 0; i < m_nSizeX;++i)
        {
            std::cout << GetValue(m_arrFieldWalls, i, j) << "\t";
        }
        std::cout << std::endl;
    }
    std::cout << std::endl<<"Colors:" << std::endl;

    for (int j = 0; j < m_nSizeY;++j)
    {
        for (int i = 0; i < m_nSizeX;++i)
        {
            std::cout << GetValue(m_arrFieldColors, i, j) << "\t";
        }
        std::cout << std::endl;
    }
}







//main.cpp
#include "stdafx.h"
#include <iostream>
#include "CountingRooms.h"

int main()
{
    CountingRooms cr;
    cr.TestFill();
    std::cout<<"There are "<<cr.Count()<<" rooms"<<std::endl;
    cr.Print();

    char key = 0;
    std::cin >> key;
    return 0;
}

btw: the BFS is replaced by a DFS, but both is working.

Output

Disjoint Sets along with Union-Find are well matched to problems of connected components. Instead of a graph, I keep track of different disjoint sets of floor tiles. Each set has its own representative tile which uniquely identifies the set.

You can read more about Union-Find here.

The algorithm is simple:

  1. For each tile, process its neighbouring tiles, if the common wall is absent. This is done using a simple union() of the two tiles. When we are done, each tile will belong to one of the disjoint sets (each set representing a connected space or room). In the attached code, parent[] stores the representative element.

    • When done, normalize each tile's representative element.
  2. Count the number of unique representative elements. This is the number of disjoint sets (and hence the number of connected spaces or rooms).

Some additional observations:

  • At any time, you only need to process the North and West walls. (Why? The proof is left to the reader.)

    That's because for a tile a[i][j], its South and East walls can be processed by tiles   a[i+1][j] and a[i][j+1] respectively.

  • Why do we need to normalize each tile's representative? The reason is that some sets may end up with two or more representative elements. We recursively find the parent element to get a unique representative for the entire set. (If you want to test this, comment out the following line from the attached code:

    // normalise each tile
    parent[i] = findSet(parent[i]);
    
  • The attached code uses a special version of Union-Find with two heuristics:

    • Union by rank (as evidenced by ranks[])
    • Path compression

Attached code: Live Example

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!