จัดเรียงรายการอาร์เรย์ใน Java


85

ฉันงุนงงที่ไม่สามารถหาคำตอบได้อย่างรวดเร็วสำหรับเรื่องนี้ ฉันกำลังมองหาโครงสร้างข้อมูลใน Java ซึ่งใช้java.util.Listอินเทอร์เฟซเป็นหลัก แต่จะจัดเก็บสมาชิกในลำดับที่เรียงลำดับ ฉันรู้ว่าคุณสามารถใช้แบบปกติArrayListและCollections.sort()ใช้ได้ แต่ฉันมีสถานการณ์ที่ฉันเพิ่มและดึงข้อมูลสมาชิกจากรายการของฉันเป็นครั้งคราวและฉันไม่ต้องการเรียงลำดับทุกครั้งที่ดึงข้อมูลสมาชิกในกรณีที่ เพิ่มอันใหม่แล้ว ใครช่วยชี้ให้ฉันเห็นสิ่งที่มีอยู่ใน JDK หรือแม้แต่ห้องสมุดบุคคลที่สาม

แก้ไข : โครงสร้างข้อมูลจะต้องเก็บข้อมูลที่ซ้ำกัน

สรุปคำตอบ : ฉันพบว่าทั้งหมดนี้น่าสนใจมากและได้เรียนรู้มากมาย โดยเฉพาะอย่างยิ่ง Aioobe สมควรได้รับการกล่าวถึงสำหรับความอุตสาหะของเขาในการพยายามบรรลุข้อกำหนดของฉันข้างต้น (ส่วนใหญ่เป็นการใช้งาน java.util.List ที่เรียงลำดับซึ่งรองรับรายการที่ซ้ำกัน) ฉันยอมรับว่าคำตอบของเขาถูกต้องที่สุดสำหรับสิ่งที่ฉันถามและความคิดส่วนใหญ่ที่กระตุ้นให้เกิดผลกระทบของสิ่งที่ฉันกำลังมองหาแม้ว่าสิ่งที่ฉันถามจะไม่ใช่สิ่งที่ฉันต้องการก็ตาม

ปัญหาเกี่ยวกับสิ่งที่ฉันขออยู่ในอินเทอร์เฟซรายการและแนวคิดของวิธีการเสริมในอินเทอร์เฟซ หากต้องการอ้างอิง javadoc:

ผู้ใช้อินเทอร์เฟซนี้สามารถควบคุมได้อย่างแม่นยำว่าจะแทรกองค์ประกอบใดในรายการ

การแทรกลงในรายการที่เรียงลำดับไม่สามารถควบคุมจุดแทรกได้อย่างแม่นยำ จากนั้นคุณต้องคิดว่าคุณจะจัดการกับวิธีการบางอย่างอย่างไร ใช้addตัวอย่างเช่น:

เพิ่มบูลีนสาธารณะ (Object o)

 Appends the specified element to the end of this list (optional operation).

ตอนนี้คุณถูกทิ้งให้อยู่ในสถานการณ์ที่ไม่สบายใจของ 1) การทำลายสัญญาและใช้การเพิ่มเวอร์ชันที่เรียงลำดับ 2) การปล่อยให้addเพิ่มองค์ประกอบในตอนท้ายของรายการทำลายลำดับที่จัดเรียงของคุณ 3) ออกaddจาก (เป็นทางเลือก) โดยการขว้างปาUnsupportedOperationExceptionและการใช้วิธีการที่จะเพิ่มรายการในลำดับที่เรียงลำดับอื่น

ตัวเลือก 3 น่าจะดีที่สุด แต่ฉันพบว่ามันไม่น่าพอใจที่มีวิธีการเพิ่มที่คุณใช้ไม่ได้และวิธีการเรียงลำดับอื่นที่ไม่ได้อยู่ในอินเทอร์เฟซ

โซลูชันอื่น ๆ ที่เกี่ยวข้อง (ไม่เรียงตามลำดับ):

  • java.util.PriorityQueueซึ่งอาจใกล้เคียงกับสิ่งที่ฉันต้องการมากกว่าที่ฉันขอ คิวไม่ใช่คำจำกัดความที่แม่นยำที่สุดของคอลเลกชันของออบเจ็กต์ในกรณีของฉัน แต่มันทำงานได้ทุกอย่างที่ฉันต้องการ
  • net.sourceforge.nite.util.SortedList อย่างไรก็ตามการดำเนินงานนี้แบ่งสัญญาของอินเตอร์เฟซรายการโดยการใช้การเรียงลำดับในส่วนวิธีการและพิกลมีวิธีไม่มีผลกระทบadd(Object obj) add(int index, Object obj)ฉันทามติทั่วไปที่แนะนำthrow new UnsupportedOperationException()อาจเป็นทางเลือกที่ดีกว่าในสถานการณ์นี้
  • TreeMultiSet ของฝรั่งการใช้งานชุดที่รองรับการทำซ้ำ
  • ca.odell.glazedlists.SortedList คลาสนี้มาพร้อมกับข้อแม้ใน javadoc:Warning: This class breaks the contract required by List

4
หากคุณแทรกเป็นครั้งคราวและอ่านบ่อยๆทำไมไม่จัดเรียงระหว่างการแทรก?
serg

คำตอบ:


62

โซลูชันที่เรียบง่าย

นี่คือวิธีแก้ปัญหา "ขั้นต่ำ"

class SortedArrayList<T> extends ArrayList<T> {

    @SuppressWarnings("unchecked")
    public void insertSorted(T value) {
        add(value);
        Comparable<T> cmp = (Comparable<T>) value;
        for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--)
            Collections.swap(this, i, i-1);
    }
}

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

การแทรกสิ่งที่ไม่สามารถเปรียบเทียบได้ใน ClassCastException (นี่เป็นแนวทางที่ใช้PriorityQueueเช่นกัน: ลำดับความสำคัญที่อาศัยการจัดลำดับตามธรรมชาติยังไม่อนุญาตให้มีการแทรกวัตถุที่ไม่สามารถเทียบเคียงได้ (การทำเช่นนั้นอาจส่งผลให้ ClassCastException )

การลบล้าง List.add

โปรดทราบว่าการลบล้างList.add(หรือList.addAllสำหรับเรื่องนั้น) เพื่อแทรกองค์ประกอบในรูปแบบที่เรียงลำดับจะเป็นการละเมิดข้อกำหนดของอินเทอร์เฟซโดยตรง สิ่งที่คุณจะUnsupportedOperationExceptionทำคือการแทนที่วิธีการนี้จะโยน

จากเอกสารของList.add:

boolean add(E e)
    ต่อท้ายองค์ประกอบที่ระบุไว้ที่ส่วนท้ายของรายการนี้ (การดำเนินการที่เป็นทางเลือก)

เหตุผลเช่นเดียวกับทั้งสองรุ่นaddทั้งรุ่นและaddAll set(ซึ่งทั้งหมดนี้เป็นการดำเนินการทางเลือกตามอินเทอร์เฟซรายการ)


การทดสอบบางอย่าง

SortedArrayList<String> test = new SortedArrayList<String>();

test.insertSorted("ddd");    System.out.println(test);
test.insertSorted("aaa");    System.out.println(test);
test.insertSorted("ccc");    System.out.println(test);
test.insertSorted("bbb");    System.out.println(test);
test.insertSorted("eee");    System.out.println(test);

.... ภาพพิมพ์:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]

การเริ่มต้นที่ดี แต่การโทรเพิ่มหรือแอดออลจะเพิ่มสมาชิกแบบไม่เรียงลำดับ
Chris Knight

ใช่. สิ่งใดก็ตามนอกจากการต่อท้ายรายการจะเป็นการละเมิดอินเทอร์เฟซรายการโดยตรง ดูคำตอบที่อัปเดตของฉัน
aioobe

@aioobe จุดดี. แต่การดำเนินการที่ไม่สนับสนุนของวิธีการเชื่อมต่อไม่ใช่กลิ่นรหัสหรือไม่? วิธีที่เหมาะสมอาจไม่ขยาย ArrayList แต่ใช้ List แต่ถึงอย่างนั้นบางที List ก็ไม่ได้มีไว้เพื่อจุดประสงค์นี้ จาก Javadoc for List: The user of this interface has precise control over where in the list each element is insertedซึ่งไม่ใช่คำอธิบายที่ดีที่สุดสำหรับการแทรกองค์ประกอบในรูปแบบที่เรียงลำดับและคุณยังต้องจัดการกับadd(int index, Object obj)วิธีการอินเทอร์เฟซ ปัญหาเหล่านี้อาจอธิบายได้ว่าทำไมรายการถึงไม่ถูกนำมาใช้ในรูปแบบที่เรียง
Chris Knight

การดำเนินการเป็นทางเลือกด้วยเหตุผล ฉันจะไม่แปลกใจถ้าฉันได้รับ UnsupportedExceptionOperation เมื่อทำ.addบน SortedArrayList ใช่เหตุผลเดียวกันนี้ใช้กับ add ทั้งสองเวอร์ชันทั้ง addAll และ set (ทั้งหมดนี้เป็นการดำเนินการทางเลือกตามอินเทอร์เฟซรายการ)
aioobe

อ่าฉันไม่รู้ว่ามันเป็นการดำเนินการทางเลือก พล็อตหนาขึ้น ... ;)
Chris Knight

10

7
ที่ไม่ใช่รายการกล่าวคือไม่มีการเข้าถึงแบบสุ่ม
Thilo

1
เป็นฮีปลำดับความสำคัญตามคิวและไม่ใช้รายการ
zengr

3
แน่นอนว่าด้วยรายการที่รักษาการเรียงลำดับดัชนีจะเปลี่ยนแปลงตลอดเวลาดังนั้นจึงไม่จำเป็นต้องเข้าถึงแบบสุ่ม
Thilo

5
@Qwerky โปรดทราบว่าคำตอบที่แน่นอนไม่ใช่คำตอบที่ดีที่สุดเสมอไปหรือคำตอบที่ OP อยู่หลัง
aioobe

3
ลำดับความสำคัญไม่ได้ให้สิทธิ์เรียงลำดับในการทำซ้ำ
marcorossi

6

ดูที่SortedList

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


เมื่อรายการมีวัตถุที่มีค่าเท่ากันตามตัวเปรียบเทียบอยู่แล้ววัตถุใหม่จะถูกแทรกทันทีหลังจากวัตถุอื่น ๆ เหล่านี้


5
ดูดี แต่ก็ดูมีปัญหาเช่นกัน: ไม่มีการแทนที่ addAll ทั้งสองเวอร์ชันดังนั้นรายการจะไม่ถูกเรียงลำดับหลังจากเรียกสิ่งเหล่านั้น
Tom Anderson

3
และวิธีการเพิ่ม "ไม่มีผล" ควรโยน UnsupportedOperationException หากไม่สามารถใช้งานได้
Thilo

@ ทอมแอนเดอร์สัน @Thilo เห็นด้วยกับทั้งสองคน
jmj

1
น่าสนใจ แต่ฉันค่อนข้างระวังว่าจะมีใครบางคนในอนาคตใช้addAll()และคิดว่ามันจะเป็นองค์ประกอบทั้งหมดในรูปแบบที่เรียงกัน เห็นด้วยกับ UnsupportedOperationException ด้วย
Chris Knight

1
ความซับซ้อนของเวลานอกเหนือจากรายการนี้คืออะไร?
shrini1000

6

คุณสามารถลองฝรั่งของ TreeMultiSet

 Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
 System.out.println(ms);

+1. นี่คือห้องสมุดที่ยอดเยี่ยม MultiSet คือA collection that supports order-independent equality, like Set, but may have duplicate elements
Shervin Asgari

5

แนวทางของ Aioobe คือหนทางที่จะไป ฉันอยากจะแนะนำให้ปรับปรุงวิธีแก้ปัญหาของเขาต่อไปนี้

class SortedList<T> extends ArrayList<T> {

    public void insertSorted(T value) {
        int insertPoint = insertPoint(value);
        add(insertPoint, value);
    }

    /**
     * @return The insert point for a new value. If the value is found the insert point can be any
     * of the possible positions that keeps the collection sorted (.33 or 3.3 or 33.).
     */
    private int insertPoint(T key) {
        int low = 0;
        int high = size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = (Comparable<T>) get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else {
                return mid; // key found
            }
        }

        return low;  // key not found
    }
}

โซลูชันของ aioobe จะช้ามากเมื่อใช้รายการขนาดใหญ่ การใช้ความจริงที่ว่ารายการถูกจัดเรียงทำให้เราสามารถค้นหาจุดแทรกสำหรับค่าใหม่โดยใช้การค้นหาแบบไบนารี

ฉันจะใช้องค์ประกอบมากกว่าการถ่ายทอดทางพันธุกรรมบางอย่างตามแนวของ

SortedList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

4

โดยทั่วไปรายการจะรักษาลำดับการเพิ่มรายการ คุณต้องการรายชื่อแน่นอนหรือจะจัดเรียงชุดที่ (เช่นTreeSet<E>) จะโอเคสำหรับคุณ? โดยทั่วไปคุณจำเป็นต้องรักษารายการที่ซ้ำกันหรือไม่?


2
ขอบคุณจอน แต่ฉันต้องรักษารายการที่ซ้ำ
Chris Knight


1

คุณสามารถ subclass ArrayList และเรียก Collections.sort (this) หลังจากเพิ่มองค์ประกอบใด ๆ - คุณจะต้องแทนที่ add สองเวอร์ชันและ addAll สองเวอร์ชันจึงจะทำได้

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


1

เพียงสร้างคลาสใหม่ดังนี้:

public class SortedList<T> extends ArrayList<T> {

private final Comparator<? super T> comparator;

public SortedList() {
    super();
    this.comparator = null;
}

public SortedList(Comparator<T> comparator) {
    super();
    this.comparator = comparator;
}

@Override
public boolean add(T item) {
    int index = comparator == null ? Collections.binarySearch((List<? extends Comparable<? super T>>)this, item) :
            Collections.binarySearch(this, item, comparator);
    if (index < 0) {
        index = index * -1 - 2;
    }
    super.add(index+1, item);
    return true;
}

@Override
public void add(int index, T item) {
    throw new UnsupportedOperationException("'add' with an index is not supported in SortedArrayList");
}

@Override
public boolean addAll(Collection<? extends T> items) {
    boolean allAdded = true;
    for (T item : items) {
        allAdded = allAdded && add(item);
    }
    return allAdded;
}

@Override
public boolean addAll(int index, Collection<? extends T> items) {
    throw new UnsupportedOperationException("'addAll' with an index is not supported in SortedArrayList");
}

}

คุณสามารถทดสอบได้ดังนี้:

    List<Integer> list = new SortedArrayList<>((Integer i1, Integer i2) -> i1.compareTo(i2));
    for (Integer i : Arrays.asList(4, 7, 3, 8, 9, 25, 20, 23, 52, 3)) {
        list.add(i);
    }
    System.out.println(list);

0

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

หากคุณไม่สามารถเลือกใช้คอลเลกชันใน JDK ได้คุณสามารถดูApache Commons Collections


0

เนื่องจากการใช้งานที่เสนอในปัจจุบันซึ่งใช้รายการที่เรียงลำดับโดยการทำลาย Collection API มีการใช้งานโครงสร้างของตัวเองหรือสิ่งที่คล้ายกันฉันจึงอยากรู้ว่าการใช้งานตาม TreeMap จะดำเนินการอย่างไร (เฉพาะเนื่องจาก TreeSet ทำฐานบน TreeMap ด้วย)

หากมีใครสนใจสิ่งนั้นเช่นกันเขาหรือเธอสามารถตรวจสอบได้:

TreeList

เป็นส่วนหนึ่งของไลบรารีหลักคุณสามารถเพิ่มได้ผ่านการพึ่งพา Maven แน่นอน (ใบอนุญาต Apache)

ขณะนี้การใช้งานดูเหมือนจะเปรียบเทียบได้ค่อนข้างดีในระดับเดียวกันกับฝรั่ง SortedMultiSet และ TreeList ของไลบรารี Apache Commons

แต่ฉันจะมีความสุขถ้ามีมากกว่าฉันเท่านั้นที่จะทดสอบการใช้งานเพื่อให้แน่ใจว่าฉันไม่พลาดสิ่งที่สำคัญ

ขอแสดงความนับถืออย่างสูง!


0

ผมมีปัญหาเดียวกัน. ดังนั้นผมจึงเอารหัสที่มาของ java.util.TreeMap และเขียนIndexedTreeMap มันใช้IndexedNavigableMapของฉันเอง:

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

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

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeight เพียงแค่อัปเดตน้ำหนักจนถึงรูท:

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

และเมื่อเราต้องการค้นหาองค์ประกอบตามดัชนีนี่คือการใช้งานที่ใช้น้ำหนัก:

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

นอกจากนี้ยังมีประโยชน์มากในการค้นหาดัชนีของคีย์:

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    index += getWeight(e.left);

    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

คุณสามารถค้นหาผลลัพธ์ของงานนี้ได้ที่http://code.google.com/p/indexed-tree-map/

TreeSet / TreeMap (เช่นเดียวกับคู่ที่จัดทำดัชนีจากโครงการแผนผังต้นไม้ที่จัดทำดัชนี) ไม่อนุญาตให้มีคีย์ที่ซ้ำกันคุณสามารถใช้ 1 คีย์สำหรับอาร์เรย์ของค่า หากคุณต้องการ SortedSet ที่มีรายการซ้ำให้ใช้ TreeMap ที่มีค่าเป็นอาร์เรย์ ฉันจะทำอย่างนั้น

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