zoukankan      html  css  js  c++  java
  • 关于排列组合中组合结果

      最近程序里面需要一个组合的计算,于是写了一个组合类,没有实现排列,因为只要组合,所以只实现了组合,这里我们思想记录下来.

      本文里约定 C(n,m)即从n个中选m个的组合,其中n>=m,m>=0,n>0

      平时我们数据里学的组合一般都是问多少种情况比哪C(3,1)有几种情况,答案是3,不是我们排列出来的情况.而我这次写的主要是排列出组合情况,比如:C(3,1),输出结果
    1,0,0
    0,1,0
    0,0,1
    这样子的结果,当然结果我能输出来,有多少种情况我也能输出来,下面是思路

      1.刚开始的时候,我会想到用动态规划,以前做过n个字符全排,就是变成n-1个字符全排列加上最后一个字符的各种位置排列.但是这是组合,我目前知道的组合公式就是
    C(n,m)=n!/(m!(n-m)!)
    我在这个公式中找不到一步一步递的算法.

      2.在网上查询组合公式查到一个如下公式
    C(n,m)=C(n-1,m-1)+C(n-1,m)
    这个公式就有了递归关系了.
    即F(n,m)=F(n-1,m-1)+F(n-1,m)
    很大的一个问题算解决了,因为找到公式了.

      3.但是又一个比较困难的问题来了,怎么存储我的组合的结.可能上面公式还是有倾向于计算数量,但是我现在要的是各种排列.
    这个看对上面公式的理解了,如果只看到公式不理解,也能只编写出计算数量的方法,写不出组合的结果.
    下面看一下对公式的理解,为什么C(n,m)就等于C(n-1,m-1)加C(n-1,m),先看一下他们分别的意思
    C(n,m)从n个在取m个的组合情况
    C(n-1,m-1)从n-1个中取m-1个的组合情况,也就是从n-1个中选m-1个,另一个确定
    C(n-1,m)从n-1中取m个的组合情况,也就是都从n-1个中先
    那么翻译一下就是
    从n个中选m个的组合等于 某个选定,某个不选的情况,
    某个选定即还要在n-1个中选m-1个
    某个没有选即要在n-1个中选m个
    所以
    C(n-1,m-1)+C(n-1,m)
    理解了上面公式,我们就好设计存储结果构了,这都是分两种情况,一种某个选定,一种某个没选,后面组给也这些继续分下去.比如:
    C(5,2)=C(4,2)+C(4,1)
    =C(3,2)+C(3,1)+C(4,1)
    =C(2,2)+C(2,1)+C(3,1)+C(4,1) //其实也可以到这种结构算最后了因为C(x,1)也是很好计算的
    =C(2,2)+C(1,1)+C(1,0)+C(2,1)+C(2,0)+C(3,1)+C(3,0)
    =.... 最后全是C(x,x) C(x,0) 这种结构 我代码里是以这种结构计算的
    我最后是以树的结构来构造这种结果的,比如从根结点分两种情况0,1,后面又继续分,因为每次都是两种情况,所以是二叉树,但是不是平衡的.比哪C(3,1)中分成 1,0,0和0,1,0和0,0,1左枝表示1,右枝表示0.

    4.最后来看一下核心的方法

        private void GetCnm(Node node, int n, int m)
        {
            if (n < 0 || m < 0)
                throw new Exception("n,m的值必须大于零");
            if (n < m)
                throw new Exception("n的值必须大于等于m的值");
    
            //最后分解后都是 Cn0 或 者Cnn
            if (n == m)
            {
                ReplicateNode(node, n, LEFT);
            }
            else if (m == 0)
            {
                ReplicateNode(node, n, RIGHT);
            }
            else
            {
                Node left = new Node() { Data = LEFT };
                Node right = new Node() { Data = RIGHT };
                node.Left = left;
                left.Parent = node;
    
                node.Right = right;
                right.Parent = node;
    
                GetCnm(node.Left, n - 1, m - 1);
                GetCnm(node.Right, n - 1, m);
            }
        }

    递归当中比较重要的就是找到递归的出口条件,这个方法中就是n==m和m==0的情况,其实上面也说了可以用n==m和m=1的情况来做出口条件下面就是分解成两个方法递归调用

      5.还有一块比较重要的就是树的遍历了,要打印出来就要遍历树,这次我是用深度优先遍历完成的.
      最后说一下广度优先和深度优先遍历的关键东西,我以前也有代码写深度优先遍历,我拿出来发现完全没有参考价值了,以前是写的一个图的深度优先遍历,也全完不是复制过来就能用,而是完全从新写了一个,当然调试费了一些时间,但是我觉得这两个遍历的关键点我是抓住了的,所以即使不考代码也能写出来,可能调试的时候会费时,但是思想在问题不是很大.
    考,说了这么多还是没说到我认为的关键点,其实我认为这两个关键点就是queue和stack,就是队列和堆栈,广度优先遍历就是队列的应用,把子层加入队列,再从队列头做相同的操作.而深度优先则是堆栈的应用,把自己访问到的结点压入堆栈,一直压,一直压,直到叶子再弹出,弹出后也做以前相同的操作.这里还有一个点小技巧,可以用两个栈,一个用来存储是否访问过了,也就是一个出栈,就压入另一个栈.这样方便后面的回退操作判断.

    这次算写得长一点的文章了......加油.

  • 相关阅读:
    4.FFMPEGAVFrame
    从0到1实现Web端H.265播放器:视频解码篇
    6.AVCodecContext和AVCodec
    7.SwrContext音频重采样使用
    5.AVStream和AVCodecParameters
    3.AVPacket使用
    FFmpeg:AVFrame结构体分析
    9.下载ffmpeg、使QT支持同时编译32位和64位
    1.ffmpeg基础经常使用知识
    2.AVFormatContext和AVInputFormat
  • 原文地址:https://www.cnblogs.com/gw2010/p/3461419.html
Copyright © 2011-2022 走看看