การรับพาวเวอร์เซตของชุดใน Java


86

ชุดอำนาจของ{1, 2, 3}คือ:

{{}, {2}, {3}, {2, 3}, {1, 2}, {1, 3}, {1, 2, 3}, {1}}

สมมติว่าฉันมีSetใน Java:

Set<Integer> mySet = new HashSet<Integer>();
mySet.add(1);
mySet.add(2);
mySet.add(3);
Set<Set<Integer>> powerSet = getPowerset(mySet);

ฉันจะเขียนฟังก์ชัน getPowerset ด้วยลำดับความซับซ้อนที่ดีที่สุดได้อย่างไร (ฉันคิดว่ามันอาจจะเป็น O (2 ^ n))


7
สมมติว่าคุณมีชุดการกำหนดค่า - พูดว่า "A", "B" และ "C" - ซึ่งสามารถใช้เพื่อกำหนดรูปแบบพารามิเตอร์ได้และคุณต้องการดูว่าชุดย่อยใดให้ผลลัพธ์ที่ดีที่สุด - เช่น "A ". วิธีแก้ปัญหาที่เป็นไปได้คือการทดสอบสมาชิกแต่ละคนของ powerset
João Silva

7
เป็นคำถามสัมภาษณ์ Google สำหรับนักพัฒนาซอฟต์แวร์ เป็นปัญหาที่สร้างขึ้นเพื่อทดสอบความคล่องตัวของจิตใจของคุณ
Eric Leschinski

นี่เป็นคำถามที่สมเหตุสมผล ตัวอย่างเช่นในการใช้ฟังก์ชันการให้คะแนนสำหรับ cribbage คุณต้องทดสอบว่าองค์ประกอบใด ๆ ของ powerset รวมกันได้ถึง 15 หรือไม่
John Henckel

คำตอบ:


101

ใช่O(2^n)แน่นอนเนื่องจากคุณต้องสร้าง2^nชุดค่าผสมที่เป็นไปได้ นี่คือการนำไปใช้งานโดยใช้ generics และ set:

public static <T> Set<Set<T>> powerSet(Set<T> originalSet) {
    Set<Set<T>> sets = new HashSet<Set<T>>();
    if (originalSet.isEmpty()) {
        sets.add(new HashSet<T>());
        return sets;
    }
    List<T> list = new ArrayList<T>(originalSet);
    T head = list.get(0);
    Set<T> rest = new HashSet<T>(list.subList(1, list.size())); 
    for (Set<T> set : powerSet(rest)) {
        Set<T> newSet = new HashSet<T>();
        newSet.add(head);
        newSet.addAll(set);
        sets.add(newSet);
        sets.add(set);
    }       
    return sets;
}  

และการทดสอบตามตัวอย่างของคุณ:

 Set<Integer> mySet = new HashSet<Integer>();
 mySet.add(1);
 mySet.add(2);
 mySet.add(3);
 for (Set<Integer> s : SetUtils.powerSet(mySet)) {
     System.out.println(s);
 }

1
จะเร็วกว่าถ้าใช้ Iterator แทนการใช้ list? เช่น: ตั้งค่า <T> rest = new HashSet <T> (originalSet); Iterator <T> i = rest.iterator (); หัว T = i.next (); i.remove (); เหรอ?
Dimath

1
@CosminVacaroiu ... มันจะทำอะไรได้อีก?
user253751

3
แน่ใจO(2^n)เหรอ? นั่นคือจำนวนชุดในชุดพลังงาน แต่ต้องสร้างแต่ละชุดในหน่วยความจำซึ่งต้องใช้เวลาตามสัดส่วนกับขนาดชุดเป็นอย่างน้อย อ้างอิงจาก Wolfram alpha มันอยู่ในO(n * 2^n): Wolfram alpha query
fabian

1
จะใช้งานได้หรือไม่แม้ว่าขนาดของชุดจะอยู่ในลำดับ 10 ^ 5
สารพิษ 19

1
@GauravShankar 2 ^ 100 = 2 ^ (10 ^ 2) มีขนาดใหญ่กว่า 10 ^ 30 แล้ว คุณจะไม่เห็นการคำนวณเสร็จสิ้นไม่ว่าคุณจะคำนวณด้วยเครื่องทัวริงเครื่องใดก็ตาม
Karl Richter

31

อันที่จริงฉันได้เขียนโค้ดที่ทำตามที่คุณต้องการใน O (1) คำถามคือคุณวางแผนจะทำอะไรกับ Set ต่อไป หากคุณเพิ่งจะเรียกsize()มันว่าโอ (1) O(2^n)แต่ถ้าคุณกำลังจะย้ำว่าเห็นได้ชัด

contains()จะO(n)เป็นต้น

คุณต้องการสิ่งนี้จริงๆหรือ?

แก้ไข:

รหัสนี้คือตอนนี้ที่มีอยู่ในฝรั่งSets.powerSet(set)สัมผัสผ่านวิธีการที่


ฉันต้องทำซ้ำทุกส่วนย่อย
Manuel Araoz

แต่คุณจำเป็นต้องจัดเก็บทุกส่วนย่อยหรือไม่?
finnw

2
ตอนนี้วิธีนี้อยู่ใน Guava: guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/…
Kevin Bourrillion

จะเกิดอะไรขึ้นถ้าฉันต้องการแค่พาวเวอร์เซตที่มีองค์ประกอบ k ตรง รหัสของคุณมีประสิทธิภาพหรือไม่
Eyal

ลิงค์ใหม่ (ฝรั่งย้ายไป Github)
yiwei

12

นี่คือวิธีแก้ปัญหาที่ฉันใช้เครื่องกำเนิดไฟฟ้าข้อดีคือชุดพลังงานทั้งหมดจะไม่ถูกจัดเก็บพร้อมกัน ... ดังนั้นคุณสามารถทำซ้ำได้ทีละรายการโดยไม่จำเป็นต้องเก็บไว้ในหน่วยความจำ ฉันอยากจะคิดว่ามันเป็นทางเลือกที่ดีกว่า ... โปรดทราบว่าความซับซ้อนเหมือนกัน O (2 ^ n) แต่ความต้องการหน่วยความจำจะลดลง (สมมติว่าคนเก็บขยะทำงาน!;))

/**
 *
 */
package org.mechaevil.util.Algorithms;

import java.util.BitSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author st0le
 *
 */
public class PowerSet<E> implements Iterator<Set<E>>,Iterable<Set<E>>{
    private E[] arr = null;
    private BitSet bset = null;

    @SuppressWarnings("unchecked")
    public PowerSet(Set<E> set)
    {
        arr = (E[])set.toArray();
        bset = new BitSet(arr.length + 1);
    }

    @Override
    public boolean hasNext() {
        return !bset.get(arr.length);
    }

    @Override
    public Set<E> next() {
        Set<E> returnSet = new TreeSet<E>();
        for(int i = 0; i < arr.length; i++)
        {
            if(bset.get(i))
                returnSet.add(arr[i]);
        }
        //increment bset
        for(int i = 0; i < bset.size(); i++)
        {
            if(!bset.get(i))
            {
                bset.set(i);
                break;
            }else
                bset.clear(i);
        }

        return returnSet;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Not Supported!");
    }

    @Override
    public Iterator<Set<E>> iterator() {
        return this;
    }

}

หากต้องการเรียกใช้รูปแบบนี้:

        Set<Character> set = new TreeSet<Character> ();
        for(int i = 0; i < 5; i++)
            set.add((char) (i + 'A'));

        PowerSet<Character> pset = new PowerSet<Character>(set);
        for(Set<Character> s:pset)
        {
            System.out.println(s);
        }

มาจาก Project Euler Library ของฉัน ... :)


ฝรั่งคนหนึ่งใช้งานได้เหมือนกับตัวนี้ แต่ถูก จำกัด ไว้ที่ 32 องค์ประกอบ นั่นไม่ใช่เหตุผลเพราะ 2 ** 32 อาจมีการทำซ้ำมากเกินไป ใช้หน่วยความจำน้อยกว่าของคุณเนื่องจากสร้าง AbstractSet เมื่อจำเป็นเท่านั้น ลองใช้รหัสของคุณเทียบกับ Guava ที่คุณพิมพ์เพียง 1 ใน 10,000 องค์ประกอบและสร้างตัวอย่างที่ยิ่งใหญ่ ฉันพนันได้เลยว่าฝรั่งจะเร็วกว่า
Eyal

@ อียัลฉันแน่ใจว่าเป็นอย่างนั้นฉันไม่เคยอ้างสิทธิ์เป็นอย่างอื่น ฉันเขียนสิ่งนี้เองไม่ได้มีไว้สำหรับรหัสการผลิต มันเป็นการออกกำลังกายในอัลกอริทึม
st0le

1
ข้อสังเกตเล็กน้อย: 'returnSet' ของคุณคือ TreeSet ซึ่งต้องการให้มีการเปรียบเทียบรายการ อาจไม่เป็นเช่นนั้น ลองเปลี่ยนเป็น HashSet หรือ LinkedHashSet
Joris Kinable

10

หาก n <63 ซึ่งเป็นข้อสันนิษฐานที่สมเหตุสมผลเนื่องจากคุณมีหน่วยความจำไม่เพียงพอ (เว้นแต่จะใช้การใช้งานตัววนซ้ำ) พยายามสร้างชุดพลังงานต่อไปนี่เป็นวิธีที่รัดกุมกว่าในการดำเนินการ การดำเนินการไบนารีเป็นวิธีที่เร็วกว่าMath.pow()และอาร์เรย์สำหรับมาสก์ แต่ผู้ใช้ Java ก็กลัวพวกเขา ...

List<T> list = new ArrayList<T>(originalSet);
int n = list.size();

Set<Set<T>> powerSet = new HashSet<Set<T>>();

for( long i = 0; i < (1 << n); i++) {
    Set<T> element = new HashSet<T>();
    for( int j = 0; j < n; j++ )
        if( (i >> j) % 2 == 1 ) element.add(list.get(j));
    powerSet.add(element); 
}

return powerSet;

เงื่อนไขการสิ้นสุดในการวนซ้ำควรเป็น i <(2 << n - 1) แทนที่จะเป็น i <(1 << n - 1)
bazeusz

ขอบคุณ @bazeusz ฉันเปลี่ยนi < (1 << n)เป็นสิ่งที่เทียบเท่า
Andrew Mao

เนื่องจากมีการใช้การดำเนินการที่ชาญฉลาดเล็กน้อยฉันคิดว่า สามารถใช้((i >> j) &1) == 1แทน(i >> j) % 2 == 1ได้ นอกจากนี้ยังlongมีการลงชื่อคุณคิดว่าการตรวจสอบการล้นนั้นสมเหตุสมผลหรือไม่?
ราวีทิวา

9

นี่คือบทช่วยสอนที่อธิบายถึงสิ่งที่คุณต้องการรวมถึงรหัสด้วย คุณเข้าใจถูกแล้วว่าความซับซ้อนคือ O (2 ^ n)


2
ไม่ซับซ้อน (n * 2 ^ n)? เนื่องจากสตริงไบนารีมีความยาว n และในการวนซ้ำแต่ละครั้งของลูปหลักเราจะวนซ้ำสตริงไบนารีทั้งหมด
Maggie

1
บทช่วยสอนนั้นยอดเยี่ยมมาก แต่ฉันได้ใช้เทคนิคนี้ในการแก้ปัญหา HackerRank มันผ่านเพียงครึ่งหนึ่งของกรณีทดสอบและอีกครึ่งหนึ่งล้มเหลวเนื่องจากหมดเวลาหรือเกิดข้อผิดพลาดรันไทม์
Eugenia Ozirna

7

ฉันคิดวิธีแก้ปัญหาอื่นตามความคิดของ @Harry He อาจจะไม่สวยหรูที่สุด แต่ก็เป็นไปตามที่ฉันเข้าใจ:

ลองมาดูตัวอย่างง่ายๆคลาสสิก PowerSet ของ SP (S) = {{1}, {2}, {3}} เรารู้ว่าสูตรที่จะได้รับจำนวนชุดย่อยคือ 2 ^ n (7 + ชุดว่าง) สำหรับตัวอย่างนี้ 2 ^ 3 = 8 ส่วนย่อย

ในการค้นหาแต่ละส่วนย่อยเราจำเป็นต้องแปลงทศนิยม 0-7 ให้เป็นการแสดงฐานสองที่แสดงในตารางการแปลงด้านล่าง:

ConversionTable

หากเราสำรวจแถวตารางทีละแถวแต่ละแถวจะส่งผลให้เป็นชุดย่อยและค่าของแต่ละชุดย่อยจะมาจากบิตที่เปิดใช้งาน

แต่ละคอลัมน์ในส่วน Bin Value จะสอดคล้องกับตำแหน่งดัชนีในชุดอินพุตดั้งเดิม

นี่คือรหัสของฉัน:

public class PowerSet {

/**
 * @param args
 */
public static void main(String[] args) {
    PowerSet ps = new PowerSet();
    Set<Integer> set = new HashSet<Integer>();
    set.add(1);
    set.add(2);
    set.add(3);
    for (Set<Integer> s : ps.powerSet(set)) {
        System.out.println(s);
    }
}

public Set<Set<Integer>> powerSet(Set<Integer> originalSet) {
    // Original set size e.g. 3
    int size = originalSet.size();
    // Number of subsets 2^n, e.g 2^3 = 8
    int numberOfSubSets = (int) Math.pow(2, size);
    Set<Set<Integer>> sets = new HashSet<Set<Integer>>();
    ArrayList<Integer> originalList = new ArrayList<Integer>(originalSet);
    for (int i = 0; i < numberOfSubSets; i++) {
        // Get binary representation of this index e.g. 010 = 2 for n = 3
        String bin = getPaddedBinString(i, size);
        //Get sub-set
        Set<Integer> set = getSet(bin, originalList));
        sets.add(set);
    }
    return sets;
}

//Gets a sub-set based on the binary representation. E.g. for 010 where n = 3 it will bring a new Set with value 2
private Set<Integer> getSet(String bin, List<Integer> origValues){
    Set<Integer> result = new HashSet<Integer>();
    for(int i = bin.length()-1; i >= 0; i--){
        //Only get sub-sets where bool flag is on
        if(bin.charAt(i) == '1'){
            int val = origValues.get(i);
            result.add(val);
        }
    }
    return result;
}

//Converts an int to Bin and adds left padding to zero's based on size
private String getPaddedBinString(int i, int size) {
    String bin = Integer.toBinaryString(i);
    bin = String.format("%0" + size + "d", Integer.parseInt(bin));
    return bin;
}

}

5

หากคุณใช้Eclipse Collections (เดิมคือGS Collections ) คุณสามารถใช้powerSet()วิธีนี้กับ SetIterables ทั้งหมด

MutableSet<Integer> set = UnifiedSet.newSetWith(1, 2, 3);
System.out.println("powerSet = " + set.powerSet());
// prints: powerSet = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

หมายเหตุ:ฉันเป็นผู้กำหนดค่า Eclipse Collections


คุณสามารถแบ่งปันและอธิบายรหัสของโซลูชันของคุณได้หรือไม่?
Konrad Höffner

3
คุณสามารถดูรหัสได้ที่นี่: github.com/goldmansachs/gs-collections/blob/…
Craig P. Motlin

4

ฉันกำลังมองหาวิธีแก้ปัญหาที่ไม่ใหญ่เท่าที่โพสต์ไว้ที่นี่ เป้าหมายนี้กำหนดเป้าหมายเป็น Java 7 ดังนั้นจึงต้องมีการวางจำนวนหนึ่งสำหรับเวอร์ชัน 5 และ 6

Set<Set<Object>> powerSetofNodes(Set<Object> orig) {
    Set<Set<Object>> powerSet = new HashSet<>(),
        runSet = new HashSet<>(),
        thisSet = new HashSet<>();

    while (powerSet.size() < (Math.pow(2, orig.size())-1)) {
        if (powerSet.isEmpty()) {
            for (Object o : orig) {
                Set<Object> s = new TreeSet<>();
                s.add(o);
                runSet.add(s);
                powerSet.add(s);
            }
            continue;
        }
        for (Object o : orig) {
            for (Set<Object> s : runSet) {
                Set<Object> s2 = new TreeSet<>();
                s2.addAll(s);
                s2.add(o);
                powerSet.add(s2);
                thisSet.add(s2);
            }
        }
        runSet.clear();
        runSet.addAll(thisSet);
        thisSet.clear();
    }
    powerSet.add(new TreeSet());
    return powerSet;

นี่คือตัวอย่างโค้ดสำหรับทดสอบ:

Set<Object> hs = new HashSet<>();
hs.add(1);
hs.add(2);
hs.add(3);
hs.add(4);
for(Set<Object> s : powerSetofNodes(hs)) {
    System.out.println(Arrays.toString(s.toArray()));
}

powerSetofNodes () ไม่มี "}" ต่อท้ายใช่หรือไม่
Peter Mortensen

3

วิธีแก้ปัญหาข้างต้นบางวิธีประสบเมื่อขนาดของชุดมีขนาดใหญ่เนื่องจากกำลังสร้างขยะวัตถุจำนวนมากที่จะรวบรวมและต้องมีการคัดลอกข้อมูล เราจะหลีกเลี่ยงสิ่งนั้นได้อย่างไร? เราสามารถใช้ประโยชน์จากความจริงที่ว่าเรารู้ว่าขนาดชุดผลลัพธ์จะใหญ่แค่ไหน (2 ^ n) จัดสรรอาร์เรย์ที่มีขนาดใหญ่แล้วต่อท้ายอาร์เรย์โดยไม่ต้องคัดลอก

speedup เติบโตอย่างรวดเร็วด้วย n ฉันเปรียบเทียบกับโซลูชันของJoão Silva ด้านบน บนเครื่องของฉัน (การวัดทั้งหมดโดยประมาณ) n = 13 เร็วขึ้น 5 เท่า n = 14 คือ 7x, n = 15 คือ 12x, n = 16 คือ 25x, n = 17 คือ 75x, n = 18 คือ 140x ดังนั้นการสร้าง / การรวบรวมและการคัดลอกขยะจึงมีอำนาจเหนือกว่าสิ่งที่ดูเหมือนจะเป็นโซลูชันขนาดใหญ่ที่คล้ายคลึงกัน

การจัดสรรอาร์เรย์ล่วงหน้าที่จุดเริ่มต้นดูเหมือนจะชนะเมื่อเทียบกับการปล่อยให้อาร์เรย์เติบโตแบบไดนามิก ด้วย n = 18 การเติบโตแบบไดนามิกจะใช้เวลานานกว่าโดยรวมประมาณสองเท่า

public static <T> List<List<T>> powerSet(List<T> originalSet) {
    // result size will be 2^n, where n=size(originalset)
    // good to initialize the array size to avoid dynamic growing
    int resultSize = (int) Math.pow(2, originalSet.size());
    // resultPowerSet is what we will return
    List<List<T>> resultPowerSet = new ArrayList<List<T>>(resultSize);

    // Initialize result with the empty set, which powersets contain by definition
    resultPowerSet.add(new ArrayList<T>(0)); 

    // for every item in the original list
    for (T itemFromOriginalSet : originalSet) {

        // iterate through the existing powerset result
        // loop through subset and append to the resultPowerset as we go
        // must remember size at the beginning, before we append new elements
        int startingResultSize = resultPowerSet.size();
        for (int i=0; i<startingResultSize; i++) {
            // start with an existing element of the powerset
            List<T> oldSubset = resultPowerSet.get(i);

            // create a new element by adding a new item from the original list
            List<T> newSubset = new ArrayList<T>(oldSubset);
            newSubset.add(itemFromOriginalSet);

            // add this element to the result powerset (past startingResultSize)
            resultPowerSet.add(newSubset);
        }
    }
    return resultPowerSet;
}

3

โซลูชันต่อไปนี้ยืมมาจากหนังสือของฉัน " Coding Interviews: Questions, Analysis & Solutions ":

จำนวนเต็มบางตัวในอาร์เรย์ถูกเลือกที่ประกอบกัน มีการใช้ชุดของบิตโดยแต่ละบิตหมายถึงจำนวนเต็มในอาร์เรย์ หากเลือกอักขระi-thสำหรับการผสมบิตi-thคือ 1; มิฉะนั้นจะเป็น 0 ตัวอย่างเช่นสามบิตใช้สำหรับการรวมอาร์เรย์ [1, 2, 3] หากเลือกจำนวนเต็ม 1 และ 2 สองตัวแรกเพื่อสร้างชุดค่าผสม [1, 2] บิตที่ตรงกันคือ {1, 1, 0} ในทำนองเดียวกันบิตที่ตรงกับชุดค่าผสมอื่น [1, 3] คือ {1, 0, 1} เราสามารถหาชุดค่าผสมทั้งหมดของอาร์เรย์ที่มีความยาวn ได้หากเราได้ชุดค่าnบิตที่เป็นไปได้ทั้งหมด

ตัวเลขประกอบด้วยชุดของบิต ชุดค่าผสมที่เป็นไปได้ทั้งหมดของnบิตสอดคล้องกับตัวเลขตั้งแต่ 1 ถึง 2 ^ n -1 ดังนั้นแต่ละตัวเลขในช่วงระหว่าง 1 ถึง 2 ^ n -1 จึงสอดคล้องกับการรวมกันของอาร์เรย์ที่มีความยาวn nตัวอย่างเช่นหมายเลข 6 ประกอบด้วยบิต {1, 1, 0} ดังนั้นอักขระตัวแรกและตัวที่สองจึงถูกเลือกในอาร์เรย์ [1, 2, 3] เพื่อสร้างชุดค่าผสม [1, 2] ในทำนองเดียวกันตัวเลข 5 ที่มีบิต {1, 0, 1} จะสอดคล้องกับชุดค่าผสม [1, 3]

โค้ด Java สำหรับใช้โซลูชันนี้มีลักษณะดังนี้:

public static ArrayList<ArrayList<Integer>> powerSet(int[] numbers) {
    ArrayList<ArrayList<Integer>> combinations = new ArrayList<ArrayList<Integer>>(); 
    BitSet bits = new BitSet(numbers.length);
    do{
        combinations.add(getCombination(numbers, bits));
    }while(increment(bits, numbers.length));

    return combinations;
}

private static boolean increment(BitSet bits, int length) {
    int index = length - 1;

    while(index >= 0 && bits.get(index)) {
        bits.clear(index);
        --index;
    }

    if(index < 0)
        return false;

    bits.set(index);
    return true;
}

private static ArrayList<Integer> getCombination(int[] numbers, BitSet bits){
    ArrayList<Integer> combination = new ArrayList<Integer>();
    for(int i = 0; i < numbers.length; ++i) {
        if(bits.get(i))
            combination.add(numbers[i]);
    }

    return combination;
}

การเพิ่มเมธอดจะเพิ่มจำนวนที่แสดงในชุดของบิต อัลกอริทึมจะล้าง 1 บิตจากบิตขวาสุดจนกว่าจะพบ 0 บิต จากนั้นตั้งค่า 0 บิตขวาสุดเป็น 1 ตัวอย่างเช่นหากต้องการเพิ่มจำนวน 5 ด้วยบิต {1, 0, 1} จะล้าง 1 บิตจากด้านขวาและตั้งค่า 0 บิตขวาสุดเป็น 1 บิตจะ {1, 1, 0} สำหรับเลข 6 ซึ่งเป็นผลมาจากการเพิ่ม 5 โดย 1


สองสิ่งที่ฉันแก้ไข: การวนซ้ำใน getCombination แทน numbers.length (หรือ bits.size ()) หนึ่งสามารถวนซ้ำเป็น bits.length () ซึ่งเพิ่มความเร็วในการสร้างขึ้นเล็กน้อย สุดท้ายฉันจัดเรียงชุดย่อยตามขนาดสำหรับปัญหาของฉันต้องการสิ่งนั้น
BoLe

3

นี่คือวิธีแก้ปัญหา O (2 ^ n) ซ้ำง่ายๆ:

public static Set<Set<Integer>> powerSet(List<Integer> intList){

    Set<Set<Integer>> result = new HashSet();
    result.add(new HashSet());

    for (Integer i : intList){

        Set<Set<Integer>> temp = new HashSet();

        for(Set<Integer> intSet : result){

            intSet = new HashSet(intSet);
            intSet.add(i);                
            temp.add(intSet);
        }
        result.addAll(temp);
    }
    return result;
}

โซลูชันนี้ยังใช้พื้นที่ O (2 ^ n) ซึ่งจะมากเกินไปสำหรับชุดอินพุตขนาดใหญ่ เป็นการดีกว่าที่จะปฏิบัติตามคำจำกัดความแบบเรียกซ้ำโดยใช้สแต็กหรือคิวแทนการเรียกซ้ำ
rossb83


1

ถ้า S เป็นเซต จำกัด ที่มีองค์ประกอบ N ดังนั้นชุดกำลังของ S จะมีองค์ประกอบ 2 ^ N เวลาในการแจกแจงองค์ประกอบของพาวเวอร์เซตคือ 2 ^ N ดังนั้นO(2^N)ขอบเขตที่ต่ำกว่าของความซับซ้อนของเวลาของการสร้างพาวเวอร์เซ็ต (อย่างกระตือรือร้น)

พูดง่ายๆก็คือการคำนวณใด ๆ ที่เกี่ยวข้องกับการสร้างชุดพาวเวอร์เซ็ตจะไม่ปรับขนาดสำหรับค่าขนาดใหญ่ของ N ไม่มีอัลกอริทึมที่ชาญฉลาดจะช่วยคุณได้ ...


1

วิธีหนึ่งที่ไม่มีการเรียกซ้ำมีดังต่อไปนี้: ใช้หน้ากากไบนารีและสร้างชุดค่าผสมที่เป็นไปได้ทั้งหมด

public HashSet<HashSet> createPowerSet(Object[] array)
{
    HashSet<HashSet> powerSet=new HashSet();
    boolean[] mask= new boolean[array.length];

    for(int i=0;i<Math.pow(2, array.length);i++)
    {
        HashSet set=new HashSet();
        for(int j=0;j<mask.length;j++)
        {
            if(mask[i])
                set.add(array[j]);
        }
        powerSet.add(set);      

        increaseMask(mask);
    }

    return powerSet;
}

public void increaseMask(boolean[] mask)
{
    boolean carry=false;

    if(mask[0])
        {
            mask[0]=false;
            carry=true;
        }
    else
        mask[0]=true;

    for(int i=1;i<mask.length;i++)
    {
        if(mask[i]==true && carry==true)
        mask[i]=false;
        else if (mask[i]==false && carry==true)
        {
            mask[i]=true;
            carry=false;
        }
        else 
            break;

    }

}

1

อัลกอริทึม:

Input: Set [], set_size 1. รับขนาดของ power set powet_set_size = pow (2, set_size) 2 Loop for counter from 0 to pow_set_size (a) Loop for i = 0 to set_size (i) ถ้า ith bit ในตัวนับคือ set Print ith element จาก set สำหรับเซ็ตย่อยนี้ (b) Print seperator สำหรับเซ็ตย่อยเช่น newline

#include <stdio.h>
#include <math.h>
 
void printPowerSet(char *set, int set_size)
{
    /*set_size of power set of a set with set_size
      n is (2**n -1)*/
    unsigned int pow_set_size = pow(2, set_size);
    int counter, j;
 
    /*Run from counter 000..0 to 111..1*/
    for(counter = 0; counter < pow_set_size; counter++)
    {
      for(j = 0; j < set_size; j++)
       {
          /* Check if jth bit in the counter is set
             If set then pront jth element from set */
          if(counter & (1<<j))
            printf("%c", set[j]);
       }
       printf("\n");
    }
}
 
/*Driver program to test printPowerSet*/
int main()
{
    char set[] = {'a','b','c'};
    printPowerSet(set, 3);
 
    getchar();
    return 0;
}


1

นี่คือโซลูชันแบบวนซ้ำของฉันซึ่งสามารถรับชุดพลังงานของชุดใดก็ได้โดยใช้ Java Generics แนวคิดหลักคือการรวมส่วนหัวของอาร์เรย์อินพุตเข้ากับโซลูชันที่เป็นไปได้ทั้งหมดของอาร์เรย์ที่เหลือดังนี้

import java.util.LinkedHashSet;
import java.util.Set;

public class SetUtil {
    private static<T>  Set<Set<T>> combine(T head, Set<Set<T>> set) {
        Set<Set<T>> all = new LinkedHashSet<>();

        for (Set<T> currentSet : set) {
            Set<T> outputSet = new LinkedHashSet<>();

            outputSet.add(head);
            outputSet.addAll(currentSet);

            all.add(outputSet);
        }

        all.addAll(set);        

        return all;
    }

    //Assuming that T[] is an array with no repeated elements ...
    public static<T> Set<Set<T>> powerSet(T[] input) {
        if (input.length == 0) {
            Set <Set<T>>emptySet = new LinkedHashSet<>();

            emptySet.add(new LinkedHashSet<T>());

            return emptySet;
        }

        T head = input[0];
        T[] newInputSet = (T[]) new Object[input.length - 1];

        for (int i = 1; i < input.length; ++i) {
            newInputSet[i - 1] = input[i];
        }

        Set<Set<T>> all = combine(head, powerSet(newInputSet));

        return all;
    }

    public static void main(String[] args) {            
        Set<Set<Integer>> set = SetUtil.powerSet(new Integer[] {1, 2, 3, 4, 5, 6});

        System.out.println(set);
    }
}

สิ่งนี้จะส่งออก:

[[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5], [1, 2, 3, 4, 6], [1, 2, 3, 4], [1, 2, 3, 5, 6], [1, 2, 3, 5], [1, 2, 3, 6], [1, 2, 3], [1, 2, 4, 5, 6], [1, 2, 4, 5], [1, 2, 4, 6], [1, 2, 4], [1, 2, 5, 6], [1, 2, 5], [1, 2, 6], [1, 2], [1, 3, 4, 5, 6], [1, 3, 4, 5], [1, 3, 4, 6], [1, 3, 4], [1, 3, 5, 6], [1, 3, 5], [1, 3, 6], [1, 3], [1, 4, 5, 6], [1, 4, 5], [1, 4, 6], [1, 4], [1, 5, 6], [1, 5], [1, 6], [1], [2, 3, 4, 5, 6], [2, 3, 4, 5], [2, 3, 4, 6], [2, 3, 4], [2, 3, 5, 6], [2, 3, 5], [2, 3, 6], [2, 3], [2, 4, 5, 6], [2, 4, 5], [2, 4, 6], [2, 4], [2, 5, 6], [2, 5], [2, 6], [2], [3, 4, 5, 6], [3, 4, 5], [3, 4, 6], [3, 4], [3, 5, 6], [3, 5], [3, 6], [3], [4, 5, 6], [4, 5], [4, 6], [4], [5, 6], [5], [6], []]

1

การใช้งานตัวอย่างอื่น:

 public static void main(String args[])
    {
        int[] arr = new int[]{1,2,3,4};
        // Assuming that number of sets are in integer range
        int totalSets = (int)Math.pow(2,arr.length);
        for(int i=0;i<totalSets;i++)
        {
            String binaryRep = Integer.toBinaryString(i);      
            for(int j=0;j<binaryRep.length();j++)
            {
                int index=binaryRep.length()-1-j;
                if(binaryRep.charAt(index)=='1')
                System.out.print(arr[j] +" ");       
            }
            System.out.println();
        }
    }

1

นี่คือแนวทางของฉันกับแลมบ์ดาส

public static <T> Set<Set<T>> powerSet(T[] set) {
      return IntStream
            .range(0, (int) Math.pow(2, set.length))
            .parallel() //performance improvement
            .mapToObj(e -> IntStream.range(0, set.length).filter(i -> (e & (0b1 << i)) != 0).mapToObj(i -> set[i]).collect(Collectors.toSet()))
            .map(Function.identity())
            .collect(Collectors.toSet());
        }

หรือแบบขนาน (ดูความคิดเห็นแบบขนาน ()):

ขนาดชุดอินพุต: 18

โปรเซสเซอร์ลอจิก: 8 à 3.4GHz

ปรับปรุงประสิทธิภาพ: 30%


1

ชุดย่อยของ t คือเซตใด ๆ ที่สามารถทำได้โดยการลบองค์ประกอบของ t เป็นศูนย์หรือมากกว่า ชุดย่อย withoutFirst จะเพิ่มชุดย่อยของ t ที่ขาดองค์ประกอบแรกและ for loop จะจัดการกับการเพิ่มชุดย่อยด้วยองค์ประกอบแรก ตัวอย่างเช่นหากไม่มีองค์ประกอบ ["1", "2", "3"] missingFirst จะเพิ่ม [[""], ["2"], ["3"], ["2", "3 "]] และ for loop จะติด" 1 "ที่ด้านหน้าขององค์ประกอบเหล่านี้และเพิ่มลงใน newSet เราจะลงเอยด้วย [[""], ["1"], ["2"], ["3"], ["1", "2"], ["1", "3"] , ["2", "3"], ["1", "2", "3"]].

public static Set<Set<String>> allSubsets(Set<String> t) {
        Set<Set<String>> powerSet = new TreeSet<>();
        if(t.isEmpty()) {
            powerSet.add(new TreeSet<>());
            return powerSet;
        }
        String first = t.get(0);
        Set<Set<String>> withoutFirst = allSubsets(t.subSet(1, t.size()));
        for (List<String> 1st : withoutFirst) {
            Set<String> newSet = new TreeSet<>();
            newSet.add(first);
            newSet.addAll(lst);
            powerSet.add(newSet);
        }
        powerSet.addAll(withoutFirst);
        return powerSet;
    }

โปรดพิจารณาเพิ่มคำอธิบายสั้น ๆ ตามรหัสที่คุณให้มา
Mirza Sisic

สิ่งนี้ไม่ได้รวบรวมด้วยซ้ำดูเหมือนว่าจะเขียนด้วย Java เวอร์ชันแฟนตาซี Setไม่มีgetวิธีการที่มีดัชนีหรือsubSetวิธีการ 1stไม่ใช่ตัวระบุที่ถูกต้อง (ฉันคิดว่าlstหมายถึง) เปลี่ยนชุดทั้งหมดเป็นรายการและเกือบจะรวบรวม ...
john16384

0
// input: S
// output: P
// S = [1,2]
// P = [], [1], [2], [1,2]

public static void main(String[] args) {
    String input = args[0];
    String[] S = input.split(",");
    String[] P = getPowerSet(S);
    if (P.length == Math.pow(2, S.length)) {
        for (String s : P) {
            System.out.print("[" + s + "],");
        }
    } else {
        System.out.println("Results are incorrect");
    }
}

private static String[] getPowerSet(String[] s) {
    if (s.length == 1) {
        return new String[] { "", s[0] };
    } else {
        String[] subP1 = getPowerSet(Arrays.copyOfRange(s, 1, s.length));
        String[] subP2 = new String[subP1.length];
        for (int i = 0; i < subP1.length; i++) {
            subP2[i] = s[0] + subP1[i];
        }
        String[] P = new String[subP1.length + subP2.length];
        System.arraycopy(subP1, 0, P, 0, subP1.length);
        System.arraycopy(subP2, 0, P, subP1.length, subP2.length);
        return P;
    }

}

ยินดีต้อนรับสู่ Stack Overflow คุณอาจต้องการสรุปคำตอบนี้เล็กน้อยด้วยข้อความบางส่วนที่อธิบายถึงสิ่งที่กำลังทำอยู่และวิธีแก้ปัญหาของผู้ถาม
Lachlan Goodhew-Cook

0

เมื่อเร็ว ๆ นี้ฉันต้องใช้สิ่งนี้ แต่ต้องการรายการย่อยที่เล็กที่สุด (มี 1 องค์ประกอบจากนั้น 2 องค์ประกอบ ... ) ก่อน ฉันไม่ต้องการรวมรายการที่ว่างเปล่าหรือทั้งรายการ นอกจากนี้ฉันไม่ต้องการรายชื่อของรายการย่อยทั้งหมดที่ส่งคืนฉันแค่ต้องทำบางอย่างกับแต่ละรายการ

ต้องการทำสิ่งนี้โดยไม่ต้องเรียกซ้ำและมีสิ่งต่อไปนี้ (โดยมี "การทำสิ่งต่างๆ" ที่สรุปเป็นอินเทอร์เฟซที่ใช้งานได้

@FunctionalInterface interface ListHandler<T> {
    void handle(List<T> list);
}


public static <T> void forAllSubLists(final List<T> list, ListHandler handler) {
    int     ll = list.size();   // Length of original list
    int     ci[] = new int[ll]; // Array for list indices
    List<T> sub = new ArrayList<>(ll);  // The sublist
    List<T> uml = Collections.unmodifiableList(sub);    // For passing to handler

    for (int gl = 1, gm; gl <= ll; gl++) {  // Subgroup length 1 .. n-1
        gm = 0; ci[0] = -1; sub.add(null);  // Some inits, and ensure sublist is at least gl items long

        do {
                ci[gm]++;                       // Get the next item for this member

                if (ci[gm] > ll - gl + gm) {    // Exhausted all possibilities for this position
                        gm--; continue;         // Continue with the next value for the previous member
                }

                sub.set(gm, list.get(ci[gm]));  // Set the corresponding member in the sublist

                if (gm == gl - 1) {             // Ok, a sublist with length gl
                        handler.handle(uml);    // Handle it
                } else {
                        ci[gm + 1] = ci[gm];    // Starting value for next member is this 
                        gm++;                   // Continue with the next member
                }
        } while (gm >= 0);  // Finished cycling through all possibilities
    }   // Next subgroup length
}

ด้วยวิธีนี้การ จำกัด ไว้ในรายการย่อยที่มีความยาวเฉพาะได้อย่างง่ายดาย


0
public class PowerSet {
    public static List<HashSet<Integer>> powerset(int[] a) {
        LinkedList<HashSet<Integer>> sets = new LinkedList<HashSet<Integer>>();
        int n = a.length;
        for (int i = 0; i < 1 << n; i++) {
            HashSet<Integer> set = new HashSet<Integer>();
            for (int j = 0; j < n; j++) {
                if ((1 << j & i) > 0)
                    set.add(a[j]);
            }
            sets.add(set);
        }
        return sets;
    }

    public static void main(String[] args) {
        List<HashSet<Integer>> sets = PowerSet.powerset(new int[]{ 1, 2, 3 });
        for (HashSet<Integer> set : sets) {
            for (int i : set)
                System.out.print(i);
            System.out.println();
        } 
    }
}

0

อีกวิธีหนึ่ง - ด้วย java8 + การสตรีม api มันขี้เกียจและเรียงลำดับดังนั้นจึงส่งคืนชุดย่อยที่ถูกต้องเมื่อใช้กับ "limit ()"

 public long bitRangeMin(int size, int bitCount){
    BitSet bs = new BitSet(size);
    bs.set(0, bitCount);
    return bs.toLongArray()[0];
}

public long bitRangeMax(int size, int bitCount){
    BitSet bs = BitSet.valueOf(new long[]{0});
    bs.set(size - bitCount, size);
    return bs.toLongArray()[0];
}

public <T> Stream<List<T>> powerSet(Collection<T> data)
{
    List<T> list = new LinkedHashSet<>(data).stream().collect(Collectors.toList());
    Stream<BitSet> head = LongStream.of(0).mapToObj( i -> BitSet.valueOf(new long[]{i}));
    Stream<BitSet> tail = IntStream.rangeClosed(1, list.size())
            .boxed()
            .flatMap( v1 -> LongStream.rangeClosed( bitRangeMin(list.size(), v1), bitRangeMax(list.size(), v1))
                    .mapToObj(v2 -> BitSet.valueOf(new long[]{v2}))
                    .filter( bs -> bs.cardinality() == v1));

    return Stream.concat(head, tail)
            .map( bs -> bs
                    .stream()
                    .mapToObj(list::get)
                    .collect(Collectors.toList()));
}

และรหัสไคลเอนต์คือ

@Test
public void testPowerSetOfGivenCollection(){
    List<Character> data = new LinkedList<>();
    for(char i = 'a'; i < 'a'+5; i++ ){
        data.add(i);
    }
    powerSet(data)
            .limit(9)
            .forEach(System.out::print);

}

/ * พิมพ์: [] [a] [b] [c] [d] [e] [a, b] [a, c] [b, c] * /


0

เราสามารถเขียนชุดกำลังโดยใช้หรือไม่ใช้การเรียกซ้ำก็ได้ นี่คือความพยายามโดยไม่มีการเรียกซ้ำ:

public List<List<Integer>> getPowerSet(List<Integer> set) {
    List<List<Integer>> powerSet = new ArrayList<List<Integer>>();
    int max = 1 << set.size();
    for(int i=0; i < max; i++) {
        List<Integer> subSet = getSubSet(i, set);
        powerSet.add(subSet);
    }
    return powerSet;
}

private List<Integer> getSubSet(int p, List<Integer> set) {
    List<Integer> subSet = new ArrayList<Integer>();
    int position = 0;
    for(int i=p; i > 0; i >>= 1) {
        if((i & 1) == 1) {
            subSet.add(set.get(position));
        }
        position++;
    }
    return subSet;
}

0

นี่คือการสร้างชุดพลังงาน แนวคิดคือชุดแรก = S[0]และชุดที่เล็กกว่าS[1,...n]คือ

คำนวณชุดย่อยทั้งหมดของ smallSet และใส่ไว้ในชุดย่อยทั้งหมด

สำหรับแต่ละส่วนย่อยในทุกย่อยให้โคลนและเพิ่มชุดย่อยก่อน

ArrayList<ArrayList<Integer>> getSubsets(ArrayList<Integer> set, int index){
    ArrayList<ArrayList<Integer>> allsubsets;
    if(set.size() == index){
        allsubsets = new ArrayList<ArrayList<Integer>>();
        allsubsets.add(new ArrayList<Integer>()); // the empty set 
    }else{
        allsubsets = getSubsets(set, index+1);
        int item = set.get(index);

        ArrayList<ArrayList<Integer>> moresubsets = new ArrayList<ArrayList<Integer>>();

        for(ArrayList<Integer> subset: allsubsets){
            ArrayList<Integer> newsubset = new ArrayList<Integer>();

            newsubset.addAll(subset);
            newsubset.add(item);
            moresubsets.add(newsubset);

        }

        moresubsets.addAll(moresubsets);

    }

    return allsubsets;
}

0
package problems;

import java.util.ArrayList;
import java.util.List;

public class SubsetFinderRecursive {
    public static void main(String[] args) {
        //input
        int[] input = new int[3];
        for(int i=0; i<input.length; i++) {
            input[i] = i+1;
        }
        // root node of the tree
        Node root = new Node();

        // insert values into tree
        for(int i=0; i<input.length; i++) {
            insertIntoTree(root, input[i]);
        }

        // print leaf nodes for subsets
        printLeafNodes(root);
    }

    static void printLeafNodes(Node root) {

        if(root == null) {
            return;
        }

        // Its a leaf node
        if(root.left == null && root.right == null) {
            System.out.println(root.values);
            return;
        }

        // if we are not at a leaf node, then explore left and right

        if(root.left !=null) {
            printLeafNodes(root.left);
        }

        if(root.right != null) {
            printLeafNodes(root.right);
        }
    }

    static void insertIntoTree(Node root, int value) {

        // Error handling
        if(root == null) {
            return;
        }

        // if there is a sub tree then go down
        if(root.left !=null && root.right != null) {
            insertIntoTree(root.left, value);
            insertIntoTree(root.right, value);
        }

        // if we are at the leaf node, then we have 2 choices
        // Either exclude or include
        if(root.left == null && root.right == null) {
            // exclude
            root.left = new Node();
            root.left.values.addAll(root.values);
            // include
            root.right = new Node();
            root.right.values.addAll(root.values);
            root.right.values.add(value);
            return;
        }
    }

}

class Node {
    Node left;
    Node right;
    List<Integer> values = new ArrayList<Integer>();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.