计算位图中的“孔”的数量

17
考虑一个MxN位图,其中单元格为0或1。'1'表示填充,'0'表示空置。
查找位图中的“孔洞”数,其中孔洞是一个连续的空单元格区域。
例如,这个位图有两个孔:
11111  
10101  
10101  
11111  

...而这个只有一个:

11111  
10001  
10101  
11111

当M和N都在1到8之间时,最快的方法是什么?

澄清:对角线不被视为相邻,只有边缘相邻才重要。

注意:我正在寻找利用数据格式的方法。我知道如何将其转换为图形并使用[BD]FS算法,但这似乎有些大材小用。


它尝起来像作业! - Luiscencio
2
这不是作业,但也无妨。我正在试图解决一个更大的问题,而这只是一个子问题。 - florin
2
这些孔只能直角连接吗?或者对角线也可以吗? - Peer Stritzinger
不要忘记位图是倒置存储的。 - Sam Dufel
欧拉数或者欧拉特征可以告诉你图形中的孔的数量。它可以通过只检查每个2x2邻域一次来计算。这里有一篇很好的文章:https://sydney4.medium.com/the-euler-number-of-a-binary-image-1d3cc9e57caa - Cris Luengo
显示剩余5条评论
6个回答

22
你需要对图像进行连通域标记。你可以使用我在上面链接的维基百科文章中描述的双通道算法。鉴于你的问题规模较小,单通道算法可能已经足够。
你也可以使用BFS/DFS,但我建议使用上述算法。

1
是的,连通组件标记应该可以解决问题。同时,恭喜@Jacob达到了10k(或者快要达到了——我猜你今天已经达到了声望上限)。 - Jonas
@Jonas:我知道!希望Florin今天会接受这个答案 :) - Jacob
@Jonas:我现在可以接受祝贺了:D - Jacob
我稍微修改了双通道算法,但主要思路还在那里。 https://gist.github.com/atonamy/3c50f63aebdecc9b881756e889fb5278 如果这个解决方案看起来适合编程面试,请给出您的评论。 - Arsenius

6

这似乎是不错的不相交集数据结构的应用。

将位图转换为2D数组

循环遍历每个元素

如果当前元素是0,则将其与其“前一个”空邻居(已访问)的集合合并

如果它没有空邻居,则将其添加到自己的集合中

然后只需计算集合数量即可


我在看他的答案之前写了这个回答。 - Sam Dufel

0

我写了一篇关于 Medium 的文章,描述了答案。https://medium.com/@ahmed.wael888/bitmap-holes-count-using-typescript-javascript-387b51dd754a

但是这里有代码,逻辑并不复杂,你可以在不阅读文章的情况下理解它。

export class CountBitMapHoles {
    bitMapArr: number[][];
    holesArr: Hole[] = [];
    maxRows: number;
    maxCols: number;

    constructor(bitMapArr: string[] | number[][]) {
        if (typeof bitMapArr[0] == 'string') {
            this.bitMapArr = (bitMapArr as string[]).map(
                (word: string): number[] => word.split('').map((bit: string): number => +bit))
        } else {
            this.bitMapArr = bitMapArr as number[][]
        }
        this.maxRows = this.bitMapArr.length;
        this.maxCols = this.bitMapArr[0].length;
    }

    moveToDirection(direction: Direction, currentPosition: number[]) {
        switch (direction) {
            case Direction.up:
                return [currentPosition[0] - 1, currentPosition[1]]

            case Direction.down:
                return [currentPosition[0] + 1, currentPosition[1]]

            case Direction.right:
                return [currentPosition[0], currentPosition[1] + 1]

            case Direction.left:
                return [currentPosition[0], currentPosition[1] - 1]

        }
    }
    reverseDirection(direction: Direction) {
        switch (direction) {
            case Direction.up:
                return Direction.down;
            case Direction.down:
                return Direction.up
            case Direction.right:
                return Direction.left
            case Direction.left:
                return Direction.right
        }
    }
    findNeighbor(parentDir: Direction, currentPosition: number[]) {
        let directions: Direction[] = []
        if (parentDir === Direction.root) {
            directions = this.returnAvailableDirections(currentPosition);
        } else {
            this.holesArr[this.holesArr.length - 1].positions.push(currentPosition)
            directions = this.returnAvailableDirections(currentPosition).filter((direction) => direction != parentDir);
        }
        directions.forEach((direction) => {
            const childPosition = this.moveToDirection(direction, currentPosition)
            if (this.bitMapArr[childPosition[0]][childPosition[1]] === 0 && !this.checkIfCurrentPositionExist(childPosition)) {
                this.findNeighbor(this.reverseDirection(direction), childPosition)
            }
        });
        return
    }
    returnAvailableDirections(currentPosition: number[]): Direction[] {
        if (currentPosition[0] == 0 && currentPosition[1] == 0) {
            return [Direction.right, Direction.down]
        } else if (currentPosition[0] == 0 && currentPosition[1] == this.maxCols - 1) {
            return [Direction.down, Direction.left]
        } else if (currentPosition[0] == this.maxRows - 1 && currentPosition[1] == this.maxCols - 1) {
            return [Direction.left, Direction.up]
        } else if (currentPosition[0] == this.maxRows - 1 && currentPosition[1] == 0) {
            return [Direction.up, Direction.right]
        } else if (currentPosition[1] == this.maxCols - 1) {
            return [Direction.down, Direction.left, Direction.up]
        } else if (currentPosition[0] == this.maxRows - 1) {
            return [Direction.left, Direction.up, Direction.right]
        } else if (currentPosition[1] == 0) {
            return [Direction.up, Direction.right, Direction.down]
        } else if (currentPosition[0] == 0) {
            return [Direction.right, Direction.down, Direction.left]
        } else {
            return [Direction.right, Direction.down, Direction.left, Direction.up]
        }
    }
    checkIfCurrentPositionExist(currentPosition: number[]): boolean {
        let found = false;
        return this.holesArr.some((hole) => {
            const foundPosition = hole.positions.find(
                (position) => (position[0] == currentPosition[0] && position[1] == currentPosition[1]));
            if (foundPosition) {
                found = true;
            }
            return found;
        })

    }

    exec() {
        this.bitMapArr.forEach((row, rowIndex) => {
            row.forEach((bit, colIndex) => {
                if (bit === 0) {
                    const currentPosition = [rowIndex, colIndex];
                    if (!this.checkIfCurrentPositionExist(currentPosition)) {
                        this.holesArr.push({
                            holeNumber: this.holesArr.length + 1,
                            positions: [currentPosition]
                        });
                        this.findNeighbor(Direction.root, currentPosition);
                    }
                }
            });
        });
        console.log(this.holesArr.length)
        this.holesArr.forEach(hole => {
            console.log(hole.positions)
        });
        return this.holesArr.length
    }
}
enum Direction {
    up = 'up',
    down = 'down',
    right = 'right',
    left = 'left',
    root = 'root'
}

interface Hole {
    holeNumber: number;
    positions: number[][]
}

main.ts文件

import {CountBitMapHoles} from './bitmap-holes'

const line = ['1010111', '1001011', '0001101', '1111001', '0101011']
function main() {
    const countBitMapHoles = new CountBitMapHoles(line)
    countBitMapHoles.exec()
}

main()

0

使用表格查找和位运算可能会带来一些优势。

例如,可以在256个元素的表格中查找8个像素的整行,因此可以通过单次查找得到1xN场地中的孔的数量。然后,可能会有一个256xK元素的查找表,其中K是前一行中孔配置的数量,包含完整孔的数量和下一个孔的配置。这只是一个想法。


我刚开始构建我的2^64查找表,但是我已经用完了本年度的RAM预算8^) - florin
使用分布式计算!=) - Vovanium
我发现这个谜题非常有趣,花了一些空闲时间用查找和位运算制作了一个“逐行”算法的草图,仅使用560字节的表格(针对8位宽度的模式)。 这是代码:http://dl.dropbox.com/u/13343791/holecount2.c 如果代码没有很好地文档化,请见谅。 - Vovanium

-1

function BitmapHoles(strArr) { 
    let returnArry = [];
    let indexOfZ = [];
    let subarr;
     for(let i=0 ; i < strArr.length; i++){
       subarr = strArr[i].split("");
      let index = [];
      for(let y=0 ; y < subarr.length; y++){
        if(subarr[y] == 0)
        index.push(y);
        if(y == subarr.length-1)
        indexOfZ.push(index);
      }
    }
    for(let i=0 ; i < indexOfZ.length; i++){
        for(let j=0; j<indexOfZ[i].length ; j++){
          if(indexOfZ[i+1] && (indexOfZ[i][j]==indexOfZ[i+1][j] || indexOfZ[i+1].indexOf(indexOfZ[i][j])))
          returnArry.indexOf(strArr[i]) < 0 ? returnArry.push(strArr[i]): false;
          if(Math.abs(indexOfZ[i][j]-indexOfZ[i][j+1])==1)
          returnArry.indexOf(strArr[i]) < 0 ? returnArry.push(strArr[i]): false;
        }
    }
    
      return returnArry.length; 
    
    }
       
    // keep this function call here 
    console.log(BitmapHoles(readline()));

如果您解释代码的工作原理以及它与问题的关系,那么这个答案可能会更有用。 - Cris Luengo

-1

function findHoles(map) {
    let hole = 0;

    const isHole = (i, j) => map[i] && map[i][j] === 0;

    for (let i = 0; i < map.length; i++) {
        for (let j = 0; j < map[i].length; j++) {
            if (isHole(i, j)) {
                markHole(i, j);
                hole++;
            }
        }
    }

    function markHole(i, j) {
        if (isHole(i, j)) {
            map[i][j] = 2;
            markHole(i, j - 1);
            markHole(i, j + 1);
            markHole(i + 1, j);
            markHole(i - 1, j);
        }
    }

    return hole;
}


3
如果你解释一下代码的工作原理以及它与问题的关系,那么这个答案可能会更有用。 - Cris Luengo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接