簡體   English   中英

Java - 遞歸地查找String(powerset)的所有子集

[英]Java - Finding all subsets of a String (powerset) recursively

所以,我需要遞歸地找到給定字符串的所有子集。 到目前為止我所擁有的是:

static ArrayList<String> powerSet(String s){
        ArrayList<String> ps = new ArrayList<String>();
        ps.add(s);


        for(int i=0; i<s.length(); i++){
            String temp = s.replace(Character.toString(s.charAt(i)), "");
            ArrayList<String> ps2 = powerSet(temp);
            for(int j = 0; j < ps2.size(); j++){
                ps.add(ps2.get(j));
            }
        }   

        return ps;

我想我現在知道問題是什么,但我不知道如何解決它。 目前,我找到所有temp的冪集,分別是“bcd”,“acd”,“abd”,“abc”,這將導致重復。 有想法該怎么解決這個嗎?

通過powerset,我的意思是如果字符串是abc,它將返回“”,“a”,“b”,“c”,“ab”,“ac”,“bc”,“abc”。

有一種方法可以在不使用遞歸的情況下完成此操作,它依賴於位串和子集之間的簡單對應關系。

所以,假設你有一個三個字符串“abc”,那么,如你所說,子集將是“”,“c”,“b”,“bc”,“a”,“ac”,“ab”, “ABC”

如果您創建一個字符表並為子集中的每個字符寫1,而在子集中不為0,則可以看到一個模式:

    a b c   bits    decimal
            0 0 0   0
        c   0 0 1   1
      b     0 1 0   2
      b c   0 1 1   3
    a       1 0 0   4
    a   c   1 0 1   5
    a b     1 1 0   6
    a b c   1 1 1   7

對於每個長度為n的唯一字符串,您將有2 個n個子集,並且您可以通過簡單地從i = 0到i = 2 n -1進行一個for循環來生成它們,並且僅包括與位對應的那些字符在i是1。

我寫了一個Java的例子在這里和C例子在這里

具有n個元素的集合的子集數量是2 n 例如,如果我們有字符串“abc”,我們將有2 n = 2 3 = 8個子集。

可由n位表示的狀態數也是2 n 我們可以證明枚舉n位的所有可能狀態與具有n個元素的集合的所有可能子集之間存在對應關系:

   2 1 0   2 1 0
   c b a    bits
0          0 0 0
1      a   0 0 1
2    b     0 1 0
3    b a   0 1 1
4  c       1 0 0
5  c   a   1 0 1
6  c b     1 1 0 
7  c b a   1 1 1

例如,如果我們考慮第5行,則第2位和第0位是活動的。 如果我們執行abc.charAt(0) + abc.charAt(2)我們得到子集ac

為了枚舉n位的所有可能狀態,我們從0開始,加1,直到達到2 n - 1.在這個解決方案中,我們將從2 n - 1開始並遞減到0,所以我們不需要另一個參數保持子集的數量,但效果是一樣的:

static List<String> powerSet(String s) {
    // the number of subsets is 2^n
    long numSubsets = 1L << s.length();
    return powerSet(s, numSubsets - 1);
}

static List<String> powerSet(String s, long active) {
    if (active < 0) {
        // Recursion base case
        // All 2^n subsets were visited, stop here and return a new list
        return new ArrayList<>();
    }

    StringBuilder subset = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        // For each bit
        if (isSet(active, i)) {
            // If the bit is set, add the correspondent char to this subset
            subset.append(s.charAt(i));
        }
    }
    // Make the recursive call, decrementing active to the next state,
    // and get the returning list
    List<String> subsets = powerSet(s, active - 1);
    // Add this subset to the list of subsets
    subsets.add(subset.toString());
    return subsets;
}

static boolean isSet(long bits, int i) {
    // return true if the ith bit is set
    return (bits & (1L << i)) != 0;
}

然后你只需要調用它:

System.out.println(powerSet("abc"));

並獲得所有8個子集:

[, a, b, ab, c, ac, bc, abc]

我發現在設計遞歸算法時首先考慮簡單的角點情況很有幫助,即空字符串和帶有一個字符的字符串。 然后你通常會分解問題,並在字符串的rest / tail上進行遞歸調用。 有點像這樣:

    static List<String> nuPowerSet(String s) {

    if (s.length() == 0) { // trivial, subset of empty string is empty
        return emptyList();
    }

    String head = s.substring(0, 1);
    if (s.length() ==1) // the subset of a one character string is exactly that character
        return asList(head);

    String tail = s.substring(1);

    ArrayList<String> ps = new ArrayList<String>();

    ps.add(head); // one of the subsets is the current first character


    List<String> tailSubsets = nuPowerSet(tail); // all the subsets of the remainder.

    List<String> tailSubsetsWithCurrentHeadPrepended = tailSubsets
            .stream()
            .map(element -> head + element) 
            .collect(Collectors.toList());

    ps.addAll(tailSubsets);
    ps.addAll(tailSubsetsWithCurrentHeadPrepended);

    return ps;
}

要消除重復,你只需要將它們全部添加到一個Set ,這可以通過某種幫助器輕松完成:

static ArrayList<String> powerSet(String s) {
    return new ArrayList<>(_powerSet(s));
}

static HashSet<String> _powerSet(String s) {
    HashSet<String> set = new HashSet<>();
    set.add(s);

    for(int i = 0; i < s.length(); i++) {
        String tmp = s.substring(0, i) + s.substring(i+1, s.length());
        set.addAll(_powerSet(tmp));
    }

    return set;
}

順便說一下,你的代碼本質上處理了邊緣情況。 你不需要擔心這個。

你是對的,你確實有重復,因為你多次創建temp (每次沒有其他字符)所以當你遞歸調用時,會有不同的子集共享相同的字符並創建dup。 例如,“abc”將創建一個帶有:[“ab”,“ac”,“bc”]的temp ,並且每一個都將只用一個字符遞歸調用,這樣你就可以獲得每個“a”,“ b“和”c“兩次。

避免它的一種方法(使用最少的更改)將使用Set而不是列表 - 這將省略所有重復:

static Set<String> powerSet(String s) {
    Set<String> ps = new HashSet<>();
    ps.add(s);

    for (int i = 0; i < s.length(); i++) {
        String temp = s.replace(Character.toString(s.charAt(i)), "");
        Set<String> ps2 = powerSet(temp);
        for (String x : ps2) {
            ps.add(x);
        }
    }
    return ps;
}

現在輸出將是:

bc
a
ab
b
ac
abc
c

不同的解決方案:

public static List<String> powerset(String s) {
    List<String> ans = new LinkedList<>();
    if (null == s) {
        return ans;
    }
    return powerset(s, ans);
}

private static List<String> powerset(String s, List<String> ans) {
    if ("".equals(s)) {
        return ans;
    }
    String first = s.substring(0, 1);
    String rest = s.substring(1);
    ans.add(first);
    List<String> pAns = new LinkedList<>(ans);
    for (String partial : ans.subList(0, ans.size()-1)) {
        pAns.add(partial + first);
    }
    return powerset(rest, pAns);
}

OUTPUT

[a, b, ab, c, ac, bc, abc]
import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;

    public class MainClass {

        static List<List<char[]>> list = new ArrayList<List<char[]>>();

        // static List<int[]> list1 = new ArrayList<int[]>();
        public static void main(String[] args) {
            List<char[]> list1 = new ArrayList<char[]>();
            String string = "abcd";
            char[] a = string.toCharArray();
            generate(a, 0, 0, list1);

            for (List<char[]> l : list) {
                for (char[] b : l) {
                    for (char c : b) {
                        System.out.print(c + ",");
                    }
                    System.out.println();
                }
            }

        }

        public static void generate(char[] array, int offset, int index, List<char[]> list1) {
            if (offset >= array.length)
                return;
            char[] newArray = Arrays.copyOfRange(array, offset, index);
            list1.add(newArray);
            if (index >= array.length) {
                list.add(list1);
                offset++;
                index = offset;
                generate(array, offset, index, new ArrayList<char[]>());
            } else {
                index++;
                generate(array, offset, index, list1);
            }
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM