基于补集最大值思想的排列组合

Posted uu6crella

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于补集最大值思想的排列组合相关的知识,希望对你有一定的参考价值。

基于补集最大值思想的排列组合。

就组合数列CombList写了文档,感觉跟ruby写的代码本身差不多。

组合数添加:文档
假设为C(n, m)模式。当前列表为@list,有m个元素。
@model是1~n所有整数构成的数组
delta数组是@model减去@list的所有元素。
@list从后往前找到第一个元素A,其位置为ji,使得A<delta数组内的最大值B(应该是delta数组的最后一位)

若存在B,则找出delta数组内所有比A大的元素集合中的最小值C。
把@list的ji位置替换为C元素。
对于更新后的@list从位置0到ji的子集D,获取@model-D的数组中所有大于C的元素组成的集合E。
如果E为空,则@list从ji到(m-1)位置上所有的数是n、n-1、n-2 ...,此时@list符合条件。直接跳出流程
如果E不为空,则把@list从(ji+1)到(m-1)位置,依次设为E[0]、E[1]..。

若不存在B, 则继续从后往前找到对应的元素A

如果整个@list都找不到符合条件的A,则@list已是本次组合中的最大倒序排列: [n, n-1, n-2 ...]。直接跳出流程。

# 文档结束

调用代码: 

 1 require ./permlist.rb
 2 a = Time.now
 3 # 排列数Anm, n=13, m=7,在13个元素中找出任意集合A,满足A的元素个数为7
 4 #~ list = Sanla::PermList.new(13, 7) # => list.pos=8648640=13!/6!,时间应该在34秒以内
 5 list = Sanla::PermList.new(8, 4) 
 6 # 组合数Cnm, n=7, m=3,在7个元素中找出任意非重复集合A,满足A的元素个数为3
 7 #~ list = Sanla::CombList.new(7, 3) 
 8 loop do
 9   r = list.get
10   
11   if (r == false)
12     break
13   end
14   #~ puts r.inspect
15   #~ puts "获取结果:#{r}"#, pos=#{list.pos}"
16   #~ fio.puts "获取结果:#{r}"
17 end
18 #~ fio.close
19 b = Time.now
20 #~ puts ‘waiting..‘; gets
21 puts list.pos
22 puts "time: #{(b-a).to_i}"

 

生成数列的模块代码:

# ruby API说明
# (1..n).to_a 表示从1到n之间(含边界)所有整数组成的数组
# array.empty? 数组是否为空, 返回true或false
# array.detect{|x| bool} 从数组中找出第一个满足bool为true的元素; 若不存在,返回nil
# array[j, k]表示从array[j]位置开始取第1个数,往后一直取数直到取了k个,然后返回这k个元素组成的数组

module Sanla
  class PermList
    # 排列数
    attr_reader :pos
    def initialize(n, m)
      if (m > n || m*n == 0)
        raise StandardError, "求排列数时参数错误: n=#{n}, m=#{m}"
      end
      @n = n; @m = m
      @list = (1..m).to_a
      @model = (1..n).to_a
      #~ @list[-1] -= 1 # 方便第一次输出get()
      @pos = 0
    end
    
    def get

      if (@list == false)
        return false
      end
      if (@pos==0)
        @pos += 1
      else
      
        self.add()
        if (@list != false)
          @pos += 1
        end
      end
      
      return @list

    end
    
    def add
      
      ji = @m-1
      while (ji>=0)
        # 获取数组开始到当前位的所有元素
        cur = @list[ji]
        array2 = @list[0, (ji+1)]
        # 获取模型中减去array2剩下的元素
        # 方式是把指定位置的元素设置为nil,再使用Array.compact
        delta = get_delta(array2)
        bigger = delta.detect{|x| x>cur}
        # 筛选出delta里比当前位大的元素
        # 必须确保delta是按从小到大排列
        if bigger.nil?
          # 当前位已是最大; 交由下一次循环操作
        else
          # 设置当前位为bigger;
          @list[ji] = bigger
          array2[-1] = bigger
          delta = get_delta(array2)
          # @list剩下的位置依次填充delta的前几个元素
          ((ji+1)..(@m-1)).each do |id|
            @list[id] = delta[id-(ji+1)]
          end
          # 跳出循环, 直接返回
          return
        end
        #~ puts "检查ji=#{ji}, list=#{@list}"
        ji -= 1
      end
            
      #~ puts "排列数组已是最大! #{@list}"
      @list = false
      return
        #~ puts "@list=#{@list}, waiting.."; gets
      #~ end
    end
    
    def get_delta(array2)
      delta = @model.clone
      array2.each do |x|
        delta[x-1] = nil
      end
      delta = delta.compact
      return delta
    end
    
    
  end


  class CombList
    # 组合数
    attr_reader :pos
    def initialize(n, m)
      if (m > n || m*n == 0)
        raise StandardError, "求排列数时参数错误: n=#{n}, m=#{m}"
      end
      @n = n; @m = m
      @list = (1..m).to_a
      @model = (1..n).to_a
      @pos = 0
    end
    
    def get

      if (@list == false)
        return false
      end
      if (@pos==0)
        @pos += 1
      else
      
        self.add()
        if (@list != false)
          @pos += 1
        end
      end
      
      return @list

    end
=begin
  组合数添加:文档
  假设为C(n, m)模式。当前列表为@list,有m个元素。
  @model是1~n所有整数构成的数组
  delta数组是@model减去@list的所有元素。
  @list从后往前找到第一个元素A,其位置为ji,使得A<delta数组内的最大值B(应该是delta数组的最后一位)
  
    若存在B,则找出delta数组内所有比A大的元素集合中的最小值C。
    把@list的ji位置替换为C元素。
    对于更新后的@list从位置0到ji的子集D,获取@model-D的数组中所有大于C的元素组成的集合E。
    如果E为空,则@list从ji到(m-1)位置上所有的数是n、n-1、n-2 ...,此时@list符合条件。直接跳出流程
    如果E不为空,则把@list从(ji+1)到(m-1)位置,依次设为E[0]、E[1]..。
    
    若不存在B, 则继续从后往前找到对应的元素A
    
  如果整个@list都找不到符合条件的A,则@list已是本次组合中的最大倒序排列: [n, n-1, n-2 ...]。直接跳出流程
 
=end   
    def add
      
      delta = get_delta(@list)
      #~ puts "add()开始时list=#{@list}, delta=#{delta}"; gets
      # 从@list的后往前比较,获取第一个位置A使得A<delta内的某个数
      # 假设delta的最大值在[-1]位
      ji = @m-1
      while (ji>=0)
        cur = @list[ji]
        if (cur < delta[-1])
          # 获取delta数组内大于cur的数中的最小的一个
          delta.each do |x|
            if (x>cur)
              @list[ji] = x
              break
            end
          end
          # 再次获取delta数组
          cur = @list[ji]
          #~ puts "中间 list=#{@list}"
          array2 = @list[0, ji]
          delta = get_delta(array2).select{|x| x>cur}
          if delta.empty?
            # 从ji到@list末尾构成的数组已经是组合数里的较大数的集合了
          else
            ((ji+1)..(@m-1)).each do |x|
              #~ puts "delta=#{delta}, 获取id为#{x-(ji+1)}"
              @list[x] = delta[x-(ji+1)]
            end
          end
          #~ puts "ji=#{ji}, list=#{@list}"; #gets
          return
        end
        ji -= 1
      end
            
      #~ puts "组合数组已是最大! #{@list}"
      @list = false
      return
        #~ puts "@list=#{@list}, waiting.."; gets
      #~ end
    end
    
    def get_delta(array2)
      # 返回@model数组减去array2的元素的其他元素构成的数组
      delta = @model.clone
      begin
        array2.each do |x|
          delta[x-1] = nil
        end
      rescue NoMethodError => ser
        puts ser.message, "array2=#{array2}"
        exit
      end
      delta = delta.compact
      return delta
    end

  end
end

 

以上是关于基于补集最大值思想的排列组合的主要内容,如果未能解决你的问题,请参考以下文章

POJ1644状态转移的思想——排列组合

Gym10081 A - Arcade Game -康托展开全排列组合数变成递推的思想

用DFS 解决全排列问题的思想详解

HDU 6035 Colorful Tree(补集思想+树形DP)

递归实现组合型指数型排列型 枚举

20191209-八大排序之基数排序