最小化矩阵中的块

时间:2013-01-17 17:04:39

标签: algorithm

假设我有以下矩阵:

Original matrix

矩阵可以分解成块,这样每个块必须具有相同的列数,其中该行的值被标记为true。

例如,以下块有效:

Valid chunk example 1

这意味着行不必是连续的。

列也不必是连续的,因为以下是有效的块:

Valid chunk example 2

但是,以下内容无效:

Invalid chunk example

那就是说,什么算法可用于选择块,以便在找到所有块时使用最少数量的块?

鉴于上面的示例,正​​确的解决方案是(具有相同颜色的项目代表有效的块):

Solution to chunking problem 1

在上面的示例中,三个是可以分解为最小数量的块。

请注意,以下内容也是有效的解决方案:

Solution to chunking problem 2

对于解决方案没有偏好,实际上,只是为了获得最少数量的块。

我想过使用相邻单元格进行计数,但这并不能解释列值不必是连续的事实。

我认为关键在于找到具有约束条件的最大区域的块,删除这些项目,然后重复。

采用这种方法,解决方案是:

Solution to chunking problem 3

但是如何遍历矩阵并找到最大的区域是我的目的。

另请注意,如果您想在操作期间重新洗牌行和/或列,这是一个有效的操作(为了找到最大的区域),但我想你只能在删除后才能这样做矩阵中最大的区域(在找到一个区域并移动到下一个区域之后)。

3 个答案:

答案 0 :(得分:2)

你在真相表上正在做circuit minimization。对于4x4真值表,您可以使用K mapQuine-McCluskey algorithm是一种可以处理更大真值表的概括。

请记住,问题是NP-Hard,因此根据真值表的大小,这个问题可能会迅速增加到难以处理的大小。

答案 1 :(得分:0)

我提出的解决方案相当简单,但非常耗时。

它可以分为4个主要步骤:

  1. 找到矩阵中所有现有的模式,
  2. 找到这些模式的所有可能组合,
  3. 删除所有不完整的模式集,
  4. 扫描剩余列表以获取具有最少元素数量的集合
  5. 首先,下面的算法适用于列或行主矩阵。我选择了列进行解释,但只要在整个过程中保持一致,您可以在方便时将其换成行。

    答案中的示例代码在OCaml中,但不使用该语言的任何特定功能,因此应该很容易移植到其他ML方言。

    第1步:

    每列可以看作是一个位向量。观察一个模式(你在问题中称为块)可以通过交叉(即)所有列,或构成它的所有行,甚至组合来构造。因此,第一步实际上是生成行和列的所有组合(矩阵的行和列的powerset,如果你愿意),同时交叉它们,并过滤掉重复项。

    我们考虑矩阵数据类型的以下接口:

     module type MATRIX = sig
        type t
        val w : int (* the width of the matrix *)
        val h : int  (* the height ........             *)
        val get : t -> int -> int -> bool  (* cell value getter *)
    
    end
    

    现在让我们来看看这一步的代码:

    let clength = M.h
    let rlength = M.w
    
    (* the vector datatype used throughought the algorithm
       operator on this type are in the module V *)
    type vector = V.t
    
    (* a pattern description and comparison operators *)
    module Pattern = struct
        type t = {
            w : int; (* width of thd pattern *)
            h : int; (* height of the pattern *)
            rows : vector; (* which rows of the matrix are used *)
            cols : vector; (* which columns... *)
        }
        let compare a b = Pervasives.compare a b
        let equal a b = compare a b = 0
    end
    (* pattern set : let us store patterns without duplicates *)
    module PS = Set.Make(Pattern)
    
    (* a simple recursive loop on @f @k times *)
    let rec fold f acc k =
        if k < 0 
        then acc
        else fold f (f acc k) (pred k)
    
    (* extract a column/row of the given matrix *)
    let cr_extract mget len =
        fold (fun v j -> if mget j then V.set v j else v) (V.null len) (pred len)
    
    let col_extract m i = cr_extract (fun j -> M.get m i j) clength
    let row_extract m i = cr_extract (fun j -> M.get m j i) rlength
    
    (* encode a single column as a pattern *)
    let col_encode c i =
        { w = 1; h = count c; rows = V.set (V.null clength) i; cols = c }
    
    let row_encode r i =
        { h = 1; w = count r; cols = V.set (V.null rlength) i; rows = r }
    
    (* try to add a column to a pattern *)
    let col_intersect p c i =
        let col = V.l_and p.cols c in
        let h = V.count col in
        if h > 0 
        then
            let row = V.set (V.copy p.rows) i in
            Some {w = V.count row; h = h; rows = row; clos = col}
        else None
    
    let row_intersect p r i =
        let row = V.l_and p.rows r in
        let w = V.count row in
        if w > 0
        then
            let col = V.set (V.copy p.cols) i in
            Some { w = w; h = V.count col; rows = row; cols = col }
        else None
    
    let build_patterns m =
        let bp k ps extract encode intersect =
            let build (l,k) =
                let c = extract m k in
                let u = encode c k in
                let fld p ps =
                    match intersect p c k with
                          None         -> l
                        | Some npc -> PS.add npc ps
                 in
                 PS.fold fld (PS.add u q) q, succ k
            in
            fst (fold (fun res _ -> build res) (ps, 0) k)
        in
        let ps = bp (pred rlength) PS.empty col_extract col_encode col_intersect in
        let ps = bp (pred clength) ps row_extract row_encode row_intersect in
        PS.elements ps
    

    V模块必须符合整个算法的以下签名:

    module type V = sig
        type t
        val null : int -> t  (* the null vector, ie. with all entries equal to false *)
        val copy : t -> t (* copy operator *)
        val get : t -> int -> bool (* get the nth element *)
        val set : t -> int -> t (* set the nth element to true *)
        val l_and : t -> t -> t (* intersection operator, ie. logical and *)
        val l_or : t -> t -> t (* logical or *)
        val count : t -> int (* number of elements set to true *)
        val equal : t -> t -> bool (* equality predicate *)
    end
    

    第2步:

    组合模式也可以看作是一个powerset结构,但有一些限制:有效的模式集可能只包含不重叠的模式。如果两个模式都包含至少一个公共矩阵单元,则后者可以被定义为真。 使用上面使用的模式数据结构,重叠谓词非常简单:

    let overlap p1 p2 =
        let nullc = V.null h
        and nullr = V.null w in
        let o v1 v2 n = not (V.equal (V.l_and v1 v2) n) in
        o p1.rows p2.rows nullr && o p1.cols p2.cols nullc
    

    模式记录的colsrows表示矩阵中的哪些坐标包含在模式中。因此,两个字段上的逻辑和将告诉我们模式是否重叠。

    为了在模式集中包含模式,我们必须确保它不与模式的任何模式重叠。

    type pset = {
        n : int; (* number of patterns in the set *)
        pats : pattern list;
    }
    
    let overlap sp p =
        List.exists (fun x -> overlap x p) sp.pats
    
    let scombine sp p =
        if overlap sp p
        then None
        else Some {
            n = sp.n + 1;
            pats = p::sp.pats;
        }
    
    let build_pattern_sets l =
        let pset l p = 
            let sp = { n = 1; pats = [p] } in
            List.fold_left (fun l spx -> 
                match scombine spx p with
                    None -> l
                 | Some nsp -> nsp::l
            ) (sp::l) l
        in List.fold_left pset [] l
    

    此步骤会产生大量集合,因此非常耗费内存和计算量。这当然是这个解决方案的弱点,但我还没有看到如何减少折叠。

    第3步:

    如果用它重建矩阵时,模式集是不完整的,我们不会获得原始模式。所以这个过程很简单。

    let build_matrix ps w =
        let add m p =
            let rec add_col p i = function
                | []     -> []
                | c::cs -> 
                    let c = 
                        if V.get p.rows i
                        then V.l_or c p.cols
                        else c
                    in c::(add_col p (succ i) cs)
            in add_col p 0 m
        in
        (* null matrix as a list of null vectors *)
        let m = fold (fun l _ -> V.null clength::l) [] (pred rlength) in
        List.fold_left add m ps.pats
    
    let drop_incomplete_sets m l =
        (* convert the matrix to a list of columns *)
        let m' = fold (fun l k -> col_extract m k ::l) [] (pred rlength) in
        let complete m sp =
            let m' = build_matrix sp in
            m = m'
        in List.filter (fun x -> complete m' x) l
    

    第4步:

    最后一步是选择元素数量最少的集合:

    let smallest_set l =
        let smallest ps1 ps2 = if ps1.n < ps2.n then ps1 else ps2 in
        match l with
            | []    -> assert false (* there should be at least 1 solution *)
            | h::t  -> List.fold_left smallest h t
    

    然后整个计算只是每个步骤的链接:

    let compute m =
       let (|>) f g = g f in
       build_patterns m |> build_pattern_sets |> drop_incomplete_sets m |> smallest_set
    

    备注

    上面的算法构造了一个powerset的powerset,并进行了一些有限的过滤。据我所知,还没有一种减少搜索的方法(如评论中提到的,如果这是一个NP难题,那就没有了)。

    此算法检查所有可能的解决方案,并正确返回最佳解决方案(使用多个矩阵进行测试,包括问题描述中给出的矩阵。

    关于您在问题中提出的启发式的一个简短评论:

    可以使用第一步轻松实现,删除找到的最大模式,然后递归。那会比我的算法更快地提出一个解决方案。但是,找到的解决方案可能不是最佳的。

    例如,请考虑以下矩阵:

    .x...
    .xxx
    xxx.
    ...x.
    

    中央4细胞块是最大的可以找到的,但使用它的集合总共包含5个模式。

    .1...
    .223
    422.
    ...5.
    

    然而,此解决方案仅使用4:

    .1...
    .122
    334.
    ...4.
    

    更新

    链接到我为此答案撰写的full code

答案 2 :(得分:0)

这个问题与Biclustering密切相关,其中有许多有效的算法(以及免费提供的实现)。通常,您必须指定您希望找到的群集K的数量;如果您不清楚K应该是什么,可以在K上进行二进制搜索。

如果双聚类没有重叠,你就完成了,否则你需要做一些几何体来将它们切成“块”。