วิธีที่มีประสิทธิภาพมากที่สุดในการทดสอบช่วงจำนวนเต็มสองช่วงสำหรับการทับซ้อนคืออะไร


252

เมื่อกำหนดจำนวนเต็มสองช่วง [x1: x2] และ [y1: y2] โดยที่ x1 ≤ x2 และ y1 ≤ y2 วิธีที่มีประสิทธิภาพมากที่สุดในการทดสอบว่ามีการทับซ้อนกันของช่วงใดหรือไม่

ใช้งานง่ายมีดังนี้:

bool testOverlap(int x1, int x2, int y1, int y2) {
  return (x1 >= y1 && x1 <= y2) ||
         (x2 >= y1 && x2 <= y2) ||
         (y1 >= x1 && y1 <= x2) ||
         (y2 >= x1 && y2 <= x2);
}

แต่ฉันคาดว่าจะมีวิธีการคำนวณที่มีประสิทธิภาพมากกว่านี้

วิธีใดที่จะมีประสิทธิภาพมากที่สุดในแง่ของการปฏิบัติการที่น้อยที่สุด


อาจเกี่ยวข้องกันอย่างน่าสนใจสำหรับบางคน - stackoverflow.com/q/17138760/104380
vsync

คำตอบ:


454

การซ้อนทับหมายถึงอะไร มันหมายความว่ามีบางจำนวน C ซึ่งอยู่ในทั้งสองช่วงคือ

x1 <= C <= x2

และ

y1 <= C <= y2

ทีนี้ถ้าเราได้รับอนุญาตให้สมมติว่าช่วงนั้นมีรูปแบบที่ดี (ดังนั้น x1 <= x2 และ y1 <= y2) ก็เพียงพอแล้วที่จะทดสอบ

x1 <= y2 && y1 <= x2

1
ฉันเชื่อว่ามันควรจะเป็นx1 <= y2 && y1 >= x2ไม่
David Beck

8
@DavidBeck: ไม่ถ้า y1> x2 ดังนั้นช่วงจะไม่ทับซ้อนกัน (เช่นพิจารณา [1: 2] และ [3: 4]: y1 = 3 และ x2 = 2 ดังนั้น y1> x2 แต่ไม่มีการทับซ้อนกัน) .
Simon Nickerson

8
นี้จะเป็นคำตอบที่ดีกว่าถ้าคุณอธิบายเหตุผลมากขึ้นอีกนิด
shoosh

2
@Vineet Deoraj - ทำไมคุณคิดว่ามันใช้งานไม่ได้? x1 = 1, y1 = 1, x2 = 1, y2 = 1 ดังนั้น x1 <= y2 && y1 <= x2 เป็นจริงดังนั้นจึงมีการทับซ้อนกัน
dcp

2
คำอธิบายอยู่ที่นี่: stackoverflow.com/questions/325933/…
อเล็กซ์

138

กำหนดสองช่วง [x1, x2], [y1, y2]

def is_overlapping(x1,x2,y1,y2):
    return max(x1,y1) <= min(x2,y2)

4
@ uyuyuy99 - ไม่ได้มีประสิทธิภาพเท่านี้เพราะเมื่อการตรวจสอบนี้ถูกทำหลายครั้งต่อวินาทีฟังก์ชั่นการโทรเป็นสิ่งที่คุณต้องการหลีกเลี่ยงและทำคณิตศาสตร์ให้มากที่สุดด้วยตัวเอง
vsync

7
@vsync เบราว์เซอร์ที่ทันสมัยจะอินไลน์และเพิ่มประสิทธิภาพฟังก์ชั่นเช่น Math.max ไม่ควรมีผลกระทบต่อประสิทธิภาพการทำงาน
Ashton Six

1
@AshtonWar - น่าสนใจ คุณมีบทความอธิบายสิ่งที่ได้รับการอินไลน์และสิ่งที่ไม่?
vsync

@vsync ไม่ แต่ฉันแน่ใจว่าคุณสามารถค้นหาข้อมูลได้ด้วยตัวเอง
Ashton Six

6
นอกจากนี้โปรดทราบว่าmin(x2,y2) - max(x1,y1)มีจำนวนของการทับซ้อนในกรณีที่คุณต้องการ
user1556435

59

นี่สามารถบิดสมองมนุษย์ปกติได้ง่ายดังนั้นฉันจึงพบวิธีการมองเห็นที่เข้าใจง่ายขึ้น:

ความบ้าซ้อนกัน

คำอธิบาย

หากสองช่วงคือ "อ้วนเกินไป"เพื่อให้พอดีกับช่องที่มีผลรวมความกว้างของทั้งสองอย่างพอดี

สำหรับช่วง[a1, a2]และ[b1, b2]สิ่งนี้จะเป็น:

/**
 * we are testing for:
 *     max point - min point < w1 + w2    
 **/
if max(a2, b2) - min(a1, b1) < (a2 - a1) + (b2 - b1) {
  // too fat -- they overlap!
}

3
มีหลายกรณีมากกว่าที่ปรากฎในรูปภาพของคุณ เช่นจะเกิดอะไรขึ้นถ้า w2 เริ่มก่อน w1 และสิ้นสุดลงหลังจาก w1
WilliamKF

7
@WilliamKF ตรรกะยืนจริง
FloatingRock

2
เห็นด้วย แต่ฉันคิดว่ามันอาจช่วยให้รูปภาพที่สาม
WilliamKF

3
@WilliamKF คุณต้องมีรูปภาพจำนวนมากขึ้นมีชุดค่าผสม 16 แบบที่สามารถวางช่วงได้ 2 ช่วง ...
Peter

3
ระวังถ้าคุณใช้วิธีนี้เพราะผลรวมa2 - a1 + b2 - b1สามารถล้น ในการแก้ไขให้จัดเรียงสูตรmax(a2, b2) - a2 - b2 < min(a1, b1) - a1 - b1ใหม่ซึ่งช่วยลดความยุ่งยากในmax(a1, b1) < min(a2, b2)การคำนวณทางคณิตศาสตร์และหลีกเลี่ยงการโอเวอร์โฟลว์ที่อาจเกิดขึ้นได้ (นี่คือคำตอบของ AX-Labs ด้านล่าง) ในกรณีพิเศษที่คุณรู้ว่าb2-b1=a2-a1อีกสายใยประโยชน์ของสูตร FloatingRock เป็นซึ่งจะกลายเป็นmax(a2, b2) - min(a1, b1) - (b2 - b1) < a2-a1 abs(b1-a1) < a2 - a1
เปาโล Bonzini

44

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

เมื่อใดที่ 2 ช่วงไม่ทับซ้อนกัน พวกเขาจะไม่ทับซ้อนกันเมื่อหนึ่งในนั้นเริ่มต้นหลังจากที่หนึ่งจบ:

dont_overlap = x2 < y1 || x1 > y2

ตอนนี้มันง่ายที่จะแสดงเมื่อมันทับซ้อนกัน:

overlap = !dont_overlap = !(x2 < y1 || x1 > y2) = (x2 >= y1 && x1 <= y2)

1
สำหรับฉันการเข้าใจการแสดงออกง่ายขึ้นคือ: x2 <y1 || y2 <x1 // โดยที่ฉันใช้ 'น้อยกว่า' แทน "มากกว่า"
Park JongBum

26

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

ป้อนคำอธิบายรูปภาพที่นี่


2
ครอบคลุมทุกกรณี
3290180

10

ฉันคิดว่าคำถามเกี่ยวกับเร็วที่สุดไม่ใช่รหัสสั้นที่สุด เวอร์ชันที่เร็วที่สุดต้องหลีกเลี่ยงสาขาดังนั้นเราจึงสามารถเขียนสิ่งนี้:

สำหรับกรณีง่าย:

static inline bool check_ov1(int x1, int x2, int y1, int y2){
    // insetead of x1 < y2 && y1 < x2
    return (bool)(((unsigned int)((y1-x2)&(x1-y2))) >> (sizeof(int)*8-1));
};

หรือสำหรับกรณีนี้:

static inline bool check_ov2(int x1, int x2, int y1, int y2){
    // insetead of x1 <= y2 && y1 <= x2
    return (bool)((((unsigned int)((x2-y1)|(y2-x1))) >> (sizeof(int)*8-1))^1);
};

7
มีความเชื่อในคอมไพเลอร์ของคุณ การแสดงออกx1 <= y2 && y1 <= x2 ไม่ได้มีสาขาใด ๆ ในนั้นสมมติว่าคอมไพเลอร์ที่มีความสามารถพอสมควรและสถาปัตยกรรมของ CPU (แม้ในปี 2010) อันที่จริงแล้วบน x86 รหัสที่สร้างขึ้นนั้นเหมือนกันสำหรับนิพจน์ทั่วไปกับรหัสในคำตอบนี้
SørenLøvborg


4

หากคุณจัดการกับสองช่วงที่กำหนด[x1:x2]และ[y1:y2]เป็นธรรมชาติ / ต่อต้านธรรมชาติในช่วงเวลาเดียวกันที่:

  • ธรรมชาติเพื่อ: x1 <= x2 && y1 <= y2หรือ
  • คำสั่งต่อต้านธรรมชาติ: x1 >= x2 && y1 >= y2

จากนั้นคุณอาจต้องการใช้สิ่งนี้เพื่อตรวจสอบ:

มันซ้อนทับกัน <=> (y2 - x1) * (x2 - y1) >= 0

มีเพียงสี่การดำเนินงานที่เกี่ยวข้อง:

  • การลบสองครั้ง
  • การคูณหนึ่งครั้ง
  • การเปรียบเทียบหนึ่งรายการ

1

หากมีคนกำลังมองหาหนึ่งซับซึ่งคำนวณการทับซ้อนจริง:

int overlap = ( x2 > y1 || y2 < x1 ) ? 0 : (y2 >= y1 && x2 <= y1 ? y1 : y2) - ( x2 <= x1 && y2 >= x1 ? x1 : x2) + 1; //max 11 operations

หากคุณต้องการการดำเนินการที่น้อยลง แต่มีตัวแปรเพิ่มเติมสองสามอย่าง:

bool b1 = x2 <= y1;
bool b2 = y2 >= x1;
int overlap = ( !b1 || !b2 ) ? 0 : (y2 >= y1 && b1 ? y1 : y2) - ( x2 <= x1 && b2 ? x1 : x2) + 1; // max 9 operations

1

คิดในทางกลับกัน: จะทำให้ช่วง 2 ช่วงไม่ทับซ้อนกันได้อย่างไร รับ[x1, x2]แล้ว[y1, y2]ควรอยู่นอก [x1, x2]คือy1 < y2 < x1 or x2 < y1 < y2ซึ่งเทียบเท่ากับy2 < x1 or x2 < y1ซึ่งเทียบเท่ากับ

ดังนั้นเงื่อนไขในการทำให้ช่วง 2 ทับซ้อนกัน: not(y2 < x1 or x2 < y1)ซึ่งเทียบเท่ากับy2 >= x1 and x2 >= y1(เหมือนกับคำตอบที่ยอมรับโดย Simon)


ดูเหมือนกับสิ่งที่ @damluar ตอบ (2 มี.ค. 59 เวลา 17:36)
Nakilon

0

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

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


2
คอมไพเลอร์ทั้งหมดจะ ภาษาที่ใช้ในปัจจุบันทั้งหมด (ตามความรู้ของฉัน) ที่มีไวยากรณ์แบบ C (C, C ++, C #, Java, ฯลฯ ) ใช้ตัวดำเนินการบูลีนแบบลัดวงจรและเป็นส่วนหนึ่งของมาตรฐานต่างๆที่ควบคุมภาษาเหล่านั้น หากผลลัพธ์ของค่า lefthand เพียงพอที่จะกำหนดผลลัพธ์ของการดำเนินการค่า righthand จะไม่ถูกประเมิน
Jonathan Grynspan

1
Mark H - คอมไพเลอร์จะข้ามประโยคที่สองหากทำได้: ดังนั้นหากคุณมีฟังก์ชั่นที่บอกว่า: foo (int c) {int i = 0; if (c <3 || ++ i == argc) printf ("Inside \ n"); printf ("ฉันคือ% d \ n", i); Foo (2) จะพิมพ์: ภายใน i คือ 0 และ Foo (4) จะพิมพ์: i คือ 1 (ทดสอบกับ gcc 4.4.3 แต่ฉันได้พึ่งพาพฤติกรรมนี้สำหรับรหัสที่น่าเกลียดใน icc ด้วย)
J Teller

0

กรณีของฉันแตกต่างกัน ฉันต้องการตรวจสอบช่วงเวลาสองช่วงซ้อนทับกัน ไม่ควรมีการทับซ้อนของหน่วยเวลา นี่คือการใช้งานไป

    func CheckRange(as, ae, bs, be int) bool {
    return (as >= be) != (ae > bs)
    }

กรณีทดสอบ

if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 6, 9) != true {
        t.Error("Expected 2,8,6,9 to equal TRUE")
    }

    if CheckRange(2, 8, 8, 9) != false {
        t.Error("Expected 2,8,8,9 to equal FALSE")
    }

    if CheckRange(2, 8, 4, 6) != true {
        t.Error("Expected 2,8,4,6 to equal TRUE")
    }

    if CheckRange(2, 8, 1, 9) != true {
        t.Error("Expected 2,8,1,9 to equal TRUE")
    }

    if CheckRange(4, 8, 1, 3) != false {
        t.Error("Expected 4,8,1,3 to equal FALSE")
    }

    if CheckRange(4, 8, 1, 4) != false {
        t.Error("Expected 4,8,1,4 to equal FALSE")
    }

    if CheckRange(2, 5, 6, 9) != false {
        t.Error("Expected 2,5,6,9 to equal FALSE")
    }

    if CheckRange(2, 5, 5, 9) != false {
        t.Error("Expected 2,5,5,9 to equal FALSE")
    }

คุณสามารถเห็นว่ามีรูปแบบ XOR ในการเปรียบเทียบขอบเขต


-10

นี่คือรุ่นของฉัน:

int xmin = min(x1,x2)
  , xmax = max(x1,x2)
  , ymin = min(y1,y2)
  , ymax = max(y1,y2);

for (int i = xmin; i < xmax; ++i)
    if (ymin <= i && i <= ymax)
        return true;

return false;

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


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

12
-1: นี่ไม่ใช่ micro-optimization; นี่เป็นการเลือกอัลกอริทึมที่เหมาะสม อัลกอริทึมของคุณคือ O (n) เมื่อมีตัวเลือก O (1) อย่างง่าย
Simon Nickerson

นี่คือสิ่งที่เกิดขึ้นเมื่อ "การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด" กลายเป็นหลักการทางศาสนาที่ไม่อาจล่วงละเมิดได้สำหรับผู้ที่ไม่ได้ใช้งานแทนที่จะเป็นข้อสังเกตที่จริงจังครึ่งหนึ่งในรูปแบบพฤติกรรมบางครั้ง
rghome
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.