Mengapa algoritma quicksort rekursif saya memiliki partisi yang miring?

Saya telah menerapkan algoritma quicksort di C++. Namun, algoritma saya berjalan jauh lebih lambat daripada algoritma mergesort saya. Ini berjalan jauh lebih cepat pada array terbalik tetapi selain itu dibutuhkan waktu sekitar empat atau lima kali lebih lama.

Algoritme saya menggunakan rekursi, meskipun menurut saya bukan itu masalahnya. Saya telah mencoba beralih ke pivot acak alih-alih median dari tiga pilihan pivot. Itu bahkan lebih lambat.

Termasuk:

#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;
}

Kedalamannya hanya untuk menghitung kedalaman rekursi. Saya mencoba melihat apakah itu masalahnya.

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;
}

Sunting: Kode untuk pengurutan gabungan, seperti yang diminta:

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;
            }
        }
    }
}

Hasilnya, untuk nomor 33554432: Edit, ubah kode saya, perbarui hasil:

33554432
33554432
8
22
Depth: 765
0.4437349830864823

Angka terakhir adalah jarak rata-rata poros dari tengah. Hampir 0,45, hampir selisih satu banding dua puluh.


person DSOI__UNUNOCTIUM    schedule 06.04.2019    source sumber
comment
Rekursi mencapai kedalaman 599 pada case ukuran 33554432, dan 332 pada case ukuran 16777216. Saya tidak berpikir itu akan menyebabkan kompleksitas waktu O(n^2).   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
Itu adalah salah satu quicksort yang sangat rumit. Saya sarankan untuk membandingkan apa yang Anda miliki dengan implementasi referensi.   -  person user4581301    schedule 06.04.2019
comment
@ user4581301 Saya melihat salah satu algoritma partisi dan mirip dengan apa yang saya gunakan, namun, setelah melihat cara orang lain mengimplementasikan quicksort mereka, saya rasa ada sedikit perbedaan dalam kode saya. Saya akan mencoba memperbaikinya. Saya harap ini berhasil.   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
max(recurse(a,middle),recurse(a+middle+1,length-middle-1)) - Ini bukan std::max, jadi tebakan saya ada makro max yang mengevaluasi parameternya lebih dari sekali.   -  person Raymond Chen    schedule 06.04.2019
comment
@RaymondChen Ini seharusnya menjadi fungsi maksimal <cmath>. Dibutuhkan lebih dalam dari dua panggilan rekursif dan menambahkan satu panggilan ke dalamnya. Juga tidak dapat mengevaluasi parameternya lebih dari sekali karena evaluasi selesai dan hanya nilai yang diteruskan, fungsi max hanya akan melihat nilai yang dihasilkan dari panggilan rekursif.   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
Saya tidak melihat fungsi max di <cmath>. cppreference dan cplusplus keduanya menyangkal keberadaan fungsi tersebut.   -  person Raymond Chen    schedule 06.04.2019
comment
@RaymondChen Anda benar. Saya rasa saya menggunakan maks <algorithm> atau ‹bits/stdc++.h›. Saya tidak percaya itu masalahnya.   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
@dsoi berhenti percaya dan mulai membuktikan, dengan satu atau lain cara. Semuanya harus tepat agar kode bisa cepat dan benar; ada satu hal yang salah membuat kode Anda lambat atau salah. Ada banyak hal berbahaya di sini, mulai dari penggunaan namespace std, mentah baru/hapus, penggunaan pointer mentah, pertukaran manual, banyak if bercabang... Saya akan terkejut jika kodenya melakukan apa yang dimaksudkan penulisnya, bukan sebaliknya. Mulailah menambahkan komentar yang secara logis membuktikan invarian yang bersama-sama membuktikan kebenarannya. Atau temukan kode semu yang berfungsi dan sejajarkan kode Anda dengan komentar.   -  person Yakk - Adam Nevraumont    schedule 06.04.2019
comment
Seperti yang dikomentari oleh user4581301, implementasi ini rumit. Tampaknya ini merupakan versi rumit dari skema partisi Hoare. Perbedaan utamanya adalah skema partisi Hoare normal menggunakan loop ketat (dua pernyataan while) untuk memindai array dari ujung, sebelum melakukan swap, dan berlanjut hingga indeks melintasi suatu tempat di dalam array, sementara kode Anda menggunakan variabel tambahan dan pernyataan bersyarat (jika), yang memperlambat proses.   -  person rcgldr    schedule 06.04.2019
comment
Saya telah mengubah kode saya sesuai dengan apa yang Anda katakan @rcgldr tetapi masih tiga kali lebih lambat dari penggabungan.   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
Tentu akan lebih baik jika Anda menyertakan penyertaannya. Kode yang disajikan tidak dapat dikompilasi.   -  person Gardener    schedule 06.04.2019
comment
@Gardener Baiklah saya mengedit posting saya untuk menyertakannya.   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
Masih mencari penyertaan mergesort. Mergesort dari stdlib.h memiliki tanda tangan yang berbeda. Saya mungkin bodoh karena tidak melihat bagaimana ini akan dikompilasi.   -  person Gardener    schedule 06.04.2019
comment
@Gardener Saya tidak menggunakan pengurutan gabungan apa pun. Saya menerapkan pengurutan gabungan saya sendiri untuk mengujinya terhadap pengurutan cepat. Saya akan memasukkan kode untuk itu.   -  person DSOI__UNUNOCTIUM    schedule 06.04.2019
comment
Lihatlah output dari array yang diurutkan untuk algoritma recurse Anda untuk lengths 50 dan 60. Apakah array tersebut diurutkan? Anndddd Anda mungkin juga ingin memeriksa output mergesort.   -  person eric    schedule 06.04.2019
comment
@eric Tampaknya beres bagi saya. 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 untuk 50, dan 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
Hati-hati dengan <bits/stdc++.h>. Segala sesuatu di direktori bits GCC adalah implementasi internal dan tidak dimaksudkan untuk digunakan secara langsung. <bits/stdc++.h>, misalnya seharusnya membantu header yang telah dikompilasi dan mempercepat kompilasi. Jika disalahgunakan, ini akan memperlambat kompilasi secara dramatis. Ditambah lagi dengan menggunakan seluruh perpustakaan standar, Anda telah menambahkan puluhan ribu pengidentifikasi yang tidak Anda gunakan, sehingga menghasilkan ladang ranjau yang hanya dapat diselamatkan oleh namespace. Dan jika Anda using namespace std; Anda telah menghilangkan pertahanan itu.   -  person user4581301    schedule 07.04.2019
comment
Ya saya tidak termasuk itu, penyertaannya sekarang ada di pos.   -  person DSOI__UNUNOCTIUM    schedule 07.04.2019
comment
Kedalaman rekursi 599 pada kumpulan data berukuran 33554432 tidak masuk akal. Artinya rata-rata Anda mempartisi objek sebesar 3% dan 97%. Jika Anda membaginya menjadi dua, Anda akan mengharapkan kedalaman 25. Ini menunjukkan bahwa ada cacat dalam kode partisi Anda.   -  person Raymond Chen    schedule 08.04.2019
comment
@RaymondChen Anda mungkin benar, saya rasa saya akan memeriksa partisi dan di mana pivot berakhir.   -  person DSOI__UNUNOCTIUM    schedule 08.04.2019
comment
@RaymondChen Saya memeriksanya dan hasilnya cukup miring, membagi array dalam rasio 1 hingga 24. Namun, saya sudah mencoba menggunakan elemen tengah hanya sebagai pivot, dan juga pivot acak, keduanya sama miringnya. Apa sekarang?   -  person DSOI__UNUNOCTIUM    schedule 12.04.2019
comment
Periksa apakah hasil partisi Anda sudah benar. Misalnya, mulailah dengan array yang sudah diurutkan. Median dari tiga harus dibagi menjadi dua bagian yang sama. Ini hanya proses debug.   -  person Raymond Chen    schedule 12.04.2019
comment
@RaymondChen Ya, itu membagi array yang sudah diurutkan menjadi dua bagian yang sama, namun, saya menyadari bahwa algoritma saya bahkan tidak mengurutkan dengan benar. Sekarang aku mempunyai masalah yang lebih besar untuk diatasi.   -  person DSOI__UNUNOCTIUM    schedule 12.04.2019


Jawaban (1)


Tidak ada cara lain untuk melakukan ini, selain menjawab di sini. Jika Anda akan menelepon new, lakukan panggilan yang sesuai ke delete.

rand() tidak terlalu acak, pertimbangkan untuk menggunakan perpustakaan acak C++11.

Demi singkatnya, saya belum menyertakan algoritme Anda, tetapi saya tidak mengubahnya. Saya telah menjalankan kode ini di Ubuntu dan 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;
}

Saya cukup tertarik dengan apa yang terjadi di sini. Mungkin saya salah, tapi saya harap ini membantu (setidaknya salah satu dari kita).

Adapun alasan mengapa jawaban saya mungkin penting adalah ketika saya menjalankan algoritma mergesort Anda, semua yang ada di array akan hilang kecuali elemen terakhir dalam array. Jika hal ini benar-benar terjadi, mungkin orang lain dapat mengonfirmasinya, maka hal ini mungkin dilakukan dengan lebih efisien daripada penyortiran quicksort Anda.

Terbaik,

person eric    schedule 06.04.2019
comment
Ini sangat menarik. Saya tidak yakin apa yang sebenarnya terjadi di sini tetapi saya rasa saya akan mencobanya. Tunggu, saya tidak yakin apa yang Anda maksud dengan nol semua yang ada di array kecuali elemen terakhir. Bisakah Anda menjelaskan? - person DSOI__UNUNOCTIUM; 06.04.2019
comment
Dibutuhkan array yang diberikan dan menimpa nilai asli sehingga menjadi seperti [0, 0, 0, 0, 0, 0... 89]. Saya hanya menjalankan apa yang telah Anda berikan kepada kami, jadi mergesort(numbers,length), tetapi pada angka-angka yang rand() hasilkan di mesin saya yang telah saya berikan kepada Anda sebagai referensi. - person eric; 06.04.2019