เรียงลำดับคะแนนตามเข็มนาฬิกาหรือไม่


156

กำหนดอาร์เรย์ของ x, y คะแนนฉันจะจัดเรียงคะแนนของอาร์เรย์นี้ตามลำดับตามเข็มนาฬิกา (รอบจุดศูนย์กลางเฉลี่ยโดยรวมของพวกเขา) ได้อย่างไร เป้าหมายของฉันคือการส่งคะแนนไปยังฟังก์ชั่นการสร้างบรรทัดเพื่อจบลงด้วยสิ่งที่ดูค่อนข้าง "แข็ง" เท่าที่จะเป็นไปได้โดยไม่ต้องมีการตัดกัน

สำหรับสิ่งที่คุ้มค่าฉันกำลังใช้ Lua แต่โค้ดปลอมใด ๆ จะได้รับการชื่นชม

อัปเดต:สำหรับการอ้างอิงนี่คือรหัส Lua ตามคำตอบที่ยอดเยี่ยมของ Ciamej (ไม่ต้องใส่คำนำหน้า "แอพ" ของฉัน):

function appSortPointsClockwise(points)
    local centerPoint = appGetCenterPointOfPoints(points)
    app.pointsCenterPoint = centerPoint
    table.sort(points, appGetIsLess)
    return points
end

function appGetIsLess(a, b)
    local center = app.pointsCenterPoint

    if a.x >= 0 and b.x < 0 then return true
    elseif a.x == 0 and b.x == 0 then return a.y > b.y
    end

    local det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)
    if det < 0 then return true
    elseif det > 0 then return false
    end

    local d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y)
    local d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y)
    return d1 > d2
end

function appGetCenterPointOfPoints(points)
    local pointsSum = {x = 0, y = 0}
    for i = 1, #points do pointsSum.x = pointsSum.x + points[i].x; pointsSum.y = pointsSum.y + points[i].y end
    return {x = pointsSum.x / #points, y = pointsSum.y / #points}
end


1
ลองคิดคำนวณมุมของเส้นเรเดียลผ่านจุดนั้น จากนั้นจัดเรียงตามมุม
ประธานาธิบดี James K. Polk

ในกรณีที่คุณไม่ทราบ lua มีฟังก์ชัน builtin ipairs(tbl)ที่วนซ้ำดัชนีและค่าของ tbl ตั้งแต่ 1 ถึง #tbl ดังนั้นสำหรับการคำนวณผลรวมคุณสามารถทำได้ซึ่งคนส่วนใหญ่พบว่าหน้าตาดีขึ้น:for _, p in ipairs(points) do pointsSum.x = pointsSum.x + p.x; pointsSum.y = pointsSum.y + p.y end
Ponkadoodle

2
@Wallacoloo นั่นเป็นเนื้อหาที่สูงมาก นอกจากนี้ในวานิลลา Lua ipairsนั้นช้ากว่าตัวเลขสำหรับลูปอย่างมาก
Alexander Gladysh

ฉันต้องทำการเปลี่ยนแปลงเล็กน้อยเพื่อให้มันทำงานกับกรณีของฉัน (เพียงเปรียบเทียบสองจุดที่เกี่ยวข้องกับศูนย์) gist.github.com/personalnadir/6624172 การเปรียบเทียบทั้งหมดกับ 0 ในรหัสดูเหมือนว่าจะมีการแจกแจงคะแนนรอบต้นกำเนิดเมื่อเทียบกับจุดที่กำหนดเอง ฉันยังคิดว่าเงื่อนไขแรกจะเรียงลำดับจุดต่ำกว่าจุดกึ่งกลางอย่างไม่ถูกต้อง ขอบคุณสำหรับรหัสแม้ว่ามันเป็นประโยชน์จริงๆ!
personalnadir

คำตอบ:


192

ก่อนอื่นให้คำนวณจุดกึ่งกลาง จากนั้นเรียงลำดับคะแนนโดยใช้อัลกอริทึมการเรียงลำดับที่คุณต้องการ แต่ใช้รูทีนการเปรียบเทียบพิเศษเพื่อพิจารณาว่าจุดใดจุดหนึ่งน้อยกว่าจุดอื่น

คุณสามารถตรวจสอบว่าจุดหนึ่ง (a) อยู่ทางซ้ายหรือทางขวาของอีกจุด (b) ที่เกี่ยวข้องกับศูนย์โดยการคำนวณอย่างง่าย ๆ นี้:

det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)

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

รหัสสำหรับฟังก์ชั่นการเปรียบเทียบสามารถมีลักษณะเช่นนี้:

bool less(point a, point b)
{
    if (a.x - center.x >= 0 && b.x - center.x < 0)
        return true;
    if (a.x - center.x < 0 && b.x - center.x >= 0)
        return false;
    if (a.x - center.x == 0 && b.x - center.x == 0) {
        if (a.y - center.y >= 0 || b.y - center.y >= 0)
            return a.y > b.y;
        return b.y > a.y;
    }

    // compute the cross product of vectors (center -> a) x (center -> b)
    int det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
    if (det < 0)
        return true;
    if (det > 0)
        return false;

    // points a and b are on the same line from the center
    // check which point is closer to the center
    int d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
    int d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
    return d1 > d2;
}

การทำเช่นนี้จะสั่งให้คะแนนตามเข็มนาฬิกาเริ่มต้นจาก 12 นาฬิกา คะแนนใน "ชั่วโมง" ที่เหมือนกันจะถูกสั่งซื้อโดยเริ่มจากจุดที่อยู่ไกลจากจุดศูนย์กลาง

หากใช้ประเภทจำนวนเต็ม (ซึ่งไม่ได้มีอยู่จริงใน Lua) คุณต้องรับรองว่า det, ตัวแปร d1 และ d2 เป็นประเภทที่จะสามารถเก็บผลการคำนวณได้

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

แก้ไข:

เพิ่มอีกหนึ่งคำสั่งif (a.y - center.y >= 0 || b.y - center.y >=0)เพื่อให้แน่ใจว่าคะแนนที่มี x = 0 และลบ y ถูกเรียงลำดับเริ่มต้นจากจุดที่อยู่ไกลจากกึ่งกลาง หากคุณไม่สนใจเกี่ยวกับคำสั่งของจุดบน 'ชั่วโมง' a.y > b.yเดียวกับที่คุณสามารถละเว้นนี้ถ้ามีคำสั่งและมักจะกลับมา

การแก้ไขครั้งแรกถ้างบที่มีการเพิ่มและ-center.x-center.y

(a.x - center.x < 0 && b.x - center.x >= 0)ที่เพิ่มเข้ามาที่สองถ้ามีคำสั่ง มันเป็นการกำกับดูแลที่ชัดเจนว่ามันหายไป คำสั่ง if สามารถจัดโครงสร้างใหม่ได้ในขณะนี้เนื่องจากการตรวจสอบบางรายการซ้ำซ้อน ตัวอย่างเช่นหากเงื่อนไขแรกในคำสั่ง if แรกเป็นเท็จดังนั้นเงื่อนไขแรกของเงื่อนไขที่สองหากต้องเป็นจริง อย่างไรก็ตามฉันตัดสินใจทิ้งโค้ดไว้เพื่อความเรียบง่าย อาจเป็นไปได้ว่าคอมไพเลอร์จะปรับโค้ดให้เหมาะสมและให้ผลลัพธ์เดียวกัน


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

แต่มันต้องมีการเปรียบเทียบคะแนนทั้งหมดกับคนอื่น ๆ ทั้งหมด มีวิธีง่าย ๆ ในการแทรกคะแนนใหม่หรือไม่?
Iterator

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

2
กรณีนี้หายไปหรือไม่: if (axe - center.x <0 && bx - center.x> = 0) return false;
Tom Martin

2
สวัสดี. มันค่อนข้างเก่า แต่: "สิ่งนี้จะสั่งให้คะแนนตามเข็มนาฬิกาเริ่มจาก 12 นาฬิกา" ทำไม 12 โมงและฉันจะเปลี่ยนเป็น 6 ได้อย่างไร มีใครบอกฉันได้ไหม
Ismoh

20

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

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


Yikes "น่าสนใจ" เป็นการพูดน้อย :)
Iterator

@Iterator: ฉันมีความสุขมากกับความคิดของฉันฉันค่อนข้างผิดหวังที่ได้รับการโหวต: - / คุณคิดว่ามันถูกต้องหรือไม่?
static_rtti

1
ฉันแนะนำให้ใช้หนึ่งในการประมาณที่รวดเร็วหลายอย่างไม่ใช่อัลกอริธึมดั้งเดิมที่สมบูรณ์แบบของ NP
static_rtti

6
ฉันขอขอบคุณมุมเพิ่มเติม! หากต้องการคำตอบที่ถูกต้องหลายอย่างหากมีคำตอบแตกต่างกันอาจเป็นประโยชน์อย่างมากหากมีใครบางคนในอนาคตที่จะสะดุดกับหัวข้อนี้ที่กำลังมองหาทางเลือกระดมสมอง
Philipp Lenssen

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

19

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

หลังจากแปลงเป็นพิกัดเชิงขั้วเพียงเรียงตามมุมที


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

1
ฉันไม่แน่ใจว่าเกณฑ์ของคุณคือ "ดีกว่า" ตัวอย่างเช่นการเปรียบเทียบคะแนนทั้งหมดกับแต่ละอื่น ๆ เป็นประเภทของการคำนวณเสีย Trig ไม่ใช่สิ่งที่ทำให้ผู้ใหญ่กลัวใช่มั้ย
Iterator

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

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

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

3

รุ่นอื่น (คืนค่าจริงถ้า a มาก่อน b ในทิศทางทวนเข็มนาฬิกา):

    bool lessCcw(const Vector2D &center, const Vector2D &a, const Vector2D &b) const
    {
        // Computes the quadrant for a and b (0-3):
        //     ^
        //   1 | 0
        //  ---+-->
        //   2 | 3

        const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;
        const int day = ((a.y() - center.y()) > 0) ? 1 : 0;
        const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);

        /* The previous computes the following:

           const int qa =
           (  (a.x() > center.x())
            ? ((a.y() > center.y())
                ? 0 : 3)
            : ((a.y() > center.y())
                ? 1 : 2)); */

        const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;
        const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;
        const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);

        if (qa == qb) {
            return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());
        } else {
            return qa < qb;
       } 
    }

นี่คือเร็วกว่าเนื่องจากคอมไพเลอร์ (ทดสอบบน Visual C ++ 2015) ไม่ได้สร้างข้ามไปคำนวณ dax, วัน, dbx, dby นี่คือชุดประกอบเอาต์พุตจากคอมไพเลอร์:

; 28   :    const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;

    vmovss  xmm2, DWORD PTR [ecx]
    vmovss  xmm0, DWORD PTR [edx]

; 29   :    const int day = ((a.y() - center.y()) > 0) ? 1 : 0;

    vmovss  xmm1, DWORD PTR [ecx+4]
    vsubss  xmm4, xmm0, xmm2
    vmovss  xmm0, DWORD PTR [edx+4]
    push    ebx
    xor ebx, ebx
    vxorps  xmm3, xmm3, xmm3
    vcomiss xmm4, xmm3
    vsubss  xmm5, xmm0, xmm1
    seta    bl
    xor ecx, ecx
    vcomiss xmm5, xmm3
    push    esi
    seta    cl

; 30   :    const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);

    mov esi, 2
    push    edi
    mov edi, esi

; 31   : 
; 32   :    /* The previous computes the following:
; 33   : 
; 34   :    const int qa =
; 35   :        (   (a.x() > center.x())
; 36   :         ? ((a.y() > center.y()) ? 0 : 3)
; 37   :         : ((a.y() > center.y()) ? 1 : 2));
; 38   :    */
; 39   : 
; 40   :    const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;

    xor edx, edx
    lea eax, DWORD PTR [ecx+ecx]
    sub edi, eax
    lea eax, DWORD PTR [ebx+ebx]
    and edi, eax
    mov eax, DWORD PTR _b$[esp+8]
    sub edi, ecx
    sub edi, ebx
    add edi, esi
    vmovss  xmm0, DWORD PTR [eax]
    vsubss  xmm2, xmm0, xmm2

; 41   :    const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;

    vmovss  xmm0, DWORD PTR [eax+4]
    vcomiss xmm2, xmm3
    vsubss  xmm0, xmm0, xmm1
    seta    dl
    xor ecx, ecx
    vcomiss xmm0, xmm3
    seta    cl

; 42   :    const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);

    lea eax, DWORD PTR [ecx+ecx]
    sub esi, eax
    lea eax, DWORD PTR [edx+edx]
    and esi, eax
    sub esi, ecx
    sub esi, edx
    add esi, 2

; 43   : 
; 44   :    if (qa == qb) {

    cmp edi, esi
    jne SHORT $LN37@lessCcw

; 45   :        return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());

    vmulss  xmm1, xmm2, xmm5
    vmulss  xmm0, xmm0, xmm4
    xor eax, eax
    pop edi
    vcomiss xmm0, xmm1
    pop esi
    seta    al
    pop ebx

; 46   :    } else {
; 47   :        return qa < qb;
; 48   :    }
; 49   : }

    ret 0
$LN37@lessCcw:
    pop edi
    pop esi
    setl    al
    pop ebx
    ret 0
?lessCcw@@YA_NABVVector2D@@00@Z ENDP            ; lessCcw

สนุก.


1
ทั้งสองคำสั่งการส่งคืนในสวิตช์นั้นเทียบเท่ากันทางคณิตศาสตร์ มีเหตุผลในการเปลี่ยนหรือไม่?
unagi

0
  • vector3 a = new vector3 (1, 0, 0) .............. wrt X_axis
  • vector3 b = any_point - ศูนย์;
- y = |a * b|   ,   x =  a . b

- Atan2(y , x)...............................gives angle between -PI  to  + PI  in radians
- (Input % 360  +  360) % 360................to convert it from  0 to 2PI in radians
- sort by adding_points to list_of_polygon_verts by angle  we got 0  to 360

ในที่สุดคุณก็จะได้ Anticlockwize เรียง verts

list.Reverse () .................. Clockwise_order

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