อัลกอริทึมที่เร็วที่สุดในการจัดเรียงรายการที่เชื่อมโยงคืออะไร


98

ฉันอยากรู้ว่า O (n log n) เป็นรายการที่เชื่อมโยงได้ดีที่สุดหรือไม่


32
เพียงเพื่อให้คุณรู้ว่า O (nlogn) คือขอบเขตสำหรับการเปรียบเทียบตามประเภทต่างๆ มีประเภทที่อิงจากการเปรียบเทียบที่ไม่สามารถให้ประสิทธิภาพ O (n) ได้ (เช่นการเรียงลำดับการนับ) แต่ต้องมีข้อ จำกัด เพิ่มเติมเกี่ยวกับข้อมูล
MAK

1
นั่นคือช่วงเวลาที่มีคำถามว่า "ทำไมรหัสนี้ใช้ไม่ได้ เป็นที่ยอมรับใน SO
Abhijit Sarkar

คำตอบ:


102

มันมีเหตุผลที่จะคาดหวังว่าคุณจะไม่สามารถดำเนินการใด ๆ ที่ดีกว่า O (n log N) ในเวลาทำงาน

แต่ส่วนที่น่าสนใจคือการตรวจสอบว่าคุณสามารถจัดเรียงในสถานที่ , เสถียรพฤติกรรมที่เลวร้ายที่สุดกรณีของตนและอื่น ๆ

Simon Tatham ของสีโป๊วชื่อเสียงอธิบายถึงวิธีการจัดเรียงรายการที่เชื่อมโยงกับการผสานการเรียงลำดับ เขาสรุปด้วยความคิดเห็นต่อไปนี้:

เช่นเดียวกับอัลกอริทึมการเรียงลำดับที่เคารพตัวเองสิ่งนี้มีเวลาทำงาน O (N log N) เนื่องจากนี่คือ Mergesort เวลาทำงานในกรณีที่เลวร้ายที่สุดยังคงเป็น O (N log N) ไม่มีกรณีทางพยาธิวิทยา

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

นอกจากนี้ยังมีตัวอย่างการใช้งานใน C ที่ใช้ได้กับทั้งรายการที่เชื่อมโยงแบบเดี่ยวและแบบทวีคูณ

ดังที่ @ Jørgen Fogh กล่าวไว้ด้านล่างสัญกรณ์ big-O อาจซ่อนปัจจัยคงที่บางอย่างที่อาจทำให้อัลกอริทึมหนึ่งทำงานได้ดีขึ้นเนื่องจากตำแหน่งหน่วยความจำเนื่องจากมีรายการจำนวนน้อยเป็นต้น


3
นี่ไม่ใช่สำหรับรายการเดียวที่เชื่อมโยง รหัส C ของเขาใช้ * prev และ * next
LE

3
@LE เป็นจริงสำหรับทั้งสอง ถ้าคุณเห็นลายเซ็นสำหรับคุณจะเห็นคุณสามารถสลับโดยใช้พารามิเตอร์listsort int is_double
csl

1
@LE: นี่คือรหัส C เวอร์ชัน Pythonlistsortที่รองรับเฉพาะรายการที่เชื่อมโยงแบบเดี่ยวเท่านั้น
jfs

O (kn) เป็นเส้นตรงในทางทฤษฎีและสามารถทำได้ด้วยการจัดเรียงถัง สมมติว่ามีค่า k ที่เหมาะสม (จำนวนบิต / ขนาดของวัตถุที่คุณกำลังจัดเรียง) อาจเร็วกว่าเล็กน้อย
Adam

74

ทั้งนี้ขึ้นอยู่กับจำนวนของปัจจัยก็จริงอาจจะเร็วขึ้นเพื่อคัดลอกรายการไปยังอาร์เรย์แล้วใช้Quicksort

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

Mergesort ขนานกันดีกว่าดังนั้นจึงอาจเป็นทางเลือกที่ดีกว่าหากเป็นสิ่งที่คุณต้องการ นอกจากนี้ยังเร็วกว่ามากหากคุณดำเนินการโดยตรงในรายการที่เชื่อมโยง

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

- แก้ไข

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

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

N = 1,000:

รายการแยกส่วนพร้อมการเรียงลำดับการผสาน: 0.000000 วินาที

อาร์เรย์ด้วย qsort: 0.000000 วินาที

รายการที่เต็มไปด้วยการเรียงลำดับการผสาน: 0.000000 วินาที

N = 100000:

รายการแยกส่วนพร้อมการเรียงลำดับการผสาน: 0.039000 วินาที

Array พร้อม qsort: 0.025000 วินาที

รายการที่เต็มไปด้วยการเรียงลำดับการผสาน: 0.009000 วินาที

N = 1000000:

รายการแยกส่วนพร้อมการเรียงลำดับการผสาน: 1.162000 วินาที

Array พร้อม qsort: 0.420000 วินาที

รายการที่เต็มไปด้วยการเรียงลำดับการผสาน: 0.112000 วินาที

N = 100000000:

รายการแยกส่วนพร้อมการเรียงลำดับการผสาน: 364.797000 วินาที

Array พร้อม qsort: 61.166000 วินาที

รายการที่เต็มไปด้วยการเรียงลำดับการผสาน: 16.525000 วินาที

สรุป:

อย่างน้อยในเครื่องของฉันการคัดลอกลงในอาร์เรย์ก็คุ้มค่าที่จะปรับปรุงประสิทธิภาพของแคชเนื่องจากคุณไม่ค่อยมีรายการที่เชื่อมโยงอย่างสมบูรณ์ในชีวิตจริง ควรสังเกตว่าเครื่องของฉันมี 2.8GHz Phenom II แต่มี RAM เพียง 0.6GHz ดังนั้นแคชจึงมีความสำคัญมาก


2
ความคิดเห็นที่ดี แต่คุณควรพิจารณาต้นทุนที่ไม่คงที่ในการคัดลอกข้อมูลจากรายการไปยังอาร์เรย์ (คุณต้องสำรวจรายการ) รวมถึงเวลาที่แย่ที่สุดในการทำงานสำหรับ Quicksort
csl

1
O (n * log n) ในทางทฤษฎีเหมือนกับ O (n * log n + n) ซึ่งจะรวมต้นทุนของสำเนาด้วย สำหรับ n ที่มีขนาดใหญ่เพียงพอค่าใช้จ่ายในการทำสำเนานั้นไม่สำคัญ การข้ามรายการหนึ่งครั้งไปยังจุดสิ้นสุดควรเป็นเวลา n
Dean J

1
@DeanJ: ในทางทฤษฎีใช่ แต่โปรดจำไว้ว่าผู้โพสต์ต้นฉบับนำเสนอกรณีที่การเพิ่มประสิทธิภาพระดับไมโครมีความสำคัญ และในกรณีนี้จะต้องพิจารณาเวลาที่ใช้ในการเปลี่ยนรายการที่เชื่อมโยงเป็นอาร์เรย์ ความคิดเห็นเป็นข้อมูลเชิงลึก แต่ฉันไม่มั่นใจอย่างสมบูรณ์ว่ามันจะช่วยเพิ่มประสิทธิภาพในความเป็นจริง มันอาจใช้ได้กับ N ขนาดเล็กมากบางที
csl

1
@csl: อันที่จริงฉันคาดหวังว่าประโยชน์ของพื้นที่จะเริ่มต้นสำหรับ N ขนาดใหญ่โดยสมมติว่าการพลาดแคชเป็นเอฟเฟกต์ประสิทธิภาพที่โดดเด่นจากนั้นวิธีคัดลอก - qsort-copy จะส่งผลให้แคชประมาณ 2 * N พลาดสำหรับการคัดลอก บวกจำนวน misses สำหรับ qsort ซึ่งจะเป็นเศษเสี้ยวเล็ก ๆ ของ N log (N) (เนื่องจากการเข้าถึงส่วนใหญ่ใน qsort เป็นองค์ประกอบที่ใกล้เคียงกับองค์ประกอบที่เพิ่งเข้าถึง) จำนวนครั้งที่พลาดสำหรับการเรียงลำดับการผสานเป็นเศษส่วนที่ใหญ่กว่าของบันทึก N (N) เนื่องจากการเปรียบเทียบในสัดส่วนที่สูงกว่าทำให้เกิดการพลาดแคช ดังนั้นสำหรับ N ขนาดใหญ่คำนี้จะครอบงำและทำให้การผสานช้าลง
Steve Jessop

2
@ สตีฟ: คุณคิดถูกแล้วที่ qsort ไม่ใช่การแทนที่แบบดรอปอิน แต่ประเด็นของฉันไม่ได้เกี่ยวกับ qsort vs. mergesort ฉันไม่รู้สึกอยากเขียนเวอร์ชันอื่นของ mergesort เมื่อ qsort พร้อมใช้งาน ห้องสมุดมาตรฐานคือวิธีที่สะดวกสบายกว่าการกลิ้งของคุณเอง
Jørgen Fogh

10

นี่เป็นบทความเล็ก ๆ ที่ดีในหัวข้อนี้ ข้อสรุปเชิงประจักษ์ของเขาคือ Treesort ดีที่สุดตามด้วย Quicksort และ Mergesort การเรียงตะกอนการเรียงฟองการเรียงลำดับการเลือกทำได้แย่มาก

การศึกษาเปรียบเทียบการเรียงลำดับรายการที่เชื่อมโยงโดย Ching-Kuang Shene

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.9981


8

ประเภทการเปรียบเทียบ (คนคือขึ้นอยู่กับการเปรียบเทียบองค์ประกอบ) n log nไม่อาจจะเร็วกว่า ไม่สำคัญว่าโครงสร้างข้อมูลพื้นฐานคืออะไร ดูวิกิพีเดีย

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


5

ตามที่ระบุไว้หลายครั้งขอบเขตล่างของการเรียงลำดับตามการเปรียบเทียบสำหรับข้อมูลทั่วไปจะเป็น O (n log n) ในการสรุปข้อโต้แย้งเหล่านี้โดยย่อมี n! วิธีต่างๆในการจัดเรียงรายการ ต้นไม้เปรียบเทียบใด ๆ ที่มี n! (ซึ่งอยู่ใน O (n ^ n)) ประเภทสุดท้ายที่เป็นไปได้จะต้องมีอย่างน้อย log (n!) ตามความสูง: สิ่งนี้จะให้ขอบเขตล่าง O (log (n ^ n)) ซึ่งเป็น O (n ล็อก n)

ดังนั้นสำหรับข้อมูลทั่วไปในรายการที่เชื่อมโยงการเรียงลำดับที่ดีที่สุดที่จะทำงานกับข้อมูลใด ๆ ที่สามารถเปรียบเทียบวัตถุสองชิ้นได้จะเป็น O (n log n) อย่างไรก็ตามหากคุณมีโดเมนที่ จำกัด มากขึ้นในการทำงานคุณสามารถปรับปรุงเวลาที่ใช้ (อย่างน้อยตามสัดส่วนกับ n) ตัวอย่างเช่นหากคุณกำลังทำงานกับจำนวนเต็มไม่เกินค่าบางค่าคุณสามารถใช้Counting SortหรือRadix Sortได้เนื่องจากใช้อ็อบเจ็กต์เฉพาะที่คุณกำลังเรียงลำดับเพื่อลดความซับซ้อนโดยมีสัดส่วนเป็น n อย่างไรก็ตามโปรดระวังสิ่งเหล่านี้เพิ่มสิ่งอื่น ๆ ให้กับความซับซ้อนที่คุณอาจไม่ได้พิจารณา (ตัวอย่างเช่นการเรียงลำดับการนับและการเรียงลำดับ Radix เพิ่มปัจจัยที่ขึ้นอยู่กับขนาดของตัวเลขที่คุณกำลังเรียงลำดับ O (n + k ) โดยที่ k คือขนาดของตัวเลขที่ใหญ่ที่สุดสำหรับการเรียงลำดับนับเป็นต้น)

นอกจากนี้หากคุณมีวัตถุที่มีแฮชที่สมบูรณ์แบบ (หรืออย่างน้อยก็มีแฮชที่จับคู่ค่าทั้งหมดแตกต่างกัน) คุณสามารถลองใช้การนับหรือการเรียงลำดับเลขในฟังก์ชันแฮช


3

การจัดเรียง Radixเหมาะอย่างยิ่งกับรายการที่เชื่อมโยงเนื่องจากง่ายต่อการสร้างตารางของตัวชี้ส่วนหัวที่สอดคล้องกับค่าที่เป็นไปได้ของตัวเลขแต่ละตัว


1
คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับหัวข้อนี้หรือให้ลิงค์แหล่งข้อมูลสำหรับการเรียงลำดับเลขในรายการที่เชื่อมโยง
LoveToCode

2

การเรียงลำดับการผสานไม่ต้องการการเข้าถึง O (1) และเป็น O (n ln n) ไม่มีอัลกอริทึมที่เป็นที่รู้จักสำหรับการเรียงลำดับข้อมูลทั่วไปที่ดีไปกว่า O (n ln n)

อัลกอริธึมข้อมูลพิเศษเช่นการจัดเรียงเรดิกซ์ (ขนาด จำกัด ของข้อมูล) หรือการจัดเรียงฮิสโตแกรม (นับข้อมูลที่ไม่ต่อเนื่อง) สามารถจัดเรียงรายการที่เชื่อมโยงโดยมีฟังก์ชันการเติบโตที่ต่ำกว่าตราบเท่าที่คุณใช้โครงสร้างอื่นที่มีการเข้าถึง O (1) เป็นที่จัดเก็บชั่วคราว .

ข้อมูลพิเศษอีกชั้นหนึ่งคือการเปรียบเทียบรายการที่เรียงลำดับเกือบที่มีองค์ประกอบ k ไม่เรียงลำดับ สิ่งนี้สามารถจัดเรียงได้ในการดำเนินการ O (kn)

การคัดลอกรายการไปยังอาร์เรย์และย้อนกลับจะเป็น O (N) ดังนั้นจึงสามารถใช้อัลกอริทึมการเรียงลำดับใด ๆ ได้หากช่องว่างไม่ใช่ปัญหา

ตัวอย่างเช่นหากมีรายการที่เชื่อมโยงซึ่งมีuint_8รหัสนี้จะเรียงลำดับตามเวลา O (N) โดยใช้การจัดเรียงฮิสโตแกรม:

#include <stdio.h>
#include <stdint.h>
#include <malloc.h>

typedef struct _list list_t;
struct _list {
    uint8_t value;
    list_t  *next;
};


list_t* sort_list ( list_t* list )
{
    list_t* heads[257] = {0};
    list_t* tails[257] = {0};

    // O(N) loop
    for ( list_t* it = list; it != 0; it = it -> next ) {
        list_t* next = it -> next;

        if ( heads[ it -> value ] == 0 ) {
            heads[ it -> value ] = it;
        } else {
            tails[ it -> value ] -> next = it;
        }

        tails[ it -> value ] = it;
    }

    list_t* result = 0;

    // constant time loop
    for ( size_t i = 255; i-- > 0; ) {
        if ( tails[i] ) {
            tails[i] -> next = result;
            result = heads[i];
        }
    }

    return result;
}

list_t* make_list ( char* string )
{
    list_t head;

    for ( list_t* it = &head; *string; it = it -> next, ++string ) {
        it -> next = malloc ( sizeof ( list_t ) );
        it -> next -> value = ( uint8_t ) * string;
        it -> next -> next = 0;
    }

    return head.next;
}

void free_list ( list_t* list )
{
    for ( list_t* it = list; it != 0; ) {
        list_t* next = it -> next;
        free ( it );
        it = next;
    }
}

void print_list ( list_t* list )
{
    printf ( "[ " );

    if ( list ) {
        printf ( "%c", list -> value );

        for ( list_t* it = list -> next; it != 0; it = it -> next )
            printf ( ", %c", it -> value );
    }

    printf ( " ]\n" );
}


int main ( int nargs, char** args )
{
    list_t* list = make_list ( nargs > 1 ? args[1] : "wibble" );


    print_list ( list );

    list_t* sorted = sort_list ( list );


    print_list ( sorted );

    free_list ( list );
}

5
ได้รับการพิสูจน์แล้วว่าไม่มี algorthms การเรียงลำดับตามการเปรียบเทียบที่เร็วกว่า n log n
Artelius

9
ไม่ได้รับการพิสูจน์แล้วว่าไม่มีอัลกอริทึมการจัดเรียงแบบเปรียบเทียบบนข้อมูลทั่วไปที่เร็วกว่า n log n
Pete Kirkham

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

3
@bdonlan ประเด็นของ "ข้อมูลทั่วไป" คือมีอัลกอริทึมที่เร็วกว่าสำหรับอินพุตที่ จำกัด มากกว่าการป้อนข้อมูลแบบสุ่ม ในกรณีที่ จำกัด คุณสามารถเขียนอัลกอริทึม O (1) เล็กน้อยซึ่งจะจัดเรียงรายการเนื่องจากข้อมูลอินพุตถูก จำกัด ให้จัดเรียงอยู่แล้ว
Pete Kirkham

และนั่นจะไม่ใช่การจัดเรียงตามการเปรียบเทียบ ตัวปรับเปลี่ยน "บนข้อมูลทั่วไป" มีความซ้ำซ้อนเนื่องจากการเปรียบเทียบจะจัดการกับข้อมูลทั่วไปอยู่แล้ว (และสัญกรณ์ขนาดใหญ่มีไว้สำหรับจำนวนการเปรียบเทียบที่ทำขึ้น)
Steve Jessop

1

ไม่ใช่คำตอบโดยตรงสำหรับคำถามของคุณ แต่ถ้าคุณใช้Skip Listระบบจะเรียงลำดับแล้วและมีเวลาค้นหา O (log N)


1
O(lg N)เวลาค้นหาที่คาดไว้ - แต่ไม่รับประกันเนื่องจากรายการข้ามขึ้นอยู่กับการสุ่ม หากคุณได้รับข้อมูลที่ไม่น่าเชื่อถือตรวจสอบให้แน่ใจว่าซัพพลายเออร์ของอินพุตไม่สามารถคาดเดา RNG ของคุณได้หรือพวกเขาอาจส่งข้อมูลที่ก่อให้เกิดประสิทธิภาพในกรณีที่เลวร้ายที่สุด
bdonlan

1

ดังที่ฉันทราบอัลกอริทึมการเรียงลำดับที่ดีที่สุดคือ O (n * log n) ไม่ว่าจะเป็นคอนเทนเนอร์ใดก็ตาม - ได้รับการพิสูจน์แล้วว่าการเรียงลำดับในความหมายกว้าง ๆ ของคำ (รูปแบบการผสาน / Quicksort ฯลฯ ) ไม่สามารถลดลงได้ การใช้รายการที่เชื่อมโยงจะไม่ทำให้คุณมีเวลาทำงานที่ดีขึ้น

อัลกอริทึมเดียวที่ทำงานใน O (n) คืออัลกอริทึม "แฮ็ก" ซึ่งอาศัยการนับค่ามากกว่าการเรียงลำดับจริง


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

พูดอย่างเคร่งครัดมันวิ่งเข้าO(n lg c)มา ถ้าทุกองค์ประกอบของคุณจะไม่ซ้ำกันแล้วและดังนั้นจึงใช้เวลานานกว่าc >= n O(n lg n)
bdonlan

1

นี่คือการใช้งานที่ข้ามผ่านรายการเพียงครั้งเดียวรวบรวมการรันจากนั้นกำหนดเวลาการผสานในลักษณะเดียวกับที่ผสาน

ความซับซ้อนคือ O (n log m) โดยที่ n คือจำนวนรายการและ m คือจำนวนการรัน กรณีที่ดีที่สุดคือ O (n) (หากจัดเรียงข้อมูลแล้ว) และกรณีที่เลวร้ายที่สุดคือ O (n log n) ตามที่คาดไว้

ต้องใช้หน่วยความจำชั่วคราว O (log m); การจัดเรียงจะเสร็จสิ้นในสถานที่ในรายการ

(อัปเดตด้านล่างผู้แสดงความคิดเห็นคนหนึ่งเป็นจุดที่ดีที่ฉันควรอธิบายที่นี่)

ส่วนสำคัญของอัลกอริทึมคือ:

    while list not empty
        accumulate a run from the start of the list
        merge the run with a stack of merges that simulate mergesort's recursion
    merge all remaining items on the stack

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

ง่ายที่สุดเพียงแค่วางรหัสการผสานที่นี่:

    int i = 0;
    for ( ; i < stack.size(); ++i) {
        if (!stack[i])
            break;
        run = merge(run, stack[i], comp);
        stack[i] = nullptr;
    }
    if (i < stack.size()) {
        stack[i] = run;
    } else {
        stack.push_back(run);
    }

พิจารณาเรียงลำดับรายการ (dagibecfjh) (ละเว้นการรัน) สถานะสแต็กดำเนินการดังนี้:

    [ ]
    [ (d) ]
    [ () (a d) ]
    [ (g), (a d) ]
    [ () () (a d g i) ]
    [ (b) () (a d g i) ]
    [ () (b e) (a d g i) ]
    [ (c) (b e) (a d g i ) ]
    [ () () () (a b c d e f g i) ]
    [ (j) () () (a b c d e f g i) ]
    [ () (h j) () (a b c d e f g i) ]

จากนั้นรวมรายการเหล่านี้ทั้งหมดเข้าด้วยกัน

โปรดสังเกตว่าจำนวนรายการ (รัน) ที่สแต็ก [i] เป็นศูนย์หรือ 2 ^ i และขนาดสแต็กถูกล้อมรอบด้วย 1 + log2 (nruns) แต่ละองค์ประกอบจะรวมกันหนึ่งครั้งต่อระดับสแต็กดังนั้นการเปรียบเทียบ O (n log m) มีความคล้ายคลึงกันกับ Timsort ที่นี่แม้ว่า Timsort จะรักษาสแต็กโดยใช้บางอย่างเช่นลำดับฟีโบนักชีซึ่งใช้พลังของสอง

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

(อืม ... ปรับปรุงครั้งที่สอง)

หรือเพียงแค่ดูวิกิพีเดียในการผสานจากล่างขึ้นบน


การสร้างการรันทำงานได้ดีด้วย "อินพุตย้อนกลับ" เป็นสิ่งที่ดี O(log m)ไม่จำเป็นต้องใช้หน่วยความจำเพิ่มเติม - เพียงแค่เพิ่มการวิ่งไปยังสองรายการสลับกันจนกว่าจะว่าง
greybeard

1

คุณสามารถคัดลอกลงในอาร์เรย์แล้วจัดเรียงได้

  • คัดลอกไปยังอาร์เรย์ O (n)

  • การเรียงลำดับ O (nlgn) (หากคุณใช้อัลกอริทึมที่รวดเร็วเช่นการเรียงลำดับการผสาน)

  • คัดลอกกลับไปยังรายการที่เชื่อมโยง O (n) หากจำเป็น

ดังนั้นมันจะเป็น O (nlgn)

โปรดทราบว่าหากคุณไม่ทราบจำนวนองค์ประกอบในรายการที่เชื่อมโยงคุณจะไม่ทราบขนาดของอาร์เรย์ หากคุณกำลังเขียนโค้ดใน java คุณสามารถใช้ Arraylist ได้เช่น


สิ่งนี้เพิ่มอะไรจากคำตอบของJørgen Fogh ?
greybeard


0

คำถามคือLeetCode # 148และมีวิธีแก้ปัญหามากมายในภาษาหลักทั้งหมด ของฉันมีดังนี้ แต่ฉันสงสัยเกี่ยวกับความซับซ้อนของเวลา ในการค้นหาองค์ประกอบตรงกลางเราสำรวจรายการทั้งหมดในแต่ละครั้ง nองค์ประกอบในครั้งแรกจะวนซ้ำ2 * n/2องค์ประกอบครั้งที่สองจะวนซ้ำไปเรื่อย ๆ ดูเหมือนว่าจะถึงO(n^2)เวลาแล้ว

def sort(linked_list: LinkedList[int]) -> LinkedList[int]:
    # Return n // 2 element
    def middle(head: LinkedList[int]) -> LinkedList[int]:
        if not head or not head.next:
            return head
        slow = head
        fast = head.next

        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next

        return slow

    def merge(head1: LinkedList[int], head2: LinkedList[int]) -> LinkedList[int]:
        p1 = head1
        p2 = head2
        prev = head = None

        while p1 and p2:
            smaller = p1 if p1.val < p2.val else p2
            if not head:
                head = smaller
            if prev:
                prev.next = smaller
            prev = smaller

            if smaller == p1:
                p1 = p1.next
            else:
                p2 = p2.next

        if prev:
            prev.next = p1 or p2
        else:
            head = p1 or p2

        return head

    def merge_sort(head: LinkedList[int]) -> LinkedList[int]:
        if head and head.next:
            mid = middle(head)
            mid_next = mid.next
            # Makes it easier to stop
            mid.next = None

            return merge(merge_sort(head), merge_sort(mid_next))
        else:
            return head

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