排列組合


參考

https://www.cnblogs.com/graphics/archive/2011/07/14/2105195.html

https://zhuanlan.zhihu.com/p/79863106

https://zh.wikipedia.org/wiki/%E7%BB%84%E5%90%88%E6%95%B0%E5%AD%A6

我們在做算法題的時候,很多時候需要用到窮舉,排列組合,獲得所需的結果。然后再根據這個增加條件,慢慢減少判斷次數,就是動態規划。所以我們先弄清楚如何排列組合。

給定字符串s,求出s所有可能的子串。

int main()
{
    string a = "abcdef";
    string b = "acdfg";
    string c = "abc";
    int n = 1 << c.size();
    for (int i = 0; i < n; i++)
    {
        int j = i;
        string tmp;
        for (auto iter = c.rbegin(); iter != c.rend(); iter++)
        {
            if (j & 1)
            {
                tmp = *iter + tmp;
            }
            j = j >> 1;
        }
        cout << tmp << endl;
    }
    char inchar;
    cin >> inchar;
}

這個解法,就是根據每一個元素,都有兩種狀態,在子串中,或是不在子串中。那么一個n個字符串,就會有2^n個組合。用一個二進制進行判斷,每次右移1,個位置上是1的表示這次存在於子串。只不過這是倒敘的,需要反過來。

這里的局限性就是,只能保存int的32位的字符串,如果是64位,也只能保存64個字符的字符串。再長的字符串就不行了,但是我們考慮到64個字符的字符串的所有組合情況,已經是天文數字了,在實際情況中,我們也不會碰到。碰到之后肯定有其他的解法。

 

問題擴展,從n個元素中,選長度是m(m<=n)子串的組合。按照大家總結的規則,c(n,m)表示從n個字符串中取m個長度所有的子串情況,那么c(n,m)=c(n-1,m) + c(n-1, m-1)。解釋就是當我要判斷第n個元素加進來的子串情況時,只需要知道前n-1個子串的所有情況,再加上當前子串的情況,就可以得出最后的答案。前面n-1子串的情況,可以分為,第n個計算在內,那么就是c(n-1, m-1)和第n個不計算在內就是c(n-1,m)。

比如abc,那么選擇c(3,2)=c(3-1,2) + c(3-1, 2-1)。那么就是ab和a b,然后ab是不需要加c,a和b需要加c,就是ab ac bc。也就是計算第n個情況的時候,肯定需要先計算出n-1的情況,n-1的情況中包含了m個數,n就不用計算在內;包含了m-1的情況,就把m-1所有的情況都加上第n個元素。

struct MNODE
{
    string substr;
    MNODE* pnext;
    MNODE()
    {
        pnext = nullptr;
    }
};
MNODE* msubstr(string& srcstr, int index, int sublen)
{
    MNODE* tmpp = nullptr;
    MNODE* prep = nullptr;
    if (index >= sublen && sublen > 0)
    {
        if (sublen == 1)
        {
            for (int i = 0; i < index; i++)
            {
                MNODE* ftpn = nullptr;
                if (tmpp == nullptr)
                {
                    tmpp = new MNODE;
                    prep = tmpp;
                    ftpn = tmpp;
                }
                else
                {
                    ftpn = new MNODE;
                    prep->pnext = ftpn;
                    prep = ftpn;
                }
                ftpn->substr = srcstr[i];
            }
        }
        else if (sublen > 1)
        {
            MNODE* nsub = msubstr(srcstr, index - 1, sublen - 1);
            tmpp = nsub;
            while (nsub != nullptr)
            {
                nsub->substr = nsub->substr + srcstr[index - 1];
                prep = nsub;
                nsub = nsub->pnext;
            }
            nsub = msubstr(srcstr, index - 1, sublen);
            prep->pnext = nsub;
            while (nsub != nullptr)
            {
                nsub = nsub->pnext;
            }
        }
    }
    return tmpp;
}
int main()
{
    string a = "abcdef";
    string b = "acdfg";
    string c = "abcd";
    MNODE* psubstr = msubstr(a, a.size(), 3);
    while (psubstr != nullptr)
    {
        cout << psubstr->substr << endl;
        MNODE* tmpp = psubstr;
        psubstr = tmpp->pnext;
        delete tmpp;
    }
    char inchar;
    cin >> inchar;
}

按照公式推導,遇到索取的子字符串是1的時候,達到邊界,可以求值退出,后面在前面的基礎上增加當前的字符,不斷累加,到最后得出結果。

 

排列就是從n中選k,相同的元素,順序不同,也算不同的一種,數學中排列的公式是

 

 

如果選取的k個元素是可以重復的,那么就是,因為每次選擇都是n個元素,可以選k次,所以就是n*n*n...*n,k個n種選擇相乘。

 

 

 

組合就是選擇的元素,位置不同沒有影響,比如從一堆球中選擇不同顏色球的組合,數學中的組合公式是

 

 

與排列類似,因為肯定還是需要取那么多種類,只不過要把重復的去掉,就再除以k的階。

如果是選取的元素可以重復,組合的公式就是需要額外加上重復的選擇。這個公式可以轉換成另一個

 

 

那我們上面的2^n是如何得到的呢?來源於這個公式,我們可以知道,獲取子串,雖然與順序有關,但是,我們不能倒序獲取或是從中間任意一個位置獲取,字符串是有順序的,獲取的其他順序的字符串不能算是子串。也就相當於獲取固定索引位置的字符串,只能按照原來的順序組成一個子串,不可能出現多個,也就是組合,按照這個公式,就是2^n。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM