組合數學—排列組合


注:原創不易,轉載請務必註明原做者和出處,感謝支持!app

一 寫在開頭

這個學期學習了組合數學這門課程,趁如今有時間作一下總結。整個課程的脈絡以下圖:學習

1.1 本文內容

本文的內容爲排列組合,內容分解圖以下。編碼

二 計數法則

2.1 加法法則(分類加法計數)

假設有兩個互斥的事件\(A\)與事件\(B\),事件\(A\)\(m\)種產生方式,事件\(B\)\(n\)種產生方式,則事件\(A\)或事件\(B\)\(m+n\)種產生方式。spa

2.2 乘法法則(分步乘法計數)

假設有兩個相互獨立的事件\(A\)與事件\(B\),事件\(A\)\(m\)種產生方式,事件\(B\)\(n\)種產生方式,則事件\(A\)和事件\(B\)\(m \cdot n\)種產生方式。code

2.3 一一對應法則

假設事件\(A\)的產生方式與事件\(B\)的產生方式一一對應,則事件\(A\)的產生方式數與事件\(B\)產生的方式數相等。blog

要對\(A\)集合計數,但直接計數有困難,因而可設法構造一易於計數的\(B\),使得\(A\)\(B\)一一對應。事件

三 排列與組合的定義及其計算公式

3.1 排列

  • 定義:從\(n\)個不一樣的元素中,取\(r\)個不重複的元素,按次序排列,稱爲從\(n\)中取\(r\)個的排列。
  • 排列數記爲\(p(n, r)\)
  • 排列數\(p(n, r)\)的計算方法:至關因而從\(n\)個不一樣的球中取出\(r\)個,依次放入\(r\)個不一樣的盒子當中。所以,第一個盒子有\(n\)種選擇,第二個盒子有\(n-1\)種選擇,......,第\(r\)個有\(n-(r-1)\)種。所以可得排列數的計算公式以下:

\[ \begin{equation} \begin{split} P(n, r) &= n \cdot (n-1) \cdot (n-2)......(n-r+2)(n-r+1)\\ &= n! \verb|/| (n-r)! \end{split} \end{equation} \]數學

特別地,當\(r=n\)時,稱爲全排列,\(p(n, n)=n!\)string

3.2 組合

  • 定義:從\(n\)個不一樣元素中取\(r\)個元素,且不考慮順序,稱爲從\(n\)箇中取\(r\)個的組合。
  • 組合數用\(c(n,r)\)或者\(\tbinom{n}{r}\)來表示
  • 與排列不一樣的是,組合只是至關於從\(n\)個球中選擇\(r\)個組合在一塊兒便可,與排列相比,無需\(r\)個球構成一個全排列這麼一個操做。所以,組合數的計算至關因而在排列數的基礎上除去一個\(r\)的全排列\(r!\)便可。因此

\[ \begin{equation} \begin{split} C(n,r) &= P(n, r) \verb|/| r!\\ &= n! \verb|/| \left[ (n-r)! \cdot r! \right] \end{split} \end{equation} \]

四 圓排列與項鍊排列

4.1 圓排列

  • 定義:從\(n\)個不一樣的元素中取\(r\)個元素排列在一個圓環上的排列
  • 圓排列數用\(Q(n,r)\)來表示。由於圓排列的緣故,每\(r\)個首尾相連順序同樣的排列都只能算一種,以下圖所示,因此,圓排列數至關因而在排列數的基礎上除去\(r\)。因此

\[ Q(n, r) = P(n, r) \verb|/| r \]

\[ Q(n, n) = P(n, n) \verb|/| n = (n-1)! \]

4.2 項鍊排列

  • 定義:項鍊排列如同項鍊通常,在圓排列的基礎上,逆時針方向和順時針方向的放置各個數是同一個排列。
  • 所以項鍊排列的排列數爲\(Q(n, r) \verb|/| 2 = P(n, r) \verb|/| 2r\)

五 可重排列與可重組合

5.1 可重排列

可重排列的定義:
設有\(n\)種不一樣的物體\(a_1,a_2,...,a_n\)
第一種物體\(a_1\)有相同的\(k_1\)個,
第二種物體\(a_2\)有相同的\(k_2\)個,
......
第n中物體\(a_n\)有相同的\(k_n\)個,
從這\(n\)中物品裏取\(r\)個物品進行排列,稱爲\(r\)可重排列。

依據\(r\)\(k_i\)的數量狀況能夠分爲如下三種狀況:

  • \(r = k_1 + k_2 + ... + k_n\)

  • \(k_1 \ge r, k_2 \ge r, ... , k_n \ge r\)或者\(k_1 = \infty, k_2 = \infty, ..., k_n = \infty\)

  • 存在\(k_i < r\)

那麼對於上述三種狀況,如何計算它們的排列數呢?

對於狀況1,設\(S =\left \{ k_1 \cdot a_1, k_2 \cdot a_2, ..., k_n \cdot a_n\right \}\),當\(k_1 + k_2 + ... + k_n = r\)時,從\(n\)種物品中取\(r\)個物品的全體排列數用\(P(r;k_1, k_2,...,k_n)\)\(\tbinom{r}{k_1 k_2 ... k_n}\)表示。則

\[ P(r;k_1, k_2, ..., k_n) = r! \verb|/| (k_1 ! \cdot k_2 ! \cdot ... \cdot k_n !) \]

證實過程略

對於狀況2,有
\[ P(r; a_1 \cdot \infty, a_2 \cdot \infty, ..., a_n \cdot \infty) = n^r \]
證實過程略。

對於狀況3,沒有一個現成的公式能夠計算其排列數。

5.2 可重組合

可重組合的定義:
\(n\)種不一樣的物品,構成集合\(S =\left \{ k_1 \cdot a_1, k_2 \cdot a_2, ..., k_n \cdot a_n\right \}\),從這\(n\)種物品中取出\(r\)個物品的組合,稱爲\(r\)可重組合。對於可重組合能夠分紅如下兩種特殊狀況:

  • \(k_i > r~(i = 1, 2, ..., n)\)
  • \(a_1 ,a_2, ..., a_n\)至少出現一次的組合

對於狀況1,有
\[ C(r; a_1 \cdot \infty, a_2 \cdot \infty, ..., a_n \cdot \infty) = \tbinom{r + n - 1}{r} \]

簡要證實:
第1步:問題至關於\(r\)個相同的球放入\(n\)個互異的盒子,每一個盒子的數目能夠不一樣,求總的方案數

第2步:上述問題又能夠轉換爲\(r\)個相同的球與\(n-1\)個相同的盒壁的排列問題

所以,排列數爲:
\[ (r+n-1)! \verb|/| (r! \cdot (n - 1)!) = \tbinom{n+r-1}{r} \]

對於狀況2,\(a_1 ,a_2, ..., a_n\)至少出現一次的組合數爲\(\tbinom{r-1}{r-n}\)

簡要證實:
由於\(a_1 ,a_2, ..., a_n\)至少出現一次,因此,先取出\(a_1 ,a_2, ..., a_n\)各一個,剩下的問題就轉化爲從\(n\)中取\(r-n\)個的可放回組合問題。所以,組合數爲:
\[ \tbinom{r-n+n-1}{r-n} = \tbinom{r-1}{r-n} \]

六 若干組合等式

6.1 Stirling近似公式

Stirling公式給出了一個求\(n!\)的近似公式。

\[ n! \approx \sqrt{2n\pi} (\frac{n}{e})^n \]

6.2 Pascal公式

\[ \tbinom{n}{k} = \tbinom{n-1}{k} + \tbinom{n-1}{k-1} \]

該公式至關直觀,證實也至關簡單:
\(S\)\(n\)個元素的集合,任取一個元素用\(x\)表示,則\(S\)\(k-\)組合的集合能夠劃分爲不包含\(x\)\(k-\)組合和包含\(x\)\(k-\)組合,因而
\[ \tbinom{n}{k} = \tbinom{n-1}{k} + \tbinom{n-1}{k-1} \]

6.3 二項式定理及二項式係數

\(n\)是正整數,對任意的\(x\)\(y\)有:
\[ (x+y)^n = \sum_{k=0}^n \tbinom{n}{k} x^k y^{n-k} \]

其中\(\tbinom{n}{k}\)爲二項式係數。

根據上述二項式定理,有如下推論:

\(y=1\)有:
\[ (1+x)^n = \sum_{k=0}^n \tbinom{n}{k} x^k = \tbinom{n}{0} + \tbinom{n}{1}x + ... + \tbinom{n}{n}x^n \]

\(y = 1, x = -x\)有:
\[ (1-x)^n = \sum_{k=0}^n (-1)^k \tbinom{n}{k}x^k = \tbinom{n}{0} - \tbinom{n}{1}x + ... + \tbinom{n}{n}(-1)^k x^n \]

\(x = 1, y = 1\)有:
\[ \tbinom{n}{0} + \tbinom{n}{1} + ... + \tbinom{n}{n} = 2^n \]

\(x = -1, y = 1\)有:
\[ \tbinom{n}{0} - \tbinom{n}{1} + \tbinom{n}{2} ... + (-1)^n \tbinom{n}{n} = 0 \]

6.4 多項式定理

\(n\)是正整數,則對一切實數\(x_1, x_2, ..., x_m\)有:
\[ \begin{equation} \begin{split} (x_1 + x_2 + ... + x_m)^n &= \sum_{n_1 + n_2 + ... + n_m = n} \frac{n!}{n_1 ! \cdot n_2 ! ... n_m !}x_1^{n_1}x_2^{n_2}...x_m^{n_m}\\ &= \sum_{n_1 + n_2 + ... + n_m = n}\tbinom{n}{n_1 n_2 ... n_m}x_1^{n_1}x_2^{n_2}...x_m^{n_m} \end{split} \end{equation} \]
其中\(\tbinom{n}{n_1 n_2 ... n_m}\)爲多項式係數。

6.5 牛頓二項式定理

\(\alpha\)是一個實數。則對於全部知足$0 \leq \vert x \vert < \vert y \vert \(的\)x\(和\)y$有:

\[ (x+y)^{\alpha} = \sum_{k=0}^{\infty} \tbinom{\alpha}{k} x^k y^{\alpha - k} \]
其中
\[ \tbinom{\alpha}{k} = \begin{cases} 0 & k < 0\\ 1 & k = 0\\ \frac{\alpha (\alpha - 1) ... (\alpha - k + 1)}{k!} & k > 0 \end{cases} \]

6.6 組合恆等式

下面是一些組合恆等式,證實過程略。
\[ \tbinom{n}{r} = \tbinom{n}{n-r} \]

\[ \tbinom{n}{1}^2 + 2\tbinom{n}{2}^2 + ... + n\tbinom{n}{n}^2 = n \cdot \tbinom{2n-1}{n-1} \]

\[ \tbinom{n}{r} = \tbinom{n-1}{r-1} + \tbinom{n-2}{r-1} + \tbinom{n-3}{r-1} + ... + \tbinom{r-1}{r-1} \]

\[ \tbinom{n}{l}\tbinom{l}{r} = \tbinom{n}{r}\tbinom{n-r}{l-r} \neq \tbinom{n}{r} \]

\[ \tbinom{n}{0} + \tbinom{n}{1} + \tbinom{n}{2} + ... + \tbinom{n}{n} = 2^n \]

\[ \tbinom{m+n}{r} = \tbinom{m}{0}\tbinom{n}{r} + \tbinom{m}{1}\tbinom{n}{r-1} + ... + \tbinom{m}{r}\tbinom{n}{0} \]

\[ \tbinom{n}{k} = \frac{n}{n-k}\tbinom{n-1}{k} \]

\[ \tbinom{n}{k} = \frac{n-k+1}{k}\tbinom{n}{k-1} \]

\[ \sum_{k=0}^m \tbinom{m}{k}\tbinom{n}{l+k} = \tbinom{m+n}{m+l} \]

七 生成算法

生成算法也叫構造算法,生成算法完成的功能是將全部知足要求的排列或組合無重複,無遺漏地枚舉出來

7.1 排列生成算法

排列的構造算法主要有直接法,字典序法,序列法,逆序數法,鄰位對換法等等,接下來要介紹其中的一種:字典序法。

字典序法:從天然順序123...n開始,按照字典序依次構造集合{1, 2, ..., n}的全部全排列,由一個排列構造下一個排列的算法以下:

  • \(p_1 p_2 ... p_n\)從右向左掃描,找出比右鄰數字小的第1個數字\(p_i\)
  • \(p_1 p_2 ... p_n\)從右向左掃描,找出比\(p_i\)大的第一個數\(p_j\)
  • \(p_i\)\(p_j\)互換獲得\(b_1 b_2 ... b_n\)
  • \(b_1 b_2 ... b_n\)中的\(b_{i+1}\)\(b_n\)部分的順序逆序,獲得\(b_1 b_2 ... b_n ... b_{i+1}\)即爲下一個排列

該算法流程很清晰,能夠用C++實現以下:

void generate_permutations(vector<string> &v, int n)
{
    if (n < 1)
        return ;

    // 構建初始和結束排列
    string first, last;
    for (int i = 1; i <= n; i++){
        first.push_back(char('0' + i));
        last.push_back(char('0' + n - i + 1));
    }
    v.push_back(first);

    // 依次產生下一個排列並加入向量中
    string next, current = first;
    while (current != last)
    {
        // 尋找要對換的兩個數的位置pi和pj
        decltype(current.length()) i, j, pi, pj;
        for (i = current.length() - 1; i >= 1; i--)
            if (current[i-1] < current[i]){
                pi = i - 1;
                break;
            }
        for (i = current.length() - 1; i > pi; i--)
            if (current[i] > current[pi]){
                pj = i;
                break;
            }

        // 對換
        next = current;
        char tmp = next[pi];
        next[pi] = next[pj];
        next[pj] = tmp;

        // 翻轉
        for (i = pi + 1, j = next.length() - 1; i < j; i++, j--){
            char tmp = next[i];
            next[i] = next[j];
            next[j] = tmp;
        }
        v.push_back(next);
        current = next;
    }
}

7.2 組合生成算法

組合的構造算法主要有字典序法,二進制編碼法等等,這裏介紹前一種算法。算法的詳細過程敘述以下:
從{1, 2, ..., n}中取r-組合表示爲\(C_1 C_2 ... C_r\),令\(C_1 < C_2 < ... < C_r\),其中有\(C_i \leq (n-r+i), i = 1, 2, ..., r\)
則生成後續組合的規則以下

  1. \(C_1 C_2 ... C_r\)從右到左掃描,找出第一個知足\(C_i < (n-r+i)\)\(i\)
  2. \(C_i \leftarrow C_i + 1\)
  3. \(C_j \leftarrow C_{j-1} + 1, j=i+1, i+2, ..., r\)

一樣,上述算法能夠用C++實現以下:

void generate_combinations(vector<string> &v, int n, int r)
{
    if ((r < 1) || (r > n))
        return ;

    // 構建初始和結束組合
    string first, last;
    for (int i = 0; i < r; i++){
        first.push_back(char('0' + i + 1));
        last.push_back(char('0' + n - r + i + 1));
    }
    v.push_back(first);

    // 依次產生下一個組合並加入向量中
    string next, current = first;
    while (current != last)
    {
        decltype(current.length()) i, j, pi;
        for (i = current.length() - 1; i >= 0; i--)
            if ((current[i] - '0') < (n - r + i + 1)){
                pi = i;
                break;
            }

        next = current;
        next[pi] = next[pi] + 1;
        for (j = pi + 1; j < r; j++)
            next[j] = next[j-1] + 1;

        v.push_back(next);
        current = next;
    }
}
相關文章
相關標籤/搜索