วิธีสร้างการเปลี่ยนแปลงใน c ++ โดยใช้ STL สำหรับจำนวนที่ต่ำกว่าความยาวทั้งหมด


15

ฉันมีc++ vectorกับstd::pair<unsigned long, unsigned long>วัตถุ std::next_permutation()ฉันพยายามที่จะสร้างวัตถุพีชคณิตของเวกเตอร์โดยใช้ อย่างไรก็ตามฉันต้องการให้การเรียงสับเปลี่ยนมีขนาดตามที่คุณต้องการคล้ายกับpermutationsฟังก์ชันในไพ ธ อนซึ่งมีการระบุขนาดของการเปลี่ยนรูปแบบการส่งคืนที่คาดไว้

โดยทั่วไปc++เทียบเท่ากับ

import itertools

list = [1,2,3,4,5,6,7]
for permutation in itertools.permutations(list, 3):
    print(permutation)

Python Demo

(1, 2, 3)                                                                                                                                                                            
(1, 2, 4)                                                                                                                                                                            
(1, 2, 5)                                                                                                                                                                            
(1, 2, 6)                                                                                                                                                                            
(1, 2, 7)                                                                                                                                                                            
(1, 3, 2)
(1, 3, 4)
..
(7, 5, 4)                                                                                                                                                                            
(7, 5, 6)                                                                                                                                                                            
(7, 6, 1)                                                                                                                                                                            
(7, 6, 2)                                                                                                                                                                            
(7, 6, 3)                                                                                                                                                                            
(7, 6, 4)                                                                                                                                                                            
(7, 6, 5) 

ขอบคุณ @ Jarod42 สำหรับการเพิ่มการสาธิตของไพ ธ อน :)
d4rk4ng31

ฉันต้องทำมันด้วยตัวเองเพราะฉันไม่รู้ผลลัพธ์ของงูหลาม แต่ค่อนข้างแน่ใจว่าฉันรู้วิธีทำใน C ++
Jarod42

ในฐานะที่เป็นบันทึกด้านข้างคุณต้องการจัดการอินพุตที่ซ้ำกันเป็น(1, 1)อย่างไร python permutations จัดทำรายการที่ซ้ำกัน[(1, 1), (1, 1)]ในขณะที่std::next_permutationหลีกเลี่ยงรายการที่ซ้ำกัน (เท่านั้น{1, 1})
Jarod42

เอ่อ .. ไม่ ไม่มีรายการซ้ำ
d4rk4ng31

คำตอบ:


6

คุณอาจใช้ 2 ลูป:

  • ใช้แต่ละ n-tuple
  • วนซ้ำพีชคณิตของ n-tuple
template <typename F, typename T>
void permutation(F f, std::vector<T> v, std::size_t n)
{
    std::vector<bool> bs(v.size() - n, false);
    bs.resize(v.size(), true);
    std::sort(v.begin(), v.end());

    do {
        std::vector<T> sub;
        for (std::size_t i = 0; i != bs.size(); ++i) {
            if (bs[i]) {
                sub.push_back(v[i]);
            }
        }
        do {
            f(sub);
        }
        while (std::next_permutation(sub.begin(), sub.end()));
    } while (std::next_permutation(bs.begin(), bs.end()));
}

การสาธิต


อะไรคือความซับซ้อนของเวลาของรหัสนี้? มันจะเป็น O (places_required * n) สำหรับกรณีทั่วไปและ O (n ^ 2) สำหรับกรณีที่แย่ที่สุดหรือไม่? ฉันยังคาดเดา O (n) สำหรับกรณีที่ดีที่สุดเช่นที่เดียว
d4rk4ng31

2
@ d4rk4ng31: เราพบการเปลี่ยนแปลงแต่ละครั้งเพียงครั้งเดียว ความซับซ้อนของการstd::next_permutationเป็น "ชัดเจน" ตามที่นับการแลกเปลี่ยน (เชิงเส้น) การแยกเวกเตอร์ย่อยสามารถปรับปรุงได้ แต่ฉันไม่คิดว่ามันจะเปลี่ยนความซับซ้อน นอกจากนี้การเปลี่ยนแปลงจำนวนขึ้นอยู่กับขนาดของเวกเตอร์ดังนั้นพารามิเตอร์ 2 จึงไม่ขึ้นกับ
Jarod42

ไม่ควรจะเป็นอย่างนั้นstd::vector<T>& v?
LF

@LF: มันมีวัตถุประสงค์ ฉันคิดว่าฉันไม่จำเป็นต้องเปลี่ยนค่าของผู้โทร (ฉันเรียงลำดับvในขณะนี้) ฉันอาจผ่านการอ้างอิง const และสร้างสำเนาคัดลอกในร่างกายแทน
Jarod42

@ Jarod42 โอ้ขอโทษฉันอ่านรหัสผิด ใช่การผ่านคุณค่าเป็นสิ่งที่ถูกต้องที่จะทำที่นี่
LF

4

หากประสิทธิภาพไม่ใช่ประเด็นหลักเราสามารถย้ำกว่าพีชคณิตทั้งหมดและข้ามสิ่งที่แตกต่างกันในคำต่อท้ายโดยเลือกเฉพาะแต่ละ(N - k)!รายการ ตัวอย่างเช่นสำหรับN = 4, k = 2เรามีวิธีเรียงสับเปลี่ยน:

12 34 <
12 43
13 24 <
13 42
14 23 <
14 32
21 34 <
21 43
23 14 <
23 41
24 13 <
24 31
...

ที่ฉันแทรกพื้นที่เพื่อความชัดเจนและมีการทำเครื่องหมายในแต่ละ(N-k)! = 2! = 2การเปลี่ยนแปลง -nd <กับ

std::size_t fact(std::size_t n) {
    std::size_t f = 1;
    while (n > 0)
        f *= n--;
    return f;
}

template<class It, class Fn>
void generate_permutations(It first, It last, std::size_t k, Fn fn) {
    assert(std::is_sorted(first, last));

    const std::size_t size = static_cast<std::size_t>(last - first);
    assert(k <= size);

    const std::size_t m = fact(size - k);
    std::size_t i = 0;
    do {
        if (i++ == 0)
            fn(first, first + k);
        i %= m;
    }
    while (std::next_permutation(first, last));
}

int main() {
    std::vector<int> vec{1, 2, 3, 4};
    generate_permutations(vec.begin(), vec.end(), 2, [](auto first, auto last) {
        for (; first != last; ++first)
            std::cout << *first;
        std::cout << ' ';
    });
}

เอาท์พุท:

12 13 14 21 23 24 31 32 34 41 42 43

3

นี่คืออัลกอริทึมที่มีประสิทธิภาพที่ไม่ได้ใช้std::next_permutationโดยตรง แต่ใช้ประโยชน์จากม้าทำงานของฟังก์ชันนั้น นั่นคือและstd::swap std::reverseในฐานะที่เป็นบวกก็อยู่ในการสั่งซื้อพจนานุกรม

#include <iostream>
#include <vector>
#include <algorithm>

void nextPartialPerm(std::vector<int> &z, int n1, int m1) {

    int p1 = m1 + 1;

    while (p1 <= n1 && z[m1] >= z[p1])
        ++p1;

    if (p1 <= n1) {
        std::swap(z[p1], z[m1]);
    } else {
        std::reverse(z.begin() + m1 + 1, z.end());
        p1 = m1;

        while (z[p1 + 1] <= z[p1])
            --p1;

        int p2 = n1;

        while (z[p2] <= z[p1])
            --p2;

        std::swap(z[p1], z[p2]);
        std::reverse(z.begin() + p1 + 1, z.end());
    }
}

และเรียกมันว่าเรามี:

int main() {
    std::vector<int> z = {1, 2, 3, 4, 5, 6, 7};
    int m = 3;
    int n = z.size();

    const int nMinusK = n - m;
    int numPerms = 1;

    for (int i = n; i > nMinusK; --i)
        numPerms *= i;

    --numPerms;

    for (int i = 0; i < numPerms; ++i) {
        for (int j = 0; j < m; ++j)
            std::cout << z[j] << ' ';

        std::cout << std::endl;
        nextPartialPerm(z, n - 1, m - 1);
    }

    // Print last permutation
    for (int j = 0; j < m; ++j)
            std::cout << z[j] << ' ';

    std::cout << std::endl;

    return 0;
}

นี่คือผลลัพธ์:

1 2 3 
1 2 4 
1 2 5 
1 2 6 
1 2 7
.
.
.
7 5 6 
7 6 1 
7 6 2 
7 6 3 
7 6 4 
7 6 5

นี่คือโค้ดที่รันได้จากideone


2
คุณสามารถเลียนแบบได้มากขึ้นด้วยลายเซ็นbool nextPartialPermutation(It begin, It mid, It end)
Jarod42

2
การสาธิต
Jarod42

@ Jarod42 นั่นเป็นทางออกที่ดีจริงๆ คุณควรเพิ่มเป็นคำตอบ ...
โจเซฟวู้ด

แนวคิดเริ่มต้นของฉันคือการปรับปรุงคำตอบของคุณ แต่ตกลงเพิ่ม
Jarod42

3

การเปิดคำตอบ Joseph Wood ด้วยอินเทอร์เฟซตัววนซ้ำคุณอาจมีวิธีการคล้ายกับstd::next_permutation:

template <typename IT>
bool next_partial_permutation(IT beg, IT mid, IT end) {
    if (beg == mid) { return false; }
    if (mid == end) { return std::next_permutation(beg, end); }

    auto p1 = mid;

    while (p1 != end && !(*(mid - 1) < *p1))
        ++p1;

    if (p1 != end) {
        std::swap(*p1, *(mid - 1));
        return true;
    } else {
        std::reverse(mid, end);
        auto p3 = std::make_reverse_iterator(mid);

        while (p3 != std::make_reverse_iterator(beg) && !(*p3 < *(p3 - 1)))
            ++p3;

        if (p3 == std::make_reverse_iterator(beg)) {
            std::reverse(beg, end);
            return false;
        }

        auto p2 = end - 1;

        while (!(*p3 < *p2))
            --p2;

        std::swap(*p3, *p2);
        std::reverse(p3.base(), end);
        return true;
    }
}

การสาธิต


1

นี่คือทางออกของฉันหลังจากที่คิด

#include <algorithm>
#include <iostream>
#include <set>
#include <vector>

int main() {
    std::vector<int> job_list;
    std::set<std::vector<int>> permutations;
    for (unsigned long i = 0; i < 7; i++) {
        int job;
        std::cin >> job;
        job_list.push_back(job);
    }
    std::sort(job_list.begin(), job_list.end());
    std::vector<int> original_permutation = job_list;
    do {
        std::next_permutation(job_list.begin(), job_list.end());
        permutations.insert(std::vector<int>(job_list.begin(), job_list.begin() + 3));
    } while (job_list != original_permutation);

    for (auto& permutation : permutations) {
        for (auto& pair : permutation) {
            std::cout << pair << " ";
        }
        std::endl(std::cout);
    }

    return 0;
}

กรุณาแสดงความคิดเห็นความคิดของคุณ


2
ไม่เท่ากับเหมืองของฉันมันเปรียบได้กับคำตอบของ Evg มากกว่า (แต่ Evg ข้ามซ้ำซ้อนได้อย่างมีประสิทธิภาพ) permuteในความเป็นจริงอาจจะset.insert(vec);ลบปัจจัยใหญ่เท่านั้น
Jarod42

ความซับซ้อนของเวลาตอนนี้คืออะไร?
d4rk4ng31

1
ฉันจะบอกว่าO(nb_total_perm * log(nb_res))( nb_total_permซึ่งส่วนใหญ่factorial(job_list.size())และnb_resขนาดของผลpermutations.size():) ดังนั้นยังคงมีขนาดใหญ่เกินไป (แต่ตอนนี้คุณจัดการข้อมูลที่ซ้ำกันที่ตรงกันข้ามกับ Evg)
Jarod42
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.