เหตุใดอัลกอริทึมการเรียงลำดับแบบเรียกซ้ำของฉันจึงมีพาร์ติชันที่ไม่สมดุลเช่นนี้

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

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

รวมถึง:

#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<iostream>
unsigned int recurse(int*a,unsigned int length)
{
    int value=0;
    unsigned int depth=0;
    if(length<=1)
    {
        depth=1;
    }
    else if(length==2)
    {
        if(a[0]>a[1])
        {
            value=a[0];
            a[0]=a[1];
            a[1]=value;
        }
        depth=1;
    }
    else if(length==3)
    {
        if(a[0]>a[1])
        {
            value=a[0];
            a[0]=a[1];
            a[1]=value;
        }
        if(a[0]>a[2])
        {
            value=a[0];
            a[0]=a[2];
            a[2]=value;
        }
        if(a[1]>a[2])
        {
            value=a[1];
            a[1]=a[2];
            a[2]=value;
        }
        depth=1;
    }
    else
    {
        //unsigned int fifth=(length>>3)+(length>>4)+(length>>6);
        unsigned int middle=length>>1;
        unsigned int choices[5]={0,middle>>1,middle,middle+(middle>>1),length-1};
        unsigned int left=0;
        unsigned int right=length-1;
        unsigned int index=0;
        for(unsigned int i=0;i<4;i++)
        {
            index=i;
            while(a[choices[index]]>a[choices[index+1]])
            {
                value=a[choices[index]];
                a[choices[index]]=a[choices[index+1]];
                a[choices[index+1]]=value;
                if(index>0)
                {
                    index--;
                }
            }
        }
        while(right>left)
        {
            while((a[++left]<=a[middle])&&right>left);
            while((a[--right]>=a[middle])&&right>left);
            if(right>left)
            {
                value=a[left];
                a[left]=a[right];
                a[right]=value;
            }
        }
        value=a[middle];
        if(left<middle&&right<middle)
        {
            a[middle]=a[left];
            a[left]=value;
            middle=left;
        }
        else if(left>middle&&right>middle)
        {
            a[middle]=a[left-1];
            a[left-1]=value;
            middle=left-1;
        }
        long double y;
        x++;
        ratio+=y=fabs((long double)(length>>1)-(long double)middle)/length;
        if(length>=1048576)
        {
            cout<<middle<<" "<<length<<" "<<y<<endl;
        }
        depth=max(recurse(a,middle),recurse(a+middle+1,length-middle-1))+1;
    }
    return depth;
}

ความลึกมีไว้เพื่อการนับความลึกของการเรียกซ้ำเท่านั้น ฉันพยายามดูว่านั่นเป็นปัญหาหรือไม่

int main(int argl,char**argv)
{
    unsigned int length=0;
    cin>>length;
    cout<<length<<endl;
    int*numbers=new int[length];
    for(unsigned int i=0;i<length;i++)
    {
        numbers[i]=(int)rand()%(length<<1);
    }
    time_t start=time(0);
    mergesort(numbers,length);
    time_t end=time(0);
    cout<<end-start<<endl;
    for(unsigned int i=0;i<length;i++)
    {
        numbers[i]=(int)rand()%(length<<1);
    }
    start=time(0);
    unsigned int depth=recurse(numbers,length);
    end=time(0);
    cout<<end-start<<endl;
    cout<<"Depth: "<<depth<<endl;
    return 0;
}

แก้ไข: รหัสสำหรับการเรียงลำดับแบบผสานตามที่ร้องขอ:

void mergesort(int*a,unsigned int length)
{
    if(length<=1)
    {
        return;
    }
    else if(length==2)
    {
        if(a[0]>a[1])
        {
            int value=a[0];
            a[0]=a[1];
            a[1]=value;
        }
    }
    else
    {
        unsigned int index1=0,index2=0;
        unsigned int divide1=1,divide2=1;
        unsigned int merge=2;
        unsigned int start=0;
        int*b=new int[length];
        while(merge<=length)
        {
            while(index1<divide1&&index2<divide2)
            {
                if(a[start+index1]>a[start+divide1+index2])
                {
                    b[start+index1+index2]=a[start+divide1+index2++];
                }
                else
                {
                    b[start+index1+index2]=a[start+index1++];
                }
            }
            if(index1<divide1)
            {
                for(unsigned int i=index1;index1<divide1;index1++)
                {
                    b[start+index1+index2]=a[start+index1];
                }
            }
            else
            {
                for(unsigned int i=index2;index2<divide2;index2++)
                {
                    b[start+index1+index2]=a[start+divide1+index2];
                }
            }
            if(start+merge>=length)
            {
                if(start==0)
                {
                    merge<<=1;
                }
                else
                {
                    start=0;
                    index1=0;
                    index2=0;
                    divide1=merge;
                    divide2=merge<<1>length?length-divide1:merge;
                    merge=divide1+divide2;
                }
                for(unsigned int i=0;i<length;i++)
                {
                    a[i]=b[i];
                }
            }
            else
            {
                start+=merge;
                index1=0;
                index2=0;
                divide1=start+divide1>length?length-start:divide1;
                divide2=start+merge>length?max((int)(length-(start+divide1)),0):divide2;
            }
        }
    }
}

ผลลัพธ์สำหรับหมายเลข 33554432: แก้ไข เปลี่ยนรหัสของฉัน อัปเดตผลลัพธ์:

33554432
33554432
8
22
Depth: 765
0.4437349830864823

ตัวเลขสุดท้ายคือระยะทางเฉลี่ยที่จุดหมุนอยู่ห่างจากจุดศูนย์กลาง อยู่ที่เกือบ 0.45 เกือบ 1 ถึง 20 แยก


person DSOI__UNUNOCTIUM    schedule 06.04.2019    source แหล่งที่มา
comment
การเรียกซ้ำจะมีความลึกถึง 599 บนเคสขนาด 33554432 และ 332 บนเคสขนาด 16777216 ฉันไม่คิดว่าจะทำให้ O(n^2) ความซับซ้อนของเวลา   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
นั่นเป็นการเรียงลำดับอย่างรวดเร็วที่ซับซ้อนอย่างไร้ความปราณี ฉันขอแนะนำให้เปรียบเทียบสิ่งที่คุณมีกับการใช้งานข้อมูลอ้างอิง   -  person user4581301    schedule 06.04.2019
comment
@ user4581301 ฉันดูหนึ่งในอัลกอริธึมการแบ่งพาร์ติชั่นและมันคล้ายกับสิ่งที่ฉันใช้อยู่ แต่หลังจากดูวิธีที่คนอื่นใช้ Quicksort แล้ว ฉันคิดว่าฉันมีความแตกต่างเล็กน้อยในโค้ดของฉัน ฉันจะพยายามแก้ไขมัน ฉันหวังว่ามันจะได้ผล   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
max(recurse(a,middle),recurse(a+middle+1,length-middle-1)) - นี่ไม่ใช่ std::max ดังนั้นฉันเดาว่ามีมาโคร max ที่ประเมินพารามิเตอร์มากกว่าหนึ่งครั้ง   -  person Raymond Chen    schedule 06.04.2019
comment
@RaymondChen นี่ควรจะเป็นฟังก์ชันสูงสุดของ <cmath> ใช้การโทรซ้ำสองครั้งที่ลึกกว่าและเพิ่มหนึ่งรายการเข้าไป นอกจากนี้ ไม่สามารถประเมินพารามิเตอร์ได้มากกว่าหนึ่งครั้งเมื่อการประเมินเสร็จสิ้น และส่งผ่านเฉพาะค่าเท่านั้น ฟังก์ชันสูงสุดจะเห็นเฉพาะค่าผลลัพธ์จากการเรียกซ้ำเท่านั้น   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
ฉันไม่เห็นฟังก์ชัน max ใน <cmath> cppreference และ cplusplus ทั้งคู่ปฏิเสธการมีอยู่ของฟังก์ชันดังกล่าว   -  person Raymond Chen    schedule 06.04.2019
comment
@RaymondChen คุณพูดถูก ฉันคิดว่าฉันใช้ <algorithm>'s max หรือ ‹bits/stdc++.h› ฉันไม่เชื่อว่านั่นคือปัญหาแม้ว่า   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
@dsoi หยุดเชื่อและเริ่มพิสูจน์ ไม่ทางใดก็ทางหนึ่ง ทุกอย่างจะต้องถูกต้องเพื่อให้โค้ดรวดเร็วและถูกต้อง สิ่งหนึ่งที่ผิดทำให้โค้ดของคุณช้าหรือไม่ถูกต้อง มีกองของอันตรายอยู่ที่นี่ ตั้งแต่การใช้เนมสเปซ std, raw new/delete, การใช้ตัวชี้แบบ raw, การสลับด้วยตนเอง, ifs ที่แตกแขนงมากมาย... ฉันจะแปลกใจถ้าโค้ดนี้ ทำตามที่ผู้เขียนตั้งใจ ไม่ใช่ตรงกันข้าม เริ่มเพิ่มความคิดเห็นที่พิสูจน์ค่าคงที่ตามหลักตรรกะและพิสูจน์ความถูกต้องร่วมกัน หรือค้นหาโค้ดเทียมที่รู้จักและจัดแนวโค้ดของคุณให้ตรงกับความคิดเห็น   -  person Yakk - Adam Nevraumont    schedule 06.04.2019
comment
ตามที่แสดงความคิดเห็นโดยผู้ใช้ 4581301 การใช้งานนี้มีความซับซ้อน ดูเหมือนว่าจะเป็น Hoare partition Scheme เวอร์ชันที่ซับซ้อน ข้อแตกต่างที่สำคัญคือรูปแบบพาร์ติชัน Hoare ปกติใช้การวนซ้ำแบบแน่น (คำสั่ง while ทั้งสอง) เพื่อสแกนอาร์เรย์จากจุดสิ้นสุด ก่อนที่จะทำการสลับ และดำเนินการต่อไปจนกว่าดัชนีจะข้ามที่ไหนสักแห่งภายในอาร์เรย์ ในขณะที่โค้ดของคุณใช้ตัวแปรเพิ่มเติม และคำสั่งแบบมีเงื่อนไข (ถ้า) ซึ่งทำให้กระบวนการช้าลง   -  person rcgldr    schedule 06.04.2019
comment
ฉันได้เปลี่ยนรหัสของฉันเป็นสิ่งที่คุณพูด @rcgldr แต่ก็ยังช้ากว่าการเรียงลำดับแบบรวมประมาณสามเท่า   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
แน่นอนว่าคงจะดีถ้าคุณรวมสิ่งที่รวมไว้ด้วย รหัสที่นำเสนอไม่ได้รวบรวม   -  person Gardener    schedule 06.04.2019
comment
@Gardener เอาล่ะฉันแก้ไขโพสต์เพื่อให้มีการรวม   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
ยังคงมองหาการรวมการควบรวมกิจการ การผสานจาก stdlib.h มีลายเซ็นที่แตกต่างกัน ฉันอาจเป็นแค่คนโง่ที่ไม่เห็นว่าสิ่งนี้จะรวบรวมได้อย่างไร   -  person Gardener    schedule 06.04.2019
comment
@Gardener ฉันไม่ได้ใช้การเรียงลำดับแบบผสานใด ๆ ฉันใช้การเรียงลำดับแบบผสานของตัวเองเพื่อทดสอบกับการเรียงลำดับแบบรวดเร็ว ฉันจะใส่รหัสลงไป   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
ดูผลลัพธ์ของอาร์เรย์ที่เรียงลำดับสำหรับอัลกอริธึม recurse ของคุณสำหรับ lengths 50 และ 60 มีการเรียงลำดับหรือไม่ Anndddd คุณอาจต้องการตรวจสอบผลลัพธ์ของ mergesort ด้วย   -  person eric    schedule 06.04.2019
comment
@eric ดูเรียงลำดับสำหรับฉัน 1 5 6 6 8 16 18 23 23 23 26 29 29 29 29 31 35 37 37 38 39 40 40 41 41 42 42 44 44 46 47 48 48 50 54 56 57 59 62 64 66 70 76 78 82 84 88 90 90 93 สำหรับ 50 และ 6 9 9 10 11 17 17 18 21 23 24 24 26 26 28 30 33 33 34 35 35 36 38 39 40 42 42 43 44 45 46 48 50 56 57 57 58 62 64 65 66 68 69 69 72 76 79 80 84 86 88 90 93 101 101 106 110 110 112 113   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
ระวัง <bits/stdc++.h> ทุกอย่างในไดเร็กทอรี bits ของ GCC นั้นเป็นการใช้งานภายในและไม่ได้มีไว้สำหรับใช้โดยตรง ตัวอย่างเช่น <bits/stdc++.h> ควรจะช่วยในการคอมไพล์ส่วนหัวและเร่งการคอมไพล์ เมื่อใช้ในทางที่ผิดจะทำให้การรวบรวมช้าลงอย่างมาก นอกจากนี้ ด้วยการดึงไลบรารีมาตรฐานทั้งหมด คุณได้เพิ่มตัวระบุนับหมื่นที่คุณไม่ได้ใช้ ส่งผลให้เกิดเขตที่วางทุ่นระเบิดที่มีเพียงเนมสเปซเท่านั้นที่สามารถช่วยคุณได้ และถ้าคุณเป็น using namespace std; คุณได้ปลดการป้องกันนั้นออกไป   -  person user4581301    schedule 07.04.2019
comment
ใช่ ฉันไม่ได้รวมเรื่องนั้น ตอนนี้การรวมอยู่ในโพสต์แล้ว   -  person DSOI__UNUNOCTIUM    schedule 07.04.2019
comment
ความลึกของการเรียกซ้ำ 599 บนชุดข้อมูลขนาด 33554432 นั้นไม่สมเหตุสมผล หมายความว่าโดยเฉลี่ยแล้วคุณแบ่งพาร์ติชันออบเจ็กต์ 3% และ 97% หากคุณแบ่งครึ่ง คุณจะคาดหวังว่าจะมีความลึกเท่ากับ 25 ซึ่งบ่งชี้ว่ามีข้อบกพร่องในโค้ดการแบ่งพาร์ติชันของคุณ   -  person Raymond Chen    schedule 08.04.2019
comment
@RaymondChen คุณคงพูดถูก ฉันคิดว่าฉันจะตรวจสอบพาร์ติชันและตำแหน่งที่เดือยสิ้นสุดลง   -  person DSOI__UNUNOCTIUM    schedule 08.04.2019
comment
@RaymondChen ฉันตรวจสอบแล้วและมันก็ค่อนข้างไม่สมดุลโดยแบ่งอาร์เรย์เป็นอัตราส่วน 1 ถึง 24 อย่างไรก็ตาม ฉันพยายามใช้องค์ประกอบตรงกลางเป็นเดือยเท่านั้น เช่นเดียวกับเดือยแบบสุ่ม ทั้งคู่มีความไม่สมดุลพอๆ กัน อะไรตอนนี้?   -  person DSOI__UNUNOCTIUM    schedule 12.04.2019
comment
ตรวจสอบว่าผลการแบ่งพาร์ติชันของคุณถูกต้อง ตัวอย่างเช่น เริ่มต้นด้วยอาร์เรย์ที่เรียงลำดับไว้แล้ว ค่ามัธยฐานของทั้งสามควรแบ่งออกเป็นสองซีกเท่าๆ กัน นี่เป็นเพียงการดีบัก   -  person Raymond Chen    schedule 12.04.2019
comment
@RaymondChen ใช่มันแบ่งอาร์เรย์ที่เรียงลำดับแล้วออกเป็นสองซีกเท่า ๆ กัน แต่ฉันรู้ว่าอัลกอริทึมของฉันเรียงลำดับไม่ถูกต้องด้วยซ้ำ ตอนนี้ฉันมีปัญหาใหญ่ที่ต้องจัดการ   -  person DSOI__UNUNOCTIUM    schedule 12.04.2019


คำตอบ (1)


ไม่มีวิธีอื่นในการทำเช่นนี้ แต่ต้องตอบที่นี่ หากคุณกำลังจะโทร new ให้โทรไปที่ delete

rand() ไม่ใช่แบบสุ่ม ลองพิจารณาใช้ไลบรารีสุ่ม C ++ 11

เพื่อความกระชับ ฉันไม่ได้รวมอัลกอริธึมของคุณไว้ แต่ฉันไม่ได้ทำการเปลี่ยนแปลงใด ๆ ฉันรันโค้ดนี้บนทั้ง Ubuntu และ MacOS

int main() {
    // Here are 50 random numbers I get when I use your method, I have put
    // them in a vector.
    std::vector<int> v = {7, 49, 89, 74, 34, 8, 24, 62, 35, 13, 24, 53,12,
                    2, 51, 71, 55, 49, 88, 52, 15, 49, 45, 5, 88, 21,
                    75, 54, 8, 7, 25, 50, 8, 19, 2, 33, 19, 13, 3, 69,
                    31, 80, 49, 72, 77, 65, 44, 43};

    // I set the size of the vector to be the size of this raw array.
    unsigned int length = v.size();

    int* numbers= new int[length];

    for(int i = 0; i < length; i++)
        numbers[i] = v[i];

    // 7 49 89 74 34 8 24 62 35 13 24 53 12 2 51 71 55...
    for (int i = 0; i < length; i++)
        std::cout << numbers[i] << " ";
    std::cout << std::endl;

    unsigned int depth = recurse(numbers, length);

    // 5 2 3 2 7 7 8 12 8 13 8 13 15 19 19 24...
    for (int i = 0; i < length; i++)
        std::cout << numbers[i] << " ";

    delete[] numbers;
    return 0;
}

ฉันค่อนข้างสนใจสิ่งที่เกิดขึ้นที่นี่ บางทีฉันอาจจะเข้าใจผิด แต่อย่างใดฉันก็หวังว่านี่จะช่วยได้ (อย่างน้อยพวกเราหนึ่งคน)

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

ดีที่สุด,

person eric    schedule 06.04.2019
comment
นั่นเป็นสิ่งที่น่าสนใจมาก ฉันไม่แน่ใจว่าเกิดอะไรขึ้นที่นี่จริงๆ แต่ฉันคิดว่าฉันจะทำการทดสอบ เดี๋ยวก่อน ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดยทำให้ทุกอย่างในอาร์เรย์เป็นศูนย์ยกเว้นองค์ประกอบสุดท้าย คุณช่วยกรุณาชี้แจงหน่อยได้ไหม? - person DSOI__UNUNOCTIUM; 06.04.2019
comment
ใช้อาร์เรย์ที่ได้รับและเขียนทับค่าดั้งเดิมจนกลายเป็น [0, 0, 0, 0, 0, 0... 89] ฉันแค่ดำเนินการตามที่คุณให้มา ดังนั้น mergesort(numbers,length) แต่ตามตัวเลขที่ rand() สร้างขึ้นบนเครื่องของฉันที่ฉันให้คุณไว้เพื่อใช้อ้างอิง - person eric; 06.04.2019