วิธีเรียงลำดับแบบเรียงซ้อนโดยใช้อัลกอริทึมการเรียงแบบผสาน?


244

ฉันรู้ว่าคำถามนั้นไม่เจาะจงเกินไป

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

ทั้งหมดที่ฉันสามารถหาได้ (บนเน็ต) คือหน้าเว็บที่บอกว่า "มันซับซ้อนเกินไป" หรือ "เกินขอบเขตของข้อความนี้"

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

แม้ว่ามันซับซ้อนเกินไปแนวคิดพื้นฐานของวิธีการผสานการเรียงลำดับคืออะไร


เป็นคำถามที่ดีฉันถามตัวเองเมื่ออ่านคำถามเมื่อวานนี้: stackoverflow.com/questions/2566459/…
Chris Lercher

มีวิธีการค่อนข้างง่ายที่อธิบายไว้ที่นี่: xinok.wordpress.com/2014/08/17/…
Branko Dimitrijevic

คำตอบ:


140

Knuth ปล่อยให้สิ่งนี้เป็นแบบฝึกหัด (ชุดที่ 3, 5.2.5) มีประเภทการรวมอยู่ในสถานที่ พวกเขาจะต้องดำเนินการอย่างระมัดระวัง

ก่อนอื่นการรวมที่ไร้เดียงสาอย่างที่อธิบายไว้ที่นี่ไม่ใช่วิธีการแก้ปัญหาที่ถูกต้อง มันปรับลดประสิทธิภาพการทำงานให้กับO (n 2 )

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

ตัวอย่างเช่นฟังก์ชั่นการผสานดังต่อไปนี้

void wmerge(Key* xs, int i, int m, int j, int n, int w) {
    while (i < m && j < n)
        swap(xs, w++, xs[i] < xs[j] ? i++ : j++);
    while (i < m)
        swap(xs, w++, i++);
    while (j < n)
        swap(xs, w++, j++);
}  

มันต้องใช้อาร์เรย์อาร์เรย์xsย่อยทั้งสองที่เรียงลำดับจะแสดงเป็นช่วง[i, m)และ[j, n)ตามลำดับ wพื้นที่การทำงานเริ่มต้นจาก เปรียบเทียบกับอัลกอริธึมการผสานมาตรฐานที่กำหนดในตำราเรียนส่วนใหญ่อันนี้แลกเปลี่ยนเนื้อหาระหว่างแถวย่อยที่เรียงลำดับและพื้นที่ทำงาน เป็นผลให้พื้นที่ทำงานก่อนหน้านี้มีองค์ประกอบที่เรียงลำดับผสานในขณะที่องค์ประกอบก่อนหน้านี้ที่เก็บไว้ในพื้นที่ทำงานจะถูกย้ายไปยังอาร์เรย์ย่อยสอง

อย่างไรก็ตามมีข้อ จำกัด สองประการที่ต้องทำให้เป็นที่พอใจ:

  1. พื้นที่ทำงานควรอยู่ภายในขอบเขตของอาเรย์ กล่าวอีกนัยหนึ่งควรมีขนาดใหญ่พอที่จะแลกเปลี่ยนองค์ประกอบโดยไม่ทำให้เกิดข้อผิดพลาดนอกขอบเขต
  2. พื้นที่ทำงานสามารถทับซ้อนกับหนึ่งในสองอาร์เรย์ที่เรียงลำดับ อย่างไรก็ตามจะต้องตรวจสอบให้แน่ใจว่าไม่มีองค์ประกอบที่ไม่รวมถูกเขียนทับ

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

... unsorted 1/2 array ... | ... sorted 1/2 array ...

แนวคิดหนึ่งที่ใช้งานง่ายคือการจัดเรียงแบบเรียกซ้ำอีกครึ่งหนึ่งของพื้นที่ทำงานดังนั้นจึงมีเพียงองค์ประกอบ 1/4 ที่ยังไม่ได้เรียง

... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...

จุดสำคัญในขั้นตอนนี้คือเราจะต้องรวมองค์ประกอบที่เรียงลำดับ 1/4 B กับองค์ประกอบที่เรียงลำดับ 1/2 A ไม่ช้าก็เร็ว

เหลือพื้นที่ใช้งานเหลือเพียง 1/4 ชิ้นซึ่งใหญ่พอที่จะรวม A และ B ได้หรือไม่? น่าเสียดายที่มันไม่ใช่

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

ที่จริงแทนที่จะเรียงลำดับครึ่งหลังของพื้นที่ทำงานเราสามารถเรียงลำดับครึ่งแรกและวางพื้นที่ทำงานระหว่างสองอาร์เรย์ที่เรียงลำดับดังนี้:

... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...

การตั้งค่านี้มีประสิทธิภาพในการจัดพื้นที่ทำงานทับซ้อนกับกลุ่มย่อย A แนวคิดนี้เสนอใน [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola `` การรวมที่เกิดขึ้นจริงในทางปฏิบัติ '' วารสารคอมพิวเตอร์นอร์ดิก, 1996]

สิ่งเดียวที่เหลือคือการทำซ้ำขั้นตอนข้างต้นซึ่งช่วยลดพื้นที่การทำงานจาก 1/2, 1/4, 1/8, …เมื่อพื้นที่การทำงานมีขนาดเล็กพอ (ตัวอย่างเช่นเหลือเพียงสององค์ประกอบ) เราสามารถ สลับไปที่การเรียงลำดับการแทรกเล็กน้อยเพื่อสิ้นสุดอัลกอริทึมนี้

นี่คือการดำเนินการใน ANSI C ตามเอกสารนี้

void imsort(Key* xs, int l, int u);

void swap(Key* xs, int i, int j) {
    Key tmp = xs[i]; xs[i] = xs[j]; xs[j] = tmp;
}

/* 
 * sort xs[l, u), and put result to working area w. 
 * constraint, len(w) == u - l
 */
void wsort(Key* xs, int l, int u, int w) {
    int m;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        imsort(xs, l, m);
        imsort(xs, m, u);
        wmerge(xs, l, m, m, u, w);
    }
    else
        while (l < u)
            swap(xs, l++, w++);
}

void imsort(Key* xs, int l, int u) {
    int m, n, w;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        w = l + u - m;
        wsort(xs, l, m, w); /* the last half contains sorted elements */
        while (w - l > 2) {
            n = w;
            w = l + (n - l + 1) / 2;
            wsort(xs, w, n, l);  /* the first half of the previous working area contains sorted elements */
            wmerge(xs, l, l + n - w, n, u, w);
        }
        for (n = w; n > l; --n) /*switch to insertion sort*/
            for (m = n; m < u && xs[m] < xs[m-1]; ++m)
                swap(xs, m, m - 1);
    }
}

ตำแหน่งที่ wmerge ถูกกำหนดก่อนหน้านี้

รหัสที่มาเต็มสามารถพบได้ที่นี่และคำอธิบายรายละเอียดสามารถพบได้ที่นี่

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


6
Knuth left this as an exercise (Vol 3, 5.2.5).หมายถึงอดีต 13. [40] ใช้วิธีการเรียงลำดับภายในที่แนะนำ [ในตอนท้ายของส่วนนี้] ซึ่งจะทำการจัดเรียงข้อมูลแบบสุ่มในหน่วย O (N) ของเวลาโดยมีเพียงตำแหน่งหน่วยความจำเพิ่มเติม O (sqrt (N)) ? ( 40แสดงให้เห็นค่อนข้างเป็นปัญหาที่ยากหรือยาวซึ่งอาจจะเหมาะเป็นโครงการระยะในสถานการณ์ในชั้นเรียน. )
greybeard

4
ฉันคิดว่าเวลาที่ซับซ้อนของอัลกอริทึมในสถานที่ที่กล่าวถึงในเว็บไซต์ penguin.ew คือ O (log n * n ^ 2) เนื่องจากเรามีการรวม log n และแต่ละการผสานนั้นเป็นไปตามลำดับ O (n ^ 2) ใช่มั้ย
code4fun

1
อัลกอริทึมนี้ยังคงเสถียรและในเวลา n log n หรือไม่
Paul Stelian

1
@ PaulStelian - มันไม่เสถียร องค์ประกอบในพื้นที่ทำงานจะถูกจัดเรียงใหม่ตามการดำเนินการสั่งซื้อกับองค์ประกอบในพื้นที่ที่เรียงลำดับ ซึ่งหมายความว่าองค์ประกอบพื้นที่ทำงานที่มีค่าเท่ากันจะได้รับการจัดเรียงใหม่เพื่อไม่ให้อยู่ในลำดับเดิมอีกต่อไป
rcgldr

1
@PaulStelian - Wiki มีบทความสำหรับการเรียงบล็อกผสานซึ่งในขณะที่คุณแสดงความคิดเห็นมีเสถียรภาพ มันทำงานได้ดีที่สุดถ้ามีค่าที่ไม่ซ้ำกันอย่างน้อย 2 · sqrt (n) ซึ่งช่วยให้พวกเขาได้รับคำสั่งใหม่เพื่อให้พื้นที่การทำงานของอาร์เรย์และคงที่
rcgldr

59

รวมถึง "ผลลัพธ์ที่ยิ่งใหญ่" ของบทความนี้จะอธิบายถึงความหลากหลายของการเรียงลำดับแบบผสานในสถานที่ (PDF):

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf

การเรียงลำดับในสถานที่ที่มีการเคลื่อนไหวน้อยลง

Jyrki Katajainen, Tomi A. Pasanen

มันแสดงให้เห็นว่าอาร์เรย์ขององค์ประกอบ n สามารถเรียงลำดับโดยใช้พื้นที่พิเศษ O (1), การย้ายองค์ประกอบ O (n log n / บันทึกการเข้าสู่ระบบ n), และ n log 2 n + O (n log log n) การเปรียบเทียบ นี่เป็นขั้นตอนแรกในการจัดเรียงอัลกอริธึมที่ต้องการการเคลื่อนที่ o (n log n) ในกรณีที่แย่ที่สุดในขณะที่รับประกันการเปรียบเทียบ O (n log n) แต่เนื่องจากปัจจัยคงที่ที่เกี่ยวข้องกับอัลกอริทึมนั้น

ฉันคิดว่ามันก็มีความเกี่ยวข้องเช่นกัน ฉันมีงานพิมพ์ที่วางอยู่รอบ ๆ ส่งต่อให้ฉันโดยเพื่อนร่วมงาน แต่ฉันไม่ได้อ่าน ดูเหมือนว่าจะครอบคลุมทฤษฎีพื้นฐาน แต่ฉันไม่คุ้นเคยเพียงพอกับหัวข้อที่จะตัดสินว่ามันครอบคลุม:

http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681

การผสานที่เสถียรที่สุด

Antonios Symvonis

บทความนี้แสดงวิธีผสานสองลำดับ A และ B ขนาด m และ n, m ≤ n ตามลำดับโดยมีการมอบหมาย O (m + n), O (mlog (n / m + 1)) เปรียบเทียบและใช้ค่าคงที่เท่านั้น จำนวนพื้นที่เพิ่มเติม ผลลัพธ์นี้ตรงกับขอบเขตที่รู้จักทั้งหมด ...


12

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

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

โปรดทราบว่าการดำเนินการนี้ช้ากว่าการรวมการผสานแบบคลาสสิคที่ไม่เข้าที่


9
Quicksort ไม่เสถียร ว่ามันเป็นเรื่องสำคัญสำหรับจำนวนมากของรหัสการผลิต
Donal Fellows

7
quicksort สามารถเสถียรและ iirc merge sort ไม่จำเป็นต้องเสถียรถ้าอยู่ในที่
jk

4
@jk: Quicksort ไม่เสถียร มันเป็นความเร็วที่เกิดขึ้นจากสิ่งนั้นและคุณไม่ควรพยายามเรียกร้องอย่างอื่น เป็นการแลกเปลี่ยนที่ดีมาก ใช่เป็นไปได้ที่จะเชื่อมโยงดัชนีเดิมกับส่วนที่เหลือของคีย์เพื่อให้คุณไม่เคยมีสององค์ประกอบเหมือนกันให้เรียงลำดับที่มั่นคง ที่มาพร้อมกับค่าใช้จ่ายที่จำเป็นของพื้นที่พิเศษบางส่วน (เป็นเส้นตรงในจำนวนองค์ประกอบ) เนื่องจากคุณไม่สามารถรักษาลำดับสัมพัทธ์ขององค์ประกอบ "เทียบเท่า" มิฉะนั้นโดยไม่ต้องหันไปใช้การเคลื่อนไหวองค์ประกอบเพิ่มเติมซึ่งทำลายประสิทธิภาพ
Donal Fellows

4
Quicksort ยังมีกรณีที่เลวร้ายที่สุด O (n ^ 2) สำหรับการป้อนข้อมูลที่สร้างขึ้นเป็นพิเศษ
HoboBen

4
@ DonalFellows: jk พูดถูก quicksort สามารถดำเนินการให้มีความเสถียรโดยไม่มีค่าใช้จ่ายเพิ่มเติม ตรวจสอบ Wikipedia
สนิม

10

ขั้นตอนสำคัญคือการผสานตัวเองเข้าที่ มันไม่ยากเท่าแหล่งที่มาที่ทำออกมา แต่คุณสูญเสียบางสิ่งเมื่อคุณลอง

ดูที่ขั้นตอนเดียวของการรวม:

[... รายการ - เรียงลำดับ ... | x ... รายการ - A ... | y ... รายการ - B ... ]

เรารู้ว่าเรียงลำดับน้อยกว่าทุกอย่างอื่นที่xน้อยกว่าทุกสิ่งทุกอย่างในและYจะน้อยกว่าทุกอย่างอื่นในB ในกรณีที่xน้อยกว่าหรือเท่ากับyคุณเพียงเลื่อนตัวชี้ไปที่จุดเริ่มต้นของAบนจุดเดียว ในกรณีที่ปีน้อยกว่าxคุณได้มีการสับเปลี่ยนปีที่ผ่านมาทั้งเพื่อเรียง ขั้นตอนสุดท้ายนั้นคือสิ่งที่ทำให้ราคาแพง (ยกเว้นในกรณีเลวลง)

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


5
การผสานในสถานที่ของคุณมีความซับซ้อนกรณีที่เลวร้ายที่สุด O (m n) โดยที่ m คือขนาดและ n คือขนาด B นี่คือกรณีที่รายการแรกใน A มีขนาดใหญ่กว่ารายการสุดท้ายใน B ความซับซ้อนสามารถปรับปรุงเป็น O (k log (k) + m + n) โดยที่ k = min (m, n) โดยการเพิ่ม ฮีประหว่าง A และ B ฮีปนี้ควรมีไอเท็มจาก A ซึ่งใหญ่กว่าไอเท็มที่เหลือใน B แต่เล็กกว่าไอเท็มที่เหลือใน A ถ้า A หมดไปก่อนฮีปจะต้องย้ายฮีปไปที่ท้าย B มิฉะนั้นต้องย้ายฮีปไปที่จุดเริ่มต้นของ A จากนั้นไอเท็มฮีปจะต้องโผล่ขึ้นมาแทนที่และกลับด้านเพื่อให้การผสานเสร็จสมบูรณ์
valyala

2
@valyala โปรดทราบว่าเมื่อใช้ heap การเรียงลำดับจะไม่เสถียรอีกต่อไป นอกจากนี้หากคุณใช้ heap คุณสามารถไปกับ heap sort แทน merge sort
martinkunev

8

เพียงสำหรับการอ้างอิงที่นี่เป็นที่ดีการดำเนินการที่มีเสถียรภาพในสถานที่ผสานการเรียงลำดับ ซับซ้อน แต่ไม่เลวร้ายเกินไป

ฉันลงเอยด้วยการใช้ทั้งการเรียงลำดับแบบผสานที่มีเสถียรภาพและการเรียงลำดับแบบเร็วแบบเสถียรใน Java โปรดทราบความซับซ้อนคือ O (n (บันทึก n) ^ 2)


4

ตัวอย่างของการผสานแบบไม่มีบัฟเฟอร์ใน C

#define SWAP(type, a, b) \
    do { type t=(a);(a)=(b);(b)=t; } while (0)

static void reverse_(int* a, int* b)
{
    for ( --b; a < b; a++, b-- )
       SWAP(int, *a, *b);
}
static int* rotate_(int* a, int* b, int* c)
/* swap the sequence [a,b) with [b,c). */
{
    if (a != b && b != c)
     {
       reverse_(a, b);
       reverse_(b, c);
       reverse_(a, c);
     }
    return a + (c - b);
}

static int* lower_bound_(int* a, int* b, const int key)
/* find first element not less than @p key in sorted sequence or end of
 * sequence (@p b) if not found. */
{
    int i;
    for ( i = b-a; i != 0; i /= 2 )
     {
       int* mid = a + i/2;
       if (*mid < key)
          a = mid + 1, i--;
     }
    return a;
}
static int* upper_bound_(int* a, int* b, const int key)
/* find first element greater than @p key in sorted sequence or end of
 * sequence (@p b) if not found. */
{
    int i;
    for ( i = b-a; i != 0; i /= 2 )
     {
       int* mid = a + i/2;
       if (*mid <= key)
          a = mid + 1, i--;
     }
    return a;
}

static void ip_merge_(int* a, int* b, int* c)
/* inplace merge. */
{
    int n1 = b - a;
    int n2 = c - b;

    if (n1 == 0 || n2 == 0)
       return;
    if (n1 == 1 && n2 == 1)
     {
       if (*b < *a)
          SWAP(int, *a, *b);
     }
    else
     {
       int* p, * q;

       if (n1 <= n2)
          p = upper_bound_(a, b, *(q = b+n2/2));
       else
          q = lower_bound_(b, c, *(p = a+n1/2));
       b = rotate_(p, b, q);

       ip_merge_(a, p, b);
       ip_merge_(b, q, c);
     }
}

void mergesort(int* v, int n)
{
    if (n > 1)
     {
       int h = n/2;
       mergesort(v, h); mergesort(v+h, n-h);
       ip_merge_(v, v+h, v+n);
     }
}

ตัวอย่างของการผสานการปรับตัว (ปรับให้เหมาะสม)

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

#include <stdlib.h>
#include <string.h>

static int* copy_(const int* a, const int* b, int* out)
{
    int count = b - a;
    if (a != out)
       memcpy(out, a, count*sizeof(int));
    return out + count;
}
static int* copy_backward_(const int* a, const int* b, int* out)
{
    int count = b - a;
    if (b != out)
       memmove(out - count, a, count*sizeof(int));
    return out - count;
}

static int* merge_(const int* a1, const int* b1, const int* a2,
  const int* b2, int* out)
{
    while ( a1 != b1 && a2 != b2 )
       *out++ = (*a1 <= *a2) ? *a1++ : *a2++;
    return copy_(a2, b2, copy_(a1, b1, out));
}
static int* merge_backward_(const int* a1, const int* b1,
  const int* a2, const int* b2, int* out)
{
    while ( a1 != b1 && a2 != b2 )
       *--out = (*(b1-1) > *(b2-1)) ? *--b1 : *--b2;
    return copy_backward_(a1, b1, copy_backward_(a2, b2, out));
}

static unsigned int gcd_(unsigned int m, unsigned int n)
{
    while ( n != 0 )
     {
       unsigned int t = m % n;
       m = n;
       n = t;
     }
    return m;
}
static void rotate_inner_(const int length, const int stride,
  int* first, int* last)
{
    int* p, * next = first, x = *first;
    while ( 1 )
     {
       p = next;
       if ((next += stride) >= last)
          next -= length;
       if (next == first)
          break;
       *p = *next;
     }
    *p = x;
}
static int* rotate_(int* a, int* b, int* c)
/* swap the sequence [a,b) with [b,c). */
{
    if (a != b && b != c)
     {
       int n1 = c - a;
       int n2 = b - a;

       int* i = a;
       int* j = a + gcd_(n1, n2);

       for ( ; i != j; i++ )
          rotate_inner_(n1, n2, i, c);
     }
    return a + (c - b);
}

static void ip_merge_small_(int* a, int* b, int* c)
/* inplace merge.
 * @note faster for small sequences. */
{
    while ( a != b && b != c )
       if (*a <= *b)
          a++;
       else
        {
          int* p = b+1;
          while ( p != c && *p < *a )
             p++;
          rotate_(a, b, p);
          b = p;
        }
}
static void ip_merge_(int* a, int* b, int* c, int* t, const int ts)
/* inplace merge.
 * @note works with or without additional memory. */
{
    int n1 = b - a;
    int n2 = c - b;

    if (n1 <= n2 && n1 <= ts)
     {
       merge_(t, copy_(a, b, t), b, c, a);
     }
    else if (n2 <= ts)
     {
       merge_backward_(a, b, t, copy_(b, c, t), c);
     }
    /* merge without buffer. */
    else if (n1 + n2 < 48)
     {
       ip_merge_small_(a, b, c);
     }
    else
     {
       int* p, * q;

       if (n1 <= n2)
          p = upper_bound_(a, b, *(q = b+n2/2));
       else
          q = lower_bound_(b, c, *(p = a+n1/2));
       b = rotate_(p, b, q);

       ip_merge_(a, p, b, t, ts);
       ip_merge_(b, q, c, t, ts);
     }
}
static void ip_merge_chunk_(const int cs, int* a, int* b, int* t,
  const int ts)
{
    int* p = a + cs*2;
    for ( ; p <= b; a = p, p += cs*2 )
       ip_merge_(a, a+cs, p, t, ts);
    if (a+cs < b)
       ip_merge_(a, a+cs, b, t, ts);
}

static void smallsort_(int* a, int* b)
/* insertion sort.
 * @note any stable sort with low setup cost will do. */
{
    int* p, * q;
    for ( p = a+1; p < b; p++ )
     {
       int x = *p;
       for ( q = p; a < q && x < *(q-1); q-- )
          *q = *(q-1);
       *q = x;
     }
}
static void smallsort_chunk_(const int cs, int* a, int* b)
{
    int* p = a + cs;
    for ( ; p <= b; a = p, p += cs )
       smallsort_(a, p);
    smallsort_(a, b);
}

static void mergesort_lower_(int* v, int n, int* t, const int ts)
{
    int cs = 16;
    smallsort_chunk_(cs, v, v+n);
    for ( ; cs < n; cs *= 2 )
       ip_merge_chunk_(cs, v, v+n, t, ts);
}

static void* get_buffer_(int size, int* final)
{
    void* p = NULL;
    while ( size != 0 && (p = malloc(size)) == NULL )
       size /= 2;
    *final = size;
    return p;
}
void mergesort(int* v, int n)
{
    /* @note buffer size may be in the range [0,(n+1)/2]. */
    int request = (n+1)/2 * sizeof(int);
    int actual;
    int* t = (int*) get_buffer_(request, &actual);

    /* @note allocation failure okay. */
    int tsize = actual / sizeof(int);
    mergesort_lower_(v, n, t, tsize);
    free(t);
}

2
คุณเขียนสิ่งนี้หรือไม่? มันจะเอาชนะความยากลำบากที่แสดงออกมาในคำตอบอื่น ๆ ได้อย่างไร? เวลาทำงานคืออะไร
โทมัส Ahle

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

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

3

คำตอบนี้มีตัวอย่างรหัสซึ่งใช้อัลกอริทึมที่อธิบายไว้ในเอกสารการผสานการปฏิบัติในสถานที่โดย Bing-Chao Huang และ Michael A. Langston ฉันต้องยอมรับว่าฉันไม่เข้าใจรายละเอียด แต่ความซับซ้อนของขั้นตอนการรวมที่ได้คือ O (n)

จากมุมมองของการปฏิบัติมีหลักฐานว่าการใช้งานในสถานที่บริสุทธิ์ไม่ทำงานได้ดีขึ้นในสถานการณ์โลกแห่งความจริง ตัวอย่างเช่นมาตรฐาน C ++ กำหนดstd :: inplace_mergeซึ่งเป็นชื่อที่แสดงถึงการดำเนินการผสานแบบแทนที่

สมมติว่าโดยทั่วไปแล้วไลบรารี C ++ นั้นจะได้รับการปรับให้เหมาะสมที่สุดนั้นเป็นเรื่องที่น่าสนใจเพื่อดูว่ามีการใช้งานอย่างไร:

1) libstdc ++ (ส่วนหนึ่งของฐานรหัส GCC): std :: inplace_merge

การใช้งานได้รับมอบหมายให้__inplace_mergeซึ่งหลบปัญหาโดยพยายามจัดสรรบัฟเฟอร์ชั่วคราว:

typedef _Temporary_buffer<_BidirectionalIterator, _ValueType> _TmpBuf;
_TmpBuf __buf(__first, __len1 + __len2);

if (__buf.begin() == 0)
  std::__merge_without_buffer
    (__first, __middle, __last, __len1, __len2, __comp);
else
  std::__merge_adaptive
   (__first, __middle, __last, __len1, __len2, __buf.begin(),
     _DistanceType(__buf.size()), __comp);

มิฉะนั้นจะกลับไปใช้งาน ( __merge_without_buffer ) ซึ่งไม่ต้องใช้หน่วยความจำเพิ่มเติม แต่จะไม่ทำงานในเวลา O (n) อีกต่อไป

2) libc ++ (ส่วนหนึ่งของฐานรหัส Clang): std :: inplace_merge

หน้าตาคล้ายกัน มันได้รับมอบหมายให้เป็นฟังก์ชั่นซึ่งยังพยายามที่จะจัดสรรบัฟเฟอร์ ขึ้นอยู่กับว่ามีองค์ประกอบเพียงพอหรือไม่นั้นจะเลือกการนำไปใช้ ฟังก์ชั่นอย่างต่อเนื่องหน่วยความจำสำรองเรียกว่า__buffered_inplace_merge

บางทีแม้แต่ทางเลือกยังคงเป็นเวลา O (n) แต่ประเด็นก็คือพวกเขาไม่ได้ใช้งานหากมีหน่วยความจำชั่วคราว


โปรดทราบว่ามาตรฐาน C ++ ให้อิสระในการนำไปใช้ในการเลือกวิธีการนี้โดยลดความซับซ้อนที่ต้องการจาก O (n) ถึง O (N log N):

ความซับซ้อน: เปรียบเทียบ N-1 อย่างแน่นอนหากมีหน่วยความจำเพิ่มเติมเพียงพอ หากหน่วยความจำไม่เพียงพอการเปรียบเทียบ O (N log N)

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


2

นี่คือเวอร์ชั่น C ของฉัน:

void mergesort(int *a, int len) {
  int temp, listsize, xsize;

  for (listsize = 1; listsize <= len; listsize*=2) {
    for (int i = 0, j = listsize; (j+listsize) <= len; i += (listsize*2), j += (listsize*2)) {
      merge(& a[i], listsize, listsize);
    }
  }

  listsize /= 2;

  xsize = len % listsize;
  if (xsize > 1)
    mergesort(& a[len-xsize], xsize);

  merge(a, listsize, xsize);
}

void merge(int *a, int sizei, int sizej) {
  int temp;
  int ii = 0;
  int ji = sizei;
  int flength = sizei+sizej;

  for (int f = 0; f < (flength-1); f++) {
    if (sizei == 0 || sizej == 0)
      break;

    if (a[ii] < a[ji]) {
      ii++;
      sizei--;
    }
    else {
      temp = a[ji];

      for (int z = (ji-1); z >= ii; z--)
        a[z+1] = a[z];  
      ii++;

      a[f] = temp;

      ji++;
      sizej--;
    }
  }
}

โปรดทราบว่าการใช้งานนี้ใช้เวลาΘ (n ^ 2 log n) เวลาในกรณีที่เลวร้ายที่สุด (อาร์เรย์กลับรายการ)
martinkunev

1

มีการใช้งานการเรียงลำดับแบบผสานค่อนข้างง่ายโดยใช้เทคนิคดั้งเดิมของ Kronrod แต่มีการใช้งานที่ง่ายขึ้น ตัวอย่างภาพที่แสดงให้เห็นเทคนิคนี้สามารถพบได้ที่นี่: http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm

นอกจากนี้ยังมีลิงก์ไปยังการวิเคราะห์เชิงทฤษฎีโดยละเอียดโดยผู้เขียนคนเดียวกันที่เชื่อมโยงกับลิงค์นี้


ลิงค์นี้ส่งผลให้ 403
Charlotte Tan

3
ลิงก์ได้รับการแก้ไข เอกสารประกอบมีความลับไปจนถึงจุดที่ไม่ชัดเจน ฉันได้รับความประทับใจว่ามีความคิดที่น่าสนใจอยู่ที่นั่น แต่ไม่มีอัลกอริธึมใดนำเสนอเพียงชุดไดอะแกรมและคำอธิบายที่ค่อนข้างอ่อนแอ ฉันไม่สามารถผูกคำอธิบายที่อ่อนแอกับไดอะแกรมในวิธีที่น่าสนใจดังนั้นฉันจึงยอมแพ้
Ira Baxter

-6

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

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

เช่น)

First___Array: 3, 7, 8, 9

อาร์เรย์ที่สอง: 1, 2, 4, 5

จากนั้น 7, 8, 9 ทำให้อาร์เรย์ที่สองสลับ (เลื่อนไปทีละอัน) องค์ประกอบทั้งหมดโดยหนึ่งครั้งในแต่ละครั้งเพื่อวางตัวเองในครั้งสุดท้าย

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

https://github.com/skanagavelu/algorithams/blob/master/src/sorting/MergeSort.java

package sorting;

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
    int[] array = { 5, 6, 10, 3, 9, 2, 12, 1, 8, 7 };
    mergeSort(array, 0, array.length -1);
    System.out.println(Arrays.toString(array));

    int[] array1 = {4, 7, 2};
    System.out.println(Arrays.toString(array1));
    mergeSort(array1, 0, array1.length -1);
    System.out.println(Arrays.toString(array1));
    System.out.println("\n\n");

    int[] array2 = {4, 7, 9};
    System.out.println(Arrays.toString(array2));
    mergeSort(array2, 0, array2.length -1);
    System.out.println(Arrays.toString(array2));
    System.out.println("\n\n");

    int[] array3 = {4, 7, 5};
    System.out.println(Arrays.toString(array3));
    mergeSort(array3, 0, array3.length -1);
    System.out.println(Arrays.toString(array3));
    System.out.println("\n\n");

    int[] array4 = {7, 4, 2};
    System.out.println(Arrays.toString(array4));
    mergeSort(array4, 0, array4.length -1);
    System.out.println(Arrays.toString(array4));
    System.out.println("\n\n");

    int[] array5 = {7, 4, 9};
    System.out.println(Arrays.toString(array5));
    mergeSort(array5, 0, array5.length -1);
    System.out.println(Arrays.toString(array5));
    System.out.println("\n\n");

    int[] array6 = {7, 4, 5};
    System.out.println(Arrays.toString(array6));
    mergeSort(array6, 0, array6.length -1);
    System.out.println(Arrays.toString(array6));
    System.out.println("\n\n");

    //Handling array of size two
    int[] array7 = {7, 4};
    System.out.println(Arrays.toString(array7));
    mergeSort(array7, 0, array7.length -1);
    System.out.println(Arrays.toString(array7));
    System.out.println("\n\n");

    int input1[] = {1};
    int input2[] = {4,2};
    int input3[] = {6,2,9};
    int input4[] = {6,-1,10,4,11,14,19,12,18};
    System.out.println(Arrays.toString(input1));
    mergeSort(input1, 0, input1.length-1);
    System.out.println(Arrays.toString(input1));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input2));
    mergeSort(input2, 0, input2.length-1);
    System.out.println(Arrays.toString(input2));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input3));
    mergeSort(input3, 0, input3.length-1);
    System.out.println(Arrays.toString(input3));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input4));
    mergeSort(input4, 0, input4.length-1);
    System.out.println(Arrays.toString(input4));
    System.out.println("\n\n");
}

private static void mergeSort(int[] array, int p, int r) {
    //Both below mid finding is fine.
    int mid = (r - p)/2 + p;
    int mid1 = (r + p)/2;
    if(mid != mid1) {
        System.out.println(" Mid is mismatching:" + mid + "/" + mid1+ "  for p:"+p+"  r:"+r);
    }

    if(p < r) {
        mergeSort(array, p, mid);
        mergeSort(array, mid+1, r);
//      merge(array, p, mid, r);
        inPlaceMerge(array, p, mid, r);
        }
    }

//Regular merge
private static void merge(int[] array, int p, int mid, int r) {
    int lengthOfLeftArray = mid - p + 1; // This is important to add +1.
    int lengthOfRightArray = r - mid;

    int[] left = new int[lengthOfLeftArray];
    int[] right = new int[lengthOfRightArray];

    for(int i = p, j = 0; i <= mid; ){
        left[j++] = array[i++];
    }

    for(int i = mid + 1, j = 0; i <= r; ){
        right[j++] = array[i++];
    }

    int i = 0, j = 0;
    for(; i < left.length && j < right.length; ) {
        if(left[i] < right[j]){
            array[p++] = left[i++];
        } else {
            array[p++] = right[j++];
        }
    }
    while(j < right.length){
        array[p++] = right[j++];
    } 
    while(i < left.length){
        array[p++] = left[i++];
    }
}

//InPlaceMerge no extra array
private static void inPlaceMerge(int[] array, int p, int mid, int r) {
    int secondArrayStart = mid+1;
    int prevPlaced = mid+1;
    int q = mid+1;
    while(p < mid+1 && q <= r){
        boolean swapped = false;
        if(array[p] > array[q]) {
            swap(array, p, q);
            swapped = true;
        }   
        if(q != secondArrayStart && array[p] > array[secondArrayStart]) {
            swap(array, p, secondArrayStart);
            swapped = true;
        }
        //Check swapped value is in right place of second sorted array
        if(swapped && secondArrayStart+1 <= r && array[secondArrayStart+1] < array[secondArrayStart]) {
            prevPlaced = placeInOrder(array, secondArrayStart, prevPlaced);
        }
        p++;
        if(q < r) {     //q+1 <= r) {
            q++;
        }
    }
}

private static int placeInOrder(int[] array, int secondArrayStart, int prevPlaced) {
    int i = secondArrayStart;
    for(; i < array.length; i++) {
        //Simply swap till the prevPlaced position
        if(secondArrayStart < prevPlaced) {
            swap(array, secondArrayStart, secondArrayStart+1);
            secondArrayStart++;
            continue;
        }
        if(array[i] < array[secondArrayStart]) {
            swap(array, i, secondArrayStart);
            secondArrayStart++;
        } else if(i != secondArrayStart && array[i] > array[secondArrayStart]){
            break;
        }
    }
    return secondArrayStart;
}

private static void swap(int[] array, int m, int n){
    int temp = array[m];
    array[m] = array[n];
    array[n] = temp;
}
}

3
มันเป็นทั้ง O (n ^ 2) และยังไม่สามารถอ่านได้สูง (เพราะในข้อผิดพลาดไวยากรณ์เป็นครั้งคราวและไม่สอดคล้องกัน / รูปแบบที่ไม่ดี)
glaba
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.