8.3. Basic Data Structure

การทำงาน find ไม่ต้องการคำตอบที่เป็นการระบุถึงชื่อใด ๆ หากแต่เพียงต้องการคำตอบว่าการ find สมาชิกทั้งสองตัวนั้นจะให้คำตอบที่เหมือนกันถ้า (if and only if) มันอยู่ใน set เดียวกัน แนวคิดหนึ่งคือ การใช้โครงสร้างทรี (tree) สำหรับแต่ละเซต เนื่องจากสมาชิกแต่ละตัวนั้นมีรากเดียวกัน ดังนั้นเราจึงใช้ชื่อของรากเป็นชื่อของเซตดังกล่าวได้ เราจะแทนเซตหนึ่งเซตด้วยทรีหนึ่งทรี ตอนเริ่มต้นแต่ละเซตประกอบด้วยสมาชิกหนึ่งตัว (กลุ่มของทรีเรียกว่า forest) ทรีที่เราใช้นี้ไม่จำเป็นต้องเป็น binary trees และสิ่งที่ต้องการคือการเชื่อมต่อกับโนดแม่ (parent link) เท่านั้น ชื่อของเซตหนึ่งก็คือโนดที่รากของทรี และเนื่องจากเราต้องการเพียงชื่อของ parent เราจึงใช้อะเรย์เป็นที่เก็บทรี กล่าวคือ สำหรับแต่ละ s[i] ในอะเรย์คือ parent ของสมาชิกตัวที่ i ถ้า i เป็นรากก็ใน s[i] = -1 รูปที่ 8.1 แสดง forest ที่ s[i] = -1 เมื่อ 0 ≤ i < 8 ดังเช่นที่แสดงใน binary heap เราจะแสดงโครงสร้างในรูปของทรีทั้ง ๆ ที่ความจริงเราใช้อะเรย์ และเพื่อความสะดวกเราจะใช้เส้นในแนวตั้งแทนเส้นเชื่อมจากราก (parent link) ไปยังโนดแม่ของมัน

เราสามารถทำ union ของเซตสองเซตได้ด้วยการควบรวมทรี 2 ตัวเข้าด้วยการด้วยการใช้ parent link ของรากของทรีหนึ่ง เชื่อมเข้ากับโนดรากของอีกทรีหนึ่งซึ่งใช้เวลาเป็นเวลาคงที่ รูปที่ 8.2, 8.3, และ 8.4 เป็น forest หลังการทำ union(4,5), union(6,7), union(4,6) โดยที่หลังทำ union(x,y) แล้วมีรากเป็น x รูปที่ 8.5 แสดงผลของ forest ที่ได้


รูปที่ 8.1 เริ่มต้นสมาชิกทั้ง 8 อยู่ในเซตคนละเซตกัน


รูปที่ 8.2 หลังการทำงาน union(4, 5)


รูปที่ 8.3 หลังการทำงาน union(6, 7)


รูปที่ 8.4 หลังการทำงาน union(4, 6)

-1 -1 -1 -1 -1 4 4 6
0 1 2 3 4 5 6 7

รูปที่ 8.5 อะเรย์ของสมาชิกในทรีข้างบน

การทำงาน find(x) เพื่อหาสมาชิก x จะได้ผลเป็นรากของทรีที่มี x เวลาที่ใช้ขึ้นอยู่กับความลึกของโนดที่เป็น x โดยที่ถ้าเราสามารถหาโนด x ได้ด้วยเวลาคงที่ ด้วยวิธีการดังกล่าวนี้ เราอาจต้องสร้างทรีที่มีความลึก N – 1 ดังนั้น worst-case running time ของ find จึงเป็น O(N) กรณีทั่วไป running time ของการทำงาน M ครั้งต่อเนื่องกันก็จะมี worst-case running time เป็น O(MN) โปรแกรมในรูปที่ 8.6 ถึงรูปที่ 8.9 แสดงอัลกอริทึมพื้นฐานโดยสมมติให้มีการทำงาน error check มาก่อนแล้ว ในโปรแกรมการทำงาน union เกิดขึ้นที่รากของทรี ในบางครั้งการทำงานเกิดขึ้นด้วยการส่งผ่านพารามิเตอร์เป็นสมาชิก 2 ตัว และการทำงาน union มีการทำงาน find 2 ครั้งเพื่อหาตัวโนดราก

/* Disjoint set class. 
 * Does not use union heuristics or path compression.
 * Elements in the set are numbered starting at 0. */
public class DisjSets 
{   public DisjSets( int numElements ) { /* Figure 8.7 */ }
    public void union( int root1, int root2 )    
	{/* Figure 8.8 and Figure 8.13 */ }
    public int find( int x ) {/* Figure 8.9 and Figure 8.15*/ }
    private int [ ] s;
}

รูปที่ 8.6 โครงร่างของคลาส Disjoint set

public DisjSets( int numElements )
{
    s = new int [ numElements ];
    for( int i = 0; i < s.length; i++ )
        s[ i ] = -1;
}

รูปที่ 8.7 Initialization routine ของ Disjoint set

/* Union two disjoint sets.
 * For simplicity, we assume root1 and root2 are distinct
 * and represent set names.*/
public void union( int root1, int root2 )
{
    s[ root2 ] = root1;
}

รูปที่ 8.8 union

/* Perform a find. Error checks omitted for simplicity. */
public int find( int x )
{
    if( s[ x ] < 0 )
        return x;
    else
        return find( s[ x ] );
}

รูปที่ 8.9 อัลกอริทึมอย่างง่ายของ find

การวิเคราะห์กรณีเฉลี่ยขึ้นอยู่กับว่าจะกำหนดนิยามของคำว่าเฉลี่ยอย่างไรเมื่อพิจารณาถึง union ดังนั้นจึงมีความยุ่งยากมากและมีผู้เสนอโมเดลการวิเคราะห์หลายโมเดลมาก ถึงกระนั้นก็ยังไม่เป็นข้อยุติเป็นการทั่วไปได้ มีผู้เสนอ running time สำหรับกรณีเฉลี่ยเป็น $\Theta(M)$, $\Theta(M\ log\ N)$, และ $\Theta(MN)$ ขึ้นอยู่กับโมเดลที่ใช้ที่แตกต่างกัน