การเปลี่ยนแปลงอย่างรวดเร็ว -> จำนวน -> อัลกอริทึมการจับคู่การเปลี่ยนแปลง


113

ฉันมี n องค์ประกอบ ยกตัวอย่างเช่น 7 องค์ประกอบ 1234567 ฉันรู้ว่ามี 7! = 5040 การเรียงสับเปลี่ยนที่เป็นไปได้ของ 7 องค์ประกอบเหล่านี้

ฉันต้องการอัลกอริทึมที่รวดเร็วซึ่งประกอบด้วยสองฟังก์ชัน:

f (ตัวเลข) จับคู่ตัวเลขระหว่าง 0 ถึง 5039 กับการเรียงสับเปลี่ยนที่ไม่ซ้ำกันและ

f '(การเรียงสับเปลี่ยน) แมปการเปลี่ยนแปลงกลับไปยังหมายเลขที่สร้างขึ้น

ฉันไม่สนใจเกี่ยวกับความสอดคล้องระหว่างตัวเลขและการเรียงสับเปลี่ยนการให้การเรียงสับเปลี่ยนแต่ละครั้งมีหมายเลขเฉพาะของตัวเอง

ตัวอย่างเช่นฉันอาจมีฟังก์ชันที่

f(0) = '1234567'
f'('1234567') = 0

อัลกอริทึมที่เร็วที่สุดที่นึกถึงคือการแจกแจงการเรียงสับเปลี่ยนทั้งหมดและสร้างตารางการค้นหาในทั้งสองทิศทางดังนั้นเมื่อสร้างตารางแล้ว f (0) จะเป็น O (1) และ f ('1234567') จะเป็น a ค้นหาสตริง อย่างไรก็ตามนี่คือความจำหิวโดยเฉพาะอย่างยิ่งเมื่อ n มีขนาดใหญ่

ใครสามารถเสนออัลกอริทึมอื่นที่จะทำงานได้อย่างรวดเร็วและไม่มีข้อเสียของหน่วยความจำ?


แม้ว่าอัลกอริทึมด้านล่างจะครอบคลุมมาก แต่คุณชี้ให้เห็นอย่างถูกต้องว่าอัลกอริทึมที่เร็วที่สุดคือตารางการค้นหา คุณไม่ได้พูดถึงหน่วยความจำที่ 'มากขนาดนั้น' แม้ว่าแน่นอนว่ามันขึ้นอยู่กับระบบและแพลตฟอร์มของคุณ แต่ถ้าตารางการค้นหาจะเพียงพอและหากนี่เป็นแอปพลิเคชันในโลกแห่งความเป็นจริงให้ใช้ ง่ายและรวดเร็ว!
Kirk Broadhurst

14
คุณพูดแบบนั้น แต่ n ไม่จำเป็นต้องใหญ่มากเพื่อให้มันโง่ สำหรับ 12 องค์ 12! คือ 479,001,600 เรียงสับเปลี่ยน นั่นคือตารางการค้นหาขนาดใหญ่!
ijw

อย่าสับสนกับโพสต์ที่แตกต่างกันใช้ n สำหรับความหมายที่แตกต่างกัน n บางตัวยืนสำหรับความยาวสตริงบางตัว n หมายถึงจำนวนการเรียงสับเปลี่ยนที่เป็นไปได้ อย่าเปรียบเทียบแนวคิด O ใหญ่ ๆ แบบสุ่มสี่สุ่มห้า - เตือนผู้มาสาย - -
把友情留在无盐

คำตอบ:


157

ในการอธิบายการเรียงสับเปลี่ยนขององค์ประกอบ n คุณจะเห็นว่าสำหรับตำแหน่งที่องค์ประกอบแรกลงเอยที่คุณมีความเป็นไปได้ n ดังนั้นคุณจึงสามารถอธิบายสิ่งนี้ด้วยตัวเลขระหว่าง 0 ถึง n-1 สำหรับตำแหน่งที่องค์ประกอบถัดไปไปสิ้นสุดที่คุณมีความเป็นไปได้ที่เหลืออยู่ n-1 ดังนั้นคุณสามารถอธิบายสิ่งนี้ด้วยตัวเลขระหว่าง 0 ถึง n-2
และอื่น ๆ จนกว่าคุณจะมี n ตัวเลข

เป็นตัวอย่างสำหรับ n = 5 พิจารณาการเปลี่ยนแปลงที่นำไปabcdecaebd

  • aองค์ประกอบแรกจบลงในตำแหน่งที่สองดังนั้นเราจึงกำหนดดัชนี1
  • bปลายขึ้นที่ตำแหน่งที่สี่ซึ่งจะเป็นดัชนีที่ 3 แต่ก็เป็นที่สามที่เหลือหนึ่งดังนั้นเราจึงกำหนด2
  • cปลายขึ้นที่ตำแหน่งแรกที่เหลือซึ่งเป็นเสมอ0
  • dจบลงที่ตำแหน่งสุดท้ายที่เหลืออยู่ซึ่ง (out of เหลืออยู่เพียงสองตำแหน่ง) เป็น1
  • eจบลงที่ตำแหน่งเดียวที่เหลืออยู่, การจัดทำดัชนีที่0

ดังนั้นเราจึงมีลำดับดัชนี{1, 2, 0, 1, 0}

ตอนนี้คุณรู้แล้วว่าตัวอย่างเช่นในเลขฐานสอง 'xyz' หมายถึง z + 2y + 4x สำหรับเลขฐานสิบ
คือ z + 10y + 100x ตัวเลขแต่ละตัวจะถูกคูณด้วยน้ำหนักบางส่วนและผลลัพธ์จะถูกสรุป รูปแบบที่ชัดเจนของน้ำหนักแน่นอนว่าน้ำหนักคือ w = b ^ k โดย b เป็นฐานของตัวเลขและ k ดัชนีของตัวเลข (ฉันจะนับหลักจากทางขวาเสมอและเริ่มต้นที่ดัชนี 0 สำหรับตัวเลขทางขวาสุดในทำนองเดียวกันเมื่อฉันพูดถึงหลัก 'แรก' ฉันหมายถึงขวาสุด)

เหตุผลว่าทำไมน้ำหนักสำหรับตัวเลขตามรูปแบบนี้คือการที่มีจำนวนมากที่สุดที่สามารถแทนด้วยตัวเลขจาก 0 ถึง k จะต้องตรง 1 ต่ำกว่าจำนวนต่ำสุดที่สามารถแสดงโดยเฉพาะการใช้หลัก k + 1 ในไบนารี 0111 ต้องต่ำกว่า 1,000 หนึ่งในทศนิยม 099999 ต้องต่ำกว่า 100000 หนึ่งรายการ

การเข้ารหัสเป็นตัวแปรฐาน
ระยะห่างระหว่างตัวเลขที่ตามมาคือ 1 เป็นกฎที่สำคัญ ตระหนักถึงนี้เราสามารถแสดงลำดับของดัชนีของเราโดยจำนวนตัวแปรฐาน ฐานของแต่ละหลักคือจำนวนของความเป็นไปได้ที่แตกต่างกันสำหรับตัวเลขนั้น สำหรับทศนิยมแต่ละหลักมีความเป็นไปได้ 10 สำหรับระบบของเราตัวเลขที่อยู่ขวาสุดจะมี 1 ความเป็นไปได้และทางซ้ายสุดจะมีความเป็นไปได้ n แต่เนื่องจากตัวเลขทางขวาสุด (ตัวเลขสุดท้ายในลำดับของเรา) เป็น 0 เสมอเราจึงปล่อยมันออกไป นั่นหมายความว่าเราเหลือฐาน 2 ถึง n โดยทั่วไปหลัก k จะมีฐาน b [k] = k + 2 ค่าสูงสุดที่อนุญาตสำหรับหลัก k คือ h [k] = b [k] - 1 = k + 1

กฎของเราเกี่ยวกับน้ำหนัก w [k] ของหลักกำหนดให้ผลรวมของ h [i] * w [i] โดยที่ฉันไปจาก i = 0 ถึง i = k มีค่าเท่ากับ 1 * w [k + 1] ระบุเป็นประจำ w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1) น้ำหนักตัวแรก w [0] ควรเป็น 1 เสมอเริ่มจากตรงนั้นเรามีค่าต่อไปนี้:

k    h[k] w[k]    

0    1    1  
1    2    2    
2    3    6    
3    4    24   
...  ...  ...
n-1  n    n!  

(ความสัมพันธ์ทั่วไป w [k-1] = k! พิสูจน์ได้ง่ายโดยการเหนี่ยวนำ)

จำนวนที่เราได้รับจากการแปลงลำดับของเราจะเป็นผลรวมของ s [k] * w [k] โดย k วิ่งจาก 0 ถึง n-1 นี่คือ [k] คือองค์ประกอบ k'th (ขวาสุดเริ่มต้นที่ 0) ของลำดับ ตัวอย่างเช่นการใช้เวลาของเรา {1, 2, 0, 1, 0} กับองค์ประกอบขวาสุดถอดดังกล่าวก่อน: {1, 2, 0, 1} ผลรวมของเราคือ * 1 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37

โปรดทราบว่าหากเรารับตำแหน่งสูงสุดสำหรับทุกดัชนีเราจะมี {4, 3, 2, 1, 0} และจะแปลงเป็น 119 เนื่องจากน้ำหนักในการเข้ารหัสตัวเลขของเราถูกเลือกเพื่อที่เราจะไม่ข้าม ตัวเลขใด ๆ ตัวเลขทั้งหมด 0 ถึง 119 ถูกต้อง มีจำนวน 120 ตัวซึ่งเป็น n! สำหรับ n = 5 ในตัวอย่างของเราจำนวนการเรียงสับเปลี่ยนที่แตกต่างกันอย่างแม่นยำ ดังนั้นคุณจะเห็นตัวเลขที่เข้ารหัสของเราระบุการเรียงสับเปลี่ยนที่เป็นไปได้ทั้งหมด

การถอดรหัสจากการถอดรหัสฐานตัวแปร
คล้ายกับการแปลงเป็นเลขฐานสองหรือฐานสิบ อัลกอริทึมทั่วไปมีดังนี้:

int number = 42;
int base = 2;
int[] bits = new int[n];

for (int k = 0; k < bits.Length; k++)
{
    bits[k] = number % base;
    number = number / base;
}

สำหรับเลขฐานตัวแปรของเรา:

int n = 5;
int number = 37;

int[] sequence = new int[n - 1];
int base = 2;

for (int k = 0; k < sequence.Length; k++)
{
    sequence[k] = number % base;
    number = number / base;

    base++; // b[k+1] = b[k] + 1
}

สิ่งนี้จะถอดรหัส 37 ของเรากลับไปเป็น {1, 2, 0, 1} sequenceได้อย่างถูกต้อง( จะอยู่{1, 0, 2, 1}ในตัวอย่างโค้ดนี้ แต่อะไรก็ตาม ... ตราบใดที่คุณทำดัชนีอย่างเหมาะสม) เราเพียงแค่ต้องเพิ่ม 0 ที่ด้านขวาสุด (โปรดจำไว้ว่าองค์ประกอบสุดท้ายจะมีความเป็นไปได้เพียงอย่างเดียวสำหรับตำแหน่งใหม่) เพื่อเรียกลำดับเดิมของเรากลับมา {1, 2, 0, 1, 0}

การอนุญาตรายการโดยใช้ลำดับดัชนี
คุณสามารถใช้อัลกอริทึมด้านล่างเพื่ออนุญาตรายการตามลำดับดัชนีเฉพาะ มันเป็นอัลกอริทึม O (n²) แต่น่าเสียดาย

int n = 5;
int[] sequence = new int[] { 1, 2, 0, 1, 0 };
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
bool[] set = new bool[n];

for (int i = 0; i < n; i++)
{
    int s = sequence[i];
    int remainingPosition = 0;
    int index;

    // Find the s'th position in the permuted list that has not been set yet.
    for (index = 0; index < n; index++)
    {
        if (!set[index])
        {
            if (remainingPosition == s)
                break;

            remainingPosition++;
        }
    }

    permuted[index] = list[i];
    set[index] = true;
}

การแทนค่าทั่วไปของการเรียงสับเปลี่ยน
โดยปกติคุณจะไม่แสดงการเปลี่ยนแปลงโดยไม่ได้ตั้งใจเหมือนที่เราเคยทำ แต่เป็นเพียงตำแหน่งที่แน่นอนของแต่ละองค์ประกอบหลังจากที่มีการใช้การเรียงสับเปลี่ยน ตัวอย่างของเรา {1, 2, 0, 1, 0} สำหรับabcdeto caebdจะแสดงโดย {1, 3, 0, 4, 2} แต่ละดัชนีตั้งแต่ 0 ถึง 4 (หรือโดยทั่วไป 0 ถึง n-1) เกิดขึ้นเพียงครั้งเดียวในการแทนค่านี้

การใช้การเรียงสับเปลี่ยนในรูปแบบนี้ทำได้ง่าย:

int[] permutation = new int[] { 1, 3, 0, 4, 2 };

char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];

for (int i = 0; i < n; i++)
{
    permuted[permutation[i]] = list[i];
}

การเปลี่ยนกลับคล้ายกันมาก:

for (int i = 0; i < n; i++)
{
    list[i] = permuted[permutation[i]];
}

การแปลงจากการเป็นตัวแทนของเราเป็นการแสดงทั่วไป
โปรดทราบว่าหากเราใช้อัลกอริทึมของเราในการจัดเรียงรายการโดยใช้ลำดับดัชนีของเราและนำไปใช้กับการเปลี่ยนแปลงข้อมูลประจำตัว {0, 1, 2, ... , n-1} เราจะได้รับการเปลี่ยนแปลงผกผันแสดงในรูปแบบทั่วไป ( {2, 0, 4, 1, 3}ในตัวอย่างของเรา)

เพื่อให้ได้การสร้างสมมติฐานแบบไม่กลับหัวเราใช้อัลกอริทึมการเปลี่ยนแปลงที่ฉันเพิ่งแสดง:

int[] identity = new int[] { 0, 1, 2, 3, 4 };
int[] inverted = { 2, 0, 4, 1, 3 };
int[] normal = new int[n];

for (int i = 0; i < n; i++)
{
    normal[identity[i]] = list[i];
}

หรือคุณสามารถใช้การเปลี่ยนแปลงโดยตรงโดยใช้อัลกอริธึมการเปลี่ยนแปลงผกผัน:

char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];

int[] inverted = { 2, 0, 4, 1, 3 };

for (int i = 0; i < n; i++)
{
    permuted[i] = list[inverted[i]];
}

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


6
ใน "การอนุญาตรายการโดยใช้ลำดับดัชนี" คุณกล่าวถึงอัลกอริทึมกำลังสอง นี่เป็นเรื่องปกติเพราะ n น่าจะน้อยมาก สิ่งนี้สามารถ "ลด" เป็น O (nlogn) ได้อย่างง่ายดายแม้ว่าจะผ่านแผนภูมิสถิติคำสั่งซื้อ ( pine.cs.yale.edu/pinewiki/OrderStatisticsTree ) นั่นคือต้นไม้สีแดง - ดำซึ่งในตอนแรกจะมีค่า 0, 1, 2 , ... , n-1 และแต่ละโหนดมีจำนวนลูกหลานที่อยู่ด้านล่าง ด้วยสิ่งนี้เราสามารถค้นหา / ลบองค์ประกอบ kth ในเวลา O (logn)
Dimitris Andreou

11
สิ่งเหล่านี้เรียกว่ารหัส lehmer ลิงค์นี้ยังอธิบายได้ดีkeithschwarz.com/interesting/code/?dir=factoradic-permutation
mihirg

อัลกอริทึมนี้ยอดเยี่ยมมาก แต่ฉันพบว่ามีหลายกรณีที่ไม่ถูกต้อง ใช้สตริง "123"; การเรียงสับเปลี่ยนครั้งที่ 4 ควรเป็น 231 แต่ตามอัลกอริทึมนี้จะเป็น 312 พูดว่า 1234 การเรียงสับเปลี่ยนครั้งที่ 4 ควรเป็น 1342 แต่จะเข้าใจผิดว่าเป็น "1423" แก้ไขฉันถ้าฉันสังเกตผิด ขอบคุณ
Isaac Li

@IsaacLi ถ้าฉันถูกต้อง f (4) = {2, 0, 0} = 231 และ f '(312) = {1, 1, 0} = 3 สำหรับ1234, f (4) = {0, 2, 0, 0} = 1342 และ f '(1423) = {0, 1 1, 0} = 3 อัลกอริทึมนี้สร้างแรงบันดาลใจมาก สงสัยเป็นผลงานต้นฉบับจาก OP ฉันได้ศึกษาและวิเคราะห์มาระยะหนึ่งแล้ว และฉันเชื่อว่ามันถูกต้อง :)
มิดไนต์

วิธีการแปลงจาก "การเป็นตัวแทนของเรา" เป็น "การแสดงทั่วไป", {1, 2, 0, 1, 0}-> {1, 3, 0, 4, 2}? และในทางกลับกัน? เป็นไปได้ไหม? (โดยไม่แปลงระหว่าง{1, 2, 0, 1, 0}<--> {C, A, E, B, D}ซึ่งต้องการ O (n ^ 2)) หาก "สไตล์ของเรา" และ "สไตล์ทั่วไป" ไม่สามารถเปลี่ยนแปลงได้อันที่จริงแล้วสิ่งเหล่านี้เป็นสองสิ่งที่แตกต่างกันใช่หรือไม่ ขอบคุณ x
midnite

18

ฉันพบอัลกอริทึม O (n) นี่คือคำอธิบายสั้น ๆhttp://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html

public static int[] perm(int n, int k)
{
    int i, ind, m=k;
    int[] permuted = new int[n];
    int[] elems = new int[n];

    for(i=0;i<n;i++) elems[i]=i;

    for(i=0;i<n;i++)
    {
            ind=m%(n-i);
            m=m/(n-i);
            permuted[i]=elems[ind];
            elems[ind]=elems[n-i-1];
    }

    return permuted;
}

public static int inv(int[] perm)
{
    int i, k=0, m=1;
    int n=perm.length;
    int[] pos = new int[n];
    int[] elems = new int[n];

    for(i=0;i<n;i++) {pos[i]=i; elems[i]=i;}

    for(i=0;i<n-1;i++)
    {
            k+=m*pos[perm[i]];
            m=m*(n-i);
            pos[elems[n-i-1]]=pos[perm[i]];
            elems[pos[perm[i]]]=elems[n-i-1];
    }

    return k;
}

1
ถ้าฉันเข้าใจอัลกอริทึมของคุณเป็นอย่างดี คุณกำลังค้นหาความเป็นไปได้ทั้งหมดที่เข้ารหัส (ในกรณีนี้ควรเป็น n! เป็นไปได้) จากนั้นคุณจับคู่ตัวเลขตามรายการที่เข้ารหัส
user3378649

ฉันเพิ่มคำอธิบายสั้น ๆ ในบล็อกของฉัน
Antoine Comeau

1
นี่คือความเรียบร้อยเป็นพิเศษ วันนี้ฉันคิดวิธีการเดียวกันนี้ขึ้นมาเอง แต่ฉันพลาดที่คุณสามารถทิ้งงานสองอย่างในทางตรงกันข้ามได้
fuz

อย่าเปรียบเทียบแนวคิด O ขนาดใหญ่แบบสุ่มสี่สุ่มห้าเนื่องจาก n ในคำตอบนี้ไม่เหมือนกับคำตอบอื่น ๆ - ดังที่ @ user3378649 ชี้ให้เห็น - แสดงถึงสัดส่วนความซับซ้อนของความยาวของสตริง คำตอบนี้มีประสิทธิภาพน้อยกว่า
把友情留在无盐

สิ่งนี้สามารถปรับให้เข้ากับลำดับศัพท์ได้หรือไม่?
Gregory Morse

7

สามารถลดความซับซ้อนลงได้ที่ n * log (n) ดูหัวข้อ 10.1.1 ("The Lehmer code (inversion table)", p.232ff) ของ fxtbook: http://www.jjj.de/fxt/ #fxtbook ข้ามไปที่ส่วน 10.1.1.1 ("การคำนวณด้วยอาร์เรย์ขนาดใหญ่" น. 235) สำหรับวิธีการที่รวดเร็ว โค้ด (GPLed, C ++) อยู่บนหน้าเว็บเดียวกัน


5

แก้ไขปัญหา. อย่างไรก็ตามฉันไม่แน่ใจว่าคุณยังต้องการวิธีแก้ไขหลังจากผ่านไปหลายปี ฮ่า ๆ ฉันเพิ่งเข้าร่วมไซต์นี้ดังนั้น ... ตรวจสอบ Java Permutation Class ของฉัน คุณสามารถใช้ดัชนีเพื่อรับการเปลี่ยนแปลงสัญลักษณ์หรือให้การเรียงสับเปลี่ยนสัญลักษณ์แล้วรับดัชนี

นี่คือคลาส Premutation ของฉัน

/**
 ****************************************************************************************************************
 * Copyright 2015 Fred Pang fred@pnode.com
 ****************************************************************************************************************
 * A complete list of Permutation base on an index.
 * Algorithm is invented and implemented by Fred Pang fred@pnode.com
 * Created by Fred Pang on 18/11/2015.
 ****************************************************************************************************************
 * LOL this is my first Java project. Therefore, my code is very much like C/C++. The coding itself is not
 * very professional. but...
 *
 * This Permutation Class can be use to generate a complete list of all different permutation of a set of symbols.
 * nPr will be n!/(n-r)!
 * the user can input       n = the number of items,
 *                          r = the number of slots for the items,
 *                          provided n >= r
 *                          and a string of single character symbols
 *
 * the program will generate all possible permutation for the condition.
 *
 * Say if n = 5, r = 3, and the string is "12345", it will generate sll 60 different permutation of the set
 * of 3 character strings.
 *
 * The algorithm I used is base on a bin slot.
 * Just like a human or simply myself to generate a permutation.
 *
 * if there are 5 symbols to chose from, I'll have 5 bin slot to indicate which symbol is taken.
 *
 * Note that, once the Permutation object is initialized, or after the constructor is called, the permutation
 * table and all entries are defined, including an index.
 *
 * eg. if pass in value is 5 chose 3, and say the symbol string is "12345"
 * then all permutation table is logically defined (not physically to save memory).
 * It will be a table as follows
 *  index  output
 *      0   123
 *      1   124
 *      2   125
 *      3   132
 *      4   134
 *      5   135
 *      6   143
 *      7   145
 *      :     :
 *      58  542
 *      59  543
 *
 * all you need to do is call the "String PermGetString(int iIndex)" or the "int[] PermGetIntArray(int iIndex)"
 * function or method with an increasing iIndex, starting from 0 to getiMaxIndex() - 1. It will return the string
 * or the integer array corresponding to the index.
 *
 * Also notice that in the input string is "12345" of  position 01234, and the output is always in accenting order
 * this is how the permutation is generated.
 *
 * ***************************************************************************************************************
 * ====  W a r n i n g  ====
 * ***************************************************************************************************************
 *
 * There is very limited error checking in this class
 *
 * Especially the  int PermGetIndex(int[] iInputArray)  method
 * if the input integer array contains invalid index, it WILL crash the system
 *
 * the other is the string of symbol pass in when the object is created, not sure what will happen if the
 * string is invalid.
 * ***************************************************************************************************************
 *
 */
public class Permutation
{
    private boolean bGoodToGo = false;      // object status
    private boolean bNoSymbol = true;
    private BinSlot slot;                   // a bin slot of size n (input)
    private int nTotal;                     // n number for permutation
    private int rChose;                     // r position to chose
    private String sSymbol;                 // character string for symbol of each choice
    private String sOutStr;
    private int iMaxIndex;                  // maximum index allowed in the Get index function
    private int[] iOutPosition;             // output array
    private int[] iDivisorArray;            // array to do calculation

    public Permutation(int inCount, int irCount, String symbol)
    {
        if (inCount >= irCount)
        {
            // save all input values passed in
            this.nTotal = inCount;
            this.rChose = irCount;
            this.sSymbol = symbol;

            // some error checking
            if (inCount < irCount || irCount <= 0)
                return;                                 // do nothing will not set the bGoodToGo flag

            if (this.sSymbol.length() >= inCount)
            {
                bNoSymbol = false;
            }

            // allocate output storage
            this.iOutPosition = new int[this.rChose];

            // initialize the bin slot with the right size
            this.slot = new BinSlot(this.nTotal);

            // allocate and initialize divid array
            this.iDivisorArray = new int[this.rChose];

            // calculate default values base on n & r
            this.iMaxIndex = CalPremFormula(this.nTotal, this.rChose);

            int i;
            int j = this.nTotal - 1;
            int k = this.rChose - 1;

            for (i = 0; i < this.rChose; i++)
            {
                this.iDivisorArray[i] = CalPremFormula(j--, k--);
            }
            bGoodToGo = true;       // we are ready to go
        }
    }

    public String PermGetString(int iIndex)
    {
        if (!this.bGoodToGo) return "Error: Object not initialized Correctly";
        if (this.bNoSymbol) return "Error: Invalid symbol string";
        if (!this.PermEvaluate(iIndex)) return "Invalid Index";

        sOutStr = "";
        // convert string back to String output
        for (int i = 0; i < this.rChose; i++)
        {
            String sTempStr = this.sSymbol.substring(this.iOutPosition[i], iOutPosition[i] + 1);
            this.sOutStr = this.sOutStr.concat(sTempStr);
        }
        return this.sOutStr;
    }

    public int[] PermGetIntArray(int iIndex)
    {
        if (!this.bGoodToGo) return null;
        if (!this.PermEvaluate(iIndex)) return null ;
        return this.iOutPosition;
    }

    // given an int array, and get the index back.
    //
    //  ====== W A R N I N G ======
    //
    // there is no error check in the array that pass in
    // if any invalid value in the input array, it can cause system crash or other unexpected result
    //
    // function pass in an int array generated by the PermGetIntArray() method
    // then return the index value.
    //
    // this is the reverse of the PermGetIntArray()
    //
    public int PermGetIndex(int[] iInputArray)
    {
        if (!this.bGoodToGo) return -1;
        return PermDoReverse(iInputArray);
    }


    public int getiMaxIndex() {
    return iMaxIndex;
}

    // function to evaluate nPr = n!/(n-r)!
    public int CalPremFormula(int n, int r)
    {
        int j = n;
        int k = 1;
        for (int i = 0; i < r; i++, j--)
        {
            k *= j;
        }
        return k;
    }


//  PermEvaluate function (method) base on an index input, evaluate the correspond permuted symbol location
//  then output it to the iOutPosition array.
//
//  In the iOutPosition[], each array element corresponding to the symbol location in the input string symbol.
//  from location 0 to length of string - 1.

    private boolean PermEvaluate(int iIndex)
    {
        int iCurrentIndex;
        int iCurrentRemainder;
        int iCurrentValue = iIndex;
        int iCurrentOutSlot;
        int iLoopCount;

        if (iIndex >= iMaxIndex)
            return false;

        this.slot.binReset();               // clear bin content
        iLoopCount = 0;
        do {
            // evaluate the table position
            iCurrentIndex = iCurrentValue / this.iDivisorArray[iLoopCount];
            iCurrentRemainder = iCurrentValue % this.iDivisorArray[iLoopCount];

            iCurrentOutSlot = this.slot.FindFreeBin(iCurrentIndex);     // find an available slot
            if (iCurrentOutSlot >= 0)
                this.iOutPosition[iLoopCount] = iCurrentOutSlot;
            else return false;                                          // fail to find a slot, quit now

            this.slot.setStatus(iCurrentOutSlot);                       // set the slot to be taken
            iCurrentValue = iCurrentRemainder;                          // set new value for current value.
            iLoopCount++;                                               // increase counter
        } while (iLoopCount < this.rChose);

        // the output is ready in iOutPosition[]
        return true;
    }

    //
    // this function is doing the reverse of the permutation
    // the input is a permutation and will find the correspond index value for that entry
    // which is doing the opposit of the PermEvaluate() method
    //
    private int PermDoReverse(int[] iInputArray)
    {
        int iReturnValue = 0;
        int iLoopIndex;
        int iCurrentValue;
        int iBinLocation;

        this.slot.binReset();               // clear bin content

        for (iLoopIndex = 0; iLoopIndex < this.rChose; iLoopIndex++)
        {
            iCurrentValue = iInputArray[iLoopIndex];
            iBinLocation = this.slot.BinCountFree(iCurrentValue);
            this.slot.setStatus(iCurrentValue);                          // set the slot to be taken
            iReturnValue = iReturnValue + iBinLocation * this.iDivisorArray[iLoopIndex];
        }
        return iReturnValue;
    }


    /*******************************************************************************************************************
     *******************************************************************************************************************
     * Created by Fred on 18/11/2015.   fred@pnode.com
     *
     * *****************************************************************************************************************
     */
    private static class BinSlot
    {
        private int iBinSize;       // size of array
        private short[] eStatus;    // the status array must have length iBinSize

        private BinSlot(int iBinSize)
        {
            this.iBinSize = iBinSize;               // save bin size
            this.eStatus = new short[iBinSize];     // llocate status array
        }

        // reset the bin content. no symbol is in use
        private void binReset()
        {
            // reset the bin's content
            for (int i = 0; i < this.iBinSize; i++) this.eStatus[i] = 0;
        }

        // set the bin position as taken or the number is already used, cannot be use again.
        private void  setStatus(int iIndex) { this.eStatus[iIndex]= 1; }

        //
        // to search for the iIndex th unused symbol
        // this is important to search through the iindex th symbol
        // because this is how the table is setup. (or the remainder means)
        // note: iIndex is the remainder of the calculation
        //
        // for example:
        // in a 5 choose 3 permutation symbols "12345",
        // the index 7 item (count starting from 0) element is "1 4 3"
        // then comes the index 8, 8/12 result 0 -> 0th symbol in symbol string = '1'
        // remainder 8. then 8/3 = 2, now we need to scan the Bin and skip 2 unused bins
        //              current the bin looks 0 1 2 3 4
        //                                    x o o o o     x -> in use; o -> free only 0 is being used
        //                                      s s ^       skipped 2 bins (bin 1 and 2), we get to bin 3
        //                                                  and bin 3 is the bin needed. Thus symbol "4" is pick
        // in 8/3, there is a remainder 2 comes in this function as 2/1 = 2, now we have to pick the empty slot
        // for the new 2.
        // the bin now looks 0 1 2 3 4
        //                   x 0 0 x 0      as bin 3 was used by the last value
        //                     s s   ^      we skip 2 free bins and the next free bin is bin 4
        //                                  therefor the symbol "5" at the symbol array is pick.
        //
        // Thus, for index 8  "1 4 5" is the symbols.
        //
        //
        private int FindFreeBin(int iIndex)
        {
            int j = iIndex;

            if (j < 0 || j > this.iBinSize) return -1;               // invalid index

            for (int i = 0; i < this.iBinSize; i++)
            {
                if (this.eStatus[i] == 0)       // is it used
                {
                    // found an empty slot
                    if (j == 0)                 // this is a free one we want?
                        return i;               // yes, found and return it.
                    else                        // we have to skip this one
                        j--;                    // else, keep looking and count the skipped one
                }
            }
            assert(true);           // something is wrong
            return -1;              // fail to find the bin we wanted
        }

        //
        // this function is to help the PermDoReverse() to find out what is the corresponding
        // value during should be added to the index value.
        //
        // it is doing the opposite of int FindFreeBin(int iIndex) method. You need to know how this
        // FindFreeBin() works before looking into this function.
        //
        private int BinCountFree(int iIndex)
        {
            int iRetVal = 0;
            for (int i = iIndex; i > 0; i--)
            {
                if (this.eStatus[i-1] == 0)       // it is free
                {
                    iRetVal++;
                }
            }
            return iRetVal;
        }
    }
}
// End of file - Permutation.java

และนี่คือคลาสหลักของฉันสำหรับการแสดงวิธีการใช้คลาส

/*
 * copyright 2015 Fred Pang
 *
 * This is the main test program for testing the Permutation Class I created.
 * It can be use to demonstrate how to use the Permutation Class and its methods to generate a complete
 * list of a permutation. It also support function to get back the index value as pass in a permutation.
 *
 * As you can see my Java is not very good. :)
 * This is my 1st Java project I created. As I am a C/C++ programmer for years.
 *
 * I still have problem with the Scanner class and the System class.
 * Note that there is only very limited error checking
 *
 *
 */

import java.util.Scanner;

public class Main
{
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args)
    {
        Permutation perm;       // declear the object
        String sOutString = "";
        int nCount;
        int rCount;
        int iMaxIndex;

        // Get user input
        System.out.println("Enter n: ");
        nCount = scanner.nextInt();

        System.out.println("Enter r: ");
        rCount = scanner.nextInt();

        System.out.println("Enter Symbol: ");
        sOutString = scanner.next();

        if (sOutString.length() < rCount)
        {
            System.out.println("String too short, default to numbers");
            sOutString = "";
        }

        // create object with user requirement
        perm = new Permutation(nCount, rCount, sOutString);

        // and print the maximum count
        iMaxIndex = perm.getiMaxIndex();
        System.out.println("Max count is:" + iMaxIndex);

        if (!sOutString.isEmpty())
        {
            for (int i = 0; i < iMaxIndex; i++)
            {   // print out the return permutation symbol string
                System.out.println(i + " " + perm.PermGetString(i));
            }
        }
        else
        {
            for (int i = 0; i < iMaxIndex; i++)
            {
                System.out.print(i + " ->");

                // Get the permutation array
                int[] iTemp = perm.PermGetIntArray(i);

                // print out the permutation
                for (int j = 0; j < rCount; j++)
                {
                    System.out.print(' ');
                    System.out.print(iTemp[j]);
                }

                // to verify my PermGetIndex() works. :)
                if (perm.PermGetIndex(iTemp)== i)
                {
                    System.out.println(" .");
                }
                else
                {   // oops something is wrong :(
                    System.out.println(" ***************** F A I L E D *************************");
                    assert(true);
                    break;
                }
            }
        }
    }
}
//
// End of file - Main.java

มีความสุข. :)


4

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

อย่างไรก็ตามด้วยตำแหน่งมากกว่า 8 ตำแหน่งคุณจะต้องมีอะไรที่ดีกว่านี้


นี่ถือว่า OP ไม่สนใจว่าการแจงนับจะไปจาก 0 ถึง 5039 จริงไหม? ถ้าไม่เป็นไรดูเหมือนว่าจะเป็นทางออกที่ดี
Troubadour

4

สิ่งนี้เป็นฟังก์ชันในตัวในJ :

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

   ?!7
5011
   5011 A. 1 2 3 4 5 6 7
7 6 4 5 1 3 2
   A. 7 6 4 5 1 3 2
5011

2

คุณสามารถเข้ารหัสการเรียงสับเปลี่ยนโดยใช้อัลกอริทึมแบบวนซ้ำ หากการเปลี่ยนแปลง N (บางลำดับของตัวเลข {0, .. , N-1}) อยู่ในรูปแบบ {x, ... } ให้เข้ารหัสเป็น x + N * การเข้ารหัสของ (N-1) - การกลายพันธุ์แสดงโดย "... " บนตัวเลข {0, N-1} - {x} ฟังดูน่าฟังนี่คือรหัสบางส่วน:

// perm[0]..perm[n-1] must contain the numbers in {0,..,n-1} in any order.
int permToNumber(int *perm, int n) {
  // base case
  if (n == 1) return 0;

  // fix up perm[1]..perm[n-1] to be a permutation on {0,..,n-2}.
  for (int i = 1; i < n; i++) {
    if (perm[i] > perm[0]) perm[i]--;
  }

  // recursively compute
  return perm[0] + n * permToNumber(perm + 1, n - 1);
}

// number must be >=0, < n!
void numberToPerm(int number, int *perm, int n) {
  if (n == 1) {
    perm[0] = 0;
    return;
  }
  perm[0] = number % n;
  numberToPerm(number / n, perm + 1, n - 1);

  // fix up perm[1] .. perm[n-1]
  for (int i = 1; i < n; i++) {
    if (perm[i] >= perm[0]) perm[i]++;
  }
}

อัลกอริทึมนี้คือ O (n ^ 2) คะแนนโบนัสหากใครมีอัลกอริทึม O (n)


1

ช่างเป็นคำถามที่น่าสนใจ!

หากองค์ประกอบทั้งหมดของคุณเป็นตัวเลขคุณอาจต้องการพิจารณาแปลงจากสตริงเป็นตัวเลขจริง จากนั้นคุณจะสามารถเรียงลำดับการเรียงสับเปลี่ยนทั้งหมดโดยจัดเรียงตามลำดับและวางไว้ในอาร์เรย์ หลังจากนั้นคุณจะเปิดให้ใช้อัลกอริทึมการค้นหาต่างๆที่นั่น


1

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


1

มีหนังสือเล่มหนึ่งเขียนเกี่ยวกับเรื่องนี้ ขออภัยฉันจำชื่อไม่ได้ (คุณอาจจะหาได้จาก wikipedia) แต่อย่างไรก็ตามฉันเขียนการใช้ python ของระบบการแจงนับนั้น: http://kks.cabal.fi/Kombinaattori บางส่วนเป็นภาษาฟินแลนด์ แต่เพียงแค่คัดลอกโค้ดและตัวแปรชื่อ ...


0

ฉันมีคำถามที่แน่นอนนี้และคิดว่าจะให้โซลูชัน Python ของฉัน มันคือ O (n ^ 2)

import copy

def permute(string, num):
    ''' generates a permutation '''
    def build_s(factoradic): # Build string from factoradic in list form
        string0 = copy.copy(string)
        n = []
        for i in range(len(factoradic)):
            n.append(string0[factoradic[i]])
            del string0[factoradic[i]]
        return n

    f = len(string)
    factoradic = []
    while(f != 0): # Generate factoradic number list
        factoradic.append(num % f)
        num = (num - factoradic[-1])//f
        f -= 1

    return build_s(factoradic)

s = set()
# Print 120 permutations of this string
for i in range(120):
    m = permute(list('abcde'), i)
    s.add(''.join(m))

print(len(s)) # Check that we have 120 unique permutations

ค่อนข้างตรงไปตรงมา หลังจากสร้างการแสดงตัวเลขแฟกตอเรดิคแล้วฉันก็แค่เลือกและลบอักขระออกจากสตริง การลบออกจากสตริงจึงเป็นวิธีแก้ปัญหา O (n ^ 2)

วิธีแก้ของ Antoine ดีกว่าสำหรับประสิทธิภาพ


-1

คำถามที่เกี่ยวข้องคือการคำนวณการเรียงสับเปลี่ยนแบบผกผันการเรียงสับเปลี่ยนซึ่งจะคืนค่าเวกเตอร์ที่ได้รับการเปลี่ยนแปลงให้เป็นลำดับดั้งเดิมเมื่อทราบเฉพาะอาร์เรย์การเรียงลำดับเท่านั้น นี่คือรหัส O (n) (ใน PHP):

// Compute the inverse of a permutation
function GetInvPerm($Perm)
    {
    $n=count($Perm);
    $InvPerm=[];
    for ($i=0; $i<$n; ++$i)
        $InvPerm[$Perm[$i]]=$i;
    return $InvPerm;
    } // GetInvPerm

ซอฟต์แวร์ David Spector Springtime

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.