Tôi tìm cách triển khai bản đồ dựa trên Trie tiêu chuẩn trong Java ở đâu? [đóng cửa]


76

Tôi có một chương trình Java lưu trữ rất nhiều ánh xạ từ Chuỗi đến các đối tượng khác nhau.

Hiện tại, các tùy chọn của tôi là dựa vào băm (thông qua HashMap) hoặc tìm kiếm nhị phân (qua TreeMap). Tôi đang tự hỏi liệu có cách triển khai bản đồ dựa trên bộ ba tiêu chuẩn và hiệu quả trong một thư viện bộ sưu tập chất lượng và phổ biến không?

Tôi đã từng viết bài của riêng mình trong quá khứ, nhưng tôi muốn đi với một cái gì đó tiêu chuẩn, nếu có.

Làm rõ nhanh: Mặc dù câu hỏi của tôi là chung chung, trong dự án hiện tại, tôi đang xử lý rất nhiều dữ liệu được lập chỉ mục bằng tên lớp hoặc chữ ký phương thức đủ điều kiện. Do đó, có nhiều tiền tố được chia sẻ.


các dây có được biết trước không? Chúng có cần được truy cập chỉ bằng chuỗi không?
sfossen

Câu trả lời:


32

Bạn có thể muốn xem việc triển khai Trie mà Limewire đang đóng góp cho Google Guava.


8
Có vẻ như Google-Collections đã được hỗ trợ bởi Guava code.google.com/p/guava-libraries và rất tiếc là tôi không thể thấy lớp Trie trong đó ở bất kỳ đâu. Các Patricia Trie dường như có trang dự án riêng của mình bây giờ: code.google.com/p/patricia-trie
Dan J

1
Các liên kết Limewire / Google hiện cũng hơi lộn xộn. Mặc dù tôi đã cố gắng tìm code.google.com/archive/p/google-collections/issues/5 với các tệp thực tế, lưu ý rằng Apache Commons Collections đi kèm với một số lần thử (bao gồm cả một bộ sưu tập phụ ). Đó là một trong những tôi muốn giới thiệu ngay bây giờ.
Jason C

Ngoài ra, việc triển khai Apache Commons dường như đến từ cùng một nơi với phần đóng góp của Limewire, vì các nhận xét tóm tắt trong tài liệu của Commons dành cho PatriciaTrie giống với các nhận xét tóm tắt trong phần triển khai do Limewire đóng góp.
Jason C

10

Không có cấu trúc dữ liệu trie trong các thư viện Java lõi.

Điều này có thể là do các lần thử thường được thiết kế để lưu trữ các chuỗi ký tự, trong khi cấu trúc dữ liệu Java tổng quát hơn, thường chứa bất kỳ Object(xác định đẳng thức và hoạt động băm), mặc dù đôi khi chúng bị giới hạn ở Comparablecác đối tượng (xác định thứ tự). Không có sự trừu tượng chung nào cho "một chuỗi ký hiệu", mặc dù CharSequencenó phù hợp với chuỗi ký tự và tôi cho rằng bạn có thể làm gì đó với Iterablecác loại ký hiệu khác.

Đây là một điểm khác cần xem xét: khi cố gắng triển khai một trie thông thường trong Java, bạn sẽ nhanh chóng đối mặt với thực tế là Java hỗ trợ Unicode. Để có bất kỳ loại hiệu quả về không gian nào, bạn phải hạn chế các chuỗi trong bộ ba của mình ở một số tập con ký hiệu hoặc từ bỏ cách tiếp cận thông thường là lưu trữ các nút con trong một mảng được lập chỉ mục bằng ký hiệu. Đây có thể là một lý do khác khiến các lần thử không được coi là đủ mục đích chung để đưa vào thư viện lõi và điều gì đó cần chú ý nếu bạn triển khai thư viện của riêng mình hoặc sử dụng thư viện của bên thứ ba.


Câu trả lời này giả sử tôi muốn triển khai một trie cho chuỗi. Trie là một cấu trúc dữ liệu chung , có khả năng giữ các trình tự tùy ý và cung cấp tra cứu tiền tố nhanh chóng.
Paul Draper

1
@PaulDraper Câu trả lời này không giả định bất cứ điều gì về những gì bạn muốn, vì bạn đã xuất hiện nhiều năm sau khi câu hỏi được hỏi. Và vì câu hỏi đặc biệt là về chuỗi ký tự, đó là trọng tâm của câu trả lời này. Mặc dù tôi đã dành rất nhiều thời gian để chỉ ra rằng một bộ ba Java sẽ cần phải được tổng quát hóa cho bất kỳ loại nào Comparable.
erickson

7

Cũng kiểm tra cây đồng thời . Chúng hỗ trợ cả cây Radix và Suffix và được thiết kế cho các môi trường đồng thời cao.


3
Kể từ năm 2014, đây phải là câu trả lời được chấp nhận. Có vẻ như các thử nghiệm được duy trì tốt, được kiểm tra tốt, đồng thời.
knub

5

Apache Commons Collections v4.0 hiện hỗ trợ cấu trúc trie.

Xem org.apache.commons.collections4.triethông tin gói để biết thêm thông tin. Đặc biệt, kiểm tra PatriciaTrielớp:

Triển khai PATRICIA Trie (Thuật toán thực tế để truy xuất thông tin được mã hóa bằng chữ và số).

Một Trie PATRICIA là một Trie nén. Thay vì lưu trữ tất cả dữ liệu ở các cạnh của Trie (và có các nút bên trong trống), PATRICIA lưu trữ dữ liệu trong mọi nút. Điều này cho phép thực hiện các thao tác duyệt, chèn, xóa, tiền tố, phạm vi và chọn (Đối tượng) rất hiệu quả. Tất cả các hoạt động được thực hiện kém nhất trong thời gian O (K), trong đó K là số bit trong mục lớn nhất trong cây. Trong thực tế, các hoạt động thực sự mất thời gian O (A (K)), trong đó A (K) là số bit trung bình của tất cả các mục trong cây.


3

Tôi đã viết và xuất bản một cách thực hiện đơn giản và nhanh chóng ở đây .


Tôi muốn thích điều này, nhưng mỗi nút của bạn yêu cầu 1024 byte và chỉ đại diện cho một ký tự. Ngoài ra, việc chèn giờ cũng mất O (n ^ 2) thời gian do ngữ nghĩa của chuỗi con () đã thay đổi của Java. Việc triển khai này thực sự không thực tế lắm.
Stefan Reich

@Stefan Reich, Không gian mảng đó chỉ dành cho các nút bên trong rất nhỏ do cây Trie quạt ra nhanh như thế nào.
Melinda Green

Cảm ơn câu trả lời của bạn, nhưng tôi không bị thuyết phục. Các phép thử có thể không phải lúc nào cũng phân nhánh nhanh chóng, trên thực tế, chúng có thể sẽ không xảy ra với dữ liệu thực. Các mảng của bạn cũng chậm quét nội dung. Chúng ta thực sự nên sử dụng Patricia Tries để mọi thứ gọn gàng và hiệu quả. Tôi đã thực hiện triển khai của riêng mình mà tôi có thể sẽ đăng ở đây ngay sau đây. Không cảm giác khó khăn, chỉ cần cố gắng để tối ưu hóa :) Nhiều lời chúc mừng
Stefan Reich

Những cố gắng của tôi chỉ có thể làm tan biến nhanh chóng vì phần dư thừa được tính theo yếu tố và được lưu trữ trong thành viên "tiền tố". Có chỗ cho rất nhiều cách triển khai khác nhau dựa trên những gì bạn đang cố gắng tối ưu hóa. Trong trường hợp của tôi, tôi đang hướng tới sự đơn giản nhưng thực tế.
Melinda Green

1
Ah, tôi đã hiểu sai phần đó của mã. Có quá nhiều "Đối tượng" và quá trình đúc mà tôi không nhìn thấy nó. Vì vậy, nó là một Patricia Trie. Lỗi của tôi.
Stefan Reich


1

Những gì bạn cần là org.apache.commons.collections.FastTreeMap, tôi nghĩ.


Đây dường như không phải là một triển khai trie.
Duncan Jones

1

Dưới đây là cách triển khai HashMap cơ bản của một Trie. Một số người có thể thấy điều này hữu ích ...

class Trie {

    HashMap<Character, HashMap> root;

    public Trie() {
        root = new HashMap<Character, HashMap>();
    }

    public void addWord(String word) {
        HashMap<Character, HashMap> node = root;
        for (int i = 0; i < word.length(); i++) {
            Character currentLetter = word.charAt(i);
            if (node.containsKey(currentLetter) == false) {
                node.put(currentLetter, new HashMap<Character, HashMap>());
            }
            node = node.get(currentLetter);
        }
    }

    public boolean containsPrefix(String word) {
        HashMap<Character, HashMap> node = root;
        for (int i = 0; i < word.length(); i++) {
            Character currentLetter = word.charAt(i);
            if (node.containsKey(currentLetter)) {
                node = node.get(currentLetter);
            } else {
                return false;
            }
        }
        return true;
    }
}


0

Bạn cũng có thể xem TopCoder này (yêu cầu đăng ký ...).


Tôi đã đăng ký nhưng thành phần đó là nghi thức không thể kiểm soát được.
Deepak

0

Nếu bạn yêu cầu bản đồ được sắp xếp, thì việc thử là đáng giá. Nếu bạn không thì hashmap tốt hơn. Bản đồ băm với các khóa chuỗi có thể được cải thiện so với việc triển khai Java tiêu chuẩn: Bản đồ băm mảng



0

đây là cách triển khai của tôi, hãy tận hưởng nó qua: GitHub - MyTrie.java

/* usage:
    MyTrie trie = new MyTrie();
    trie.insert("abcde");
    trie.insert("abc");
    trie.insert("sadas");
    trie.insert("abc");
    trie.insert("wqwqd");
    System.out.println(trie.contains("abc"));
    System.out.println(trie.contains("abcd"));
    System.out.println(trie.contains("abcdefg"));
    System.out.println(trie.contains("ab"));
    System.out.println(trie.getWordCount("abc"));
    System.out.println(trie.getAllDistinctWords());
*/

import java.util.*;

public class MyTrie {
  private class Node {
    public int[] next = new int[26];
    public int wordCount;
    public Node() {
      for(int i=0;i<26;i++) {
        next[i] = NULL;
      }
      wordCount = 0;
    }
  }

  private int curr;
  private Node[] nodes;
  private List<String> allDistinctWords;
  public final static int NULL = -1;

  public MyTrie() {
    nodes = new Node[100000];
    nodes[0] = new Node();
    curr = 1;
  }

  private int getIndex(char c) {
    return (int)(c - 'a');
  }

  private void depthSearchWord(int x, String currWord) {
    for(int i=0;i<26;i++) {
      int p = nodes[x].next[i];
      if(p != NULL) {
        String word = currWord + (char)(i + 'a');
        if(nodes[p].wordCount > 0) {
          allDistinctWords.add(word);
        }
        depthSearchWord(p, word);
      }
    }
  }

  public List<String> getAllDistinctWords() {
    allDistinctWords = new ArrayList<String>();
    depthSearchWord(0, "");
    return allDistinctWords;
  }

  public int getWordCount(String str) {
    int len = str.length();
    int p = 0;
    for(int i=0;i<len;i++) {
      int j = getIndex(str.charAt(i));
      if(nodes[p].next[j] == NULL) {
        return 0;
      }
      p = nodes[p].next[j];
    }
    return nodes[p].wordCount;
  }

  public boolean contains(String str) {
    int len = str.length();
    int p = 0;
    for(int i=0;i<len;i++) {
      int j = getIndex(str.charAt(i));
      if(nodes[p].next[j] == NULL) {
        return false;
      }
      p = nodes[p].next[j];
    }
    return nodes[p].wordCount > 0;
  }

  public void insert(String str) {
    int len = str.length();
    int p = 0;
    for(int i=0;i<len;i++) {
      int j = getIndex(str.charAt(i));
      if(nodes[p].next[j] == NULL) {
        nodes[curr] = new Node();
        nodes[p].next[j] = curr;
        curr++;
      }
      p = nodes[p].next[j];
    }
    nodes[p].wordCount++;
  }
}

0

Tôi vừa thử triển khai Concurrent TRIE của riêng mình nhưng không dựa trên ký tự, nó dựa trên HashCode. Tuy nhiên, Chúng tôi có thể sử dụng điều này có Bản đồ Bản đồ cho mỗi hascode CHAR.
Bạn có thể kiểm tra điều này bằng cách sử dụng mã @ https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapPerformanceTest.java https://github.com/skanagavelu/TrieHashMap/blob/master/src/TrieMapValidationTest.java

import java.util.concurrent.atomic.AtomicReferenceArray;

public class TrieMap {
    public static int SIZEOFEDGE = 4; 
    public static int OSIZE = 5000;
}

abstract class Node {
    public Node getLink(String key, int hash, int level){
        throw new UnsupportedOperationException();
    }
    public Node createLink(int hash, int level, String key, String val) {
        throw new UnsupportedOperationException();
    }
    public Node removeLink(String key, int hash, int level){
        throw new UnsupportedOperationException();
    }
}

class Vertex extends Node {
    String key;
    volatile String val;
    volatile Vertex next;

    public Vertex(String key, String val) {
        this.key = key;
        this.val = val;
    }

    @Override
    public boolean equals(Object obj) {
        Vertex v = (Vertex) obj;
        return this.key.equals(v.key);
    }

    @Override
    public int hashCode() {
        return key.hashCode();
    }

    @Override
    public String toString() {
        return key +"@"+key.hashCode();
    }
}


class Edge extends Node {
    volatile AtomicReferenceArray<Node> array; //This is needed to ensure array elements are volatile

    public Edge(int size) {
        array = new AtomicReferenceArray<Node>(8);
    }


    @Override
    public Node getLink(String key, int hash, int level){
        int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
        Node returnVal = array.get(index);
        for(;;) {
            if(returnVal == null) {
                return null;
            }
            else if((returnVal instanceof Vertex)) {
                Vertex node = (Vertex) returnVal;
                for(;node != null; node = node.next) {
                    if(node.key.equals(key)) {  
                        return node; 
                    }
                } 
                return null;
            } else { //instanceof Edge
                level = level + 1;
                index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
                Edge e = (Edge) returnVal;
                returnVal = e.array.get(index);
            }
        }
    }

    @Override
    public Node createLink(int hash, int level, String key, String val) { //Remove size
        for(;;) { //Repeat the work on the current node, since some other thread modified this node
            int index =  Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
            Node nodeAtIndex = array.get(index);
            if ( nodeAtIndex == null) {  
                Vertex newV = new Vertex(key, val);
                boolean result = array.compareAndSet(index, null, newV);
                if(result == Boolean.TRUE) {
                    return newV;
                }
                //continue; since new node is inserted by other thread, hence repeat it.
            } 
            else if(nodeAtIndex instanceof Vertex) {
                Vertex vrtexAtIndex = (Vertex) nodeAtIndex;
                int newIndex = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, vrtexAtIndex.hashCode(), level+1);
                int newIndex1 = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level+1);
                Edge edge = new Edge(Base10ToBaseX.Base.BASE8.getLevelZeroMask()+1);
                if(newIndex != newIndex1) {
                    Vertex newV = new Vertex(key, val);
                    edge.array.set(newIndex, vrtexAtIndex);
                    edge.array.set(newIndex1, newV);
                    boolean result = array.compareAndSet(index, vrtexAtIndex, edge); //REPLACE vertex to edge
                    if(result == Boolean.TRUE) {
                        return newV;
                    }
                   //continue; since vrtexAtIndex may be removed or changed to Edge already.
                } else if(vrtexAtIndex.key.hashCode() == hash) {//vrtex.hash == hash) {       HERE newIndex == newIndex1
                    synchronized (vrtexAtIndex) {   
                        boolean result = array.compareAndSet(index, vrtexAtIndex, vrtexAtIndex); //Double check this vertex is not removed.
                        if(result == Boolean.TRUE) {
                            Vertex prevV = vrtexAtIndex;
                            for(;vrtexAtIndex != null; vrtexAtIndex = vrtexAtIndex.next) {
                                prevV = vrtexAtIndex; // prevV is used to handle when vrtexAtIndex reached NULL
                                if(vrtexAtIndex.key.equals(key)){
                                    vrtexAtIndex.val = val;
                                    return vrtexAtIndex;
                                }
                            } 
                            Vertex newV = new Vertex(key, val);
                            prevV.next = newV; // Within SYNCHRONIZATION since prevV.next may be added with some other.
                            return newV;
                        }
                        //Continue; vrtexAtIndex got changed
                    }
                } else {   //HERE newIndex == newIndex1  BUT vrtex.hash != hash
                    edge.array.set(newIndex, vrtexAtIndex);
                    boolean result = array.compareAndSet(index, vrtexAtIndex, edge); //REPLACE vertex to edge
                    if(result == Boolean.TRUE) {
                        return edge.createLink(hash, (level + 1), key, val);
                    }
                }
            }               
            else {  //instanceof Edge
                return nodeAtIndex.createLink(hash, (level + 1), key, val);
            }
        }
    }




    @Override
    public Node removeLink(String key, int hash, int level){
        for(;;) {
            int index = Base10ToBaseX.getBaseXValueOnAtLevel(Base10ToBaseX.Base.BASE8, hash, level);
            Node returnVal = array.get(index);
            if(returnVal == null) {
                return null;
            }
            else if((returnVal instanceof Vertex)) {
                synchronized (returnVal) {
                    Vertex node = (Vertex) returnVal;
                    if(node.next == null) {
                        if(node.key.equals(key)) {
                            boolean result = array.compareAndSet(index, node, null); 
                            if(result == Boolean.TRUE) {
                                return node;
                            }
                            continue; //Vertex may be changed to Edge
                        }
                        return null;  //Nothing found; This is not the same vertex we are looking for. Here hashcode is same but key is different. 
                    } else {
                        if(node.key.equals(key)) { //Removing the first node in the link
                            boolean result = array.compareAndSet(index, node, node.next);
                            if(result == Boolean.TRUE) {
                                return node;
                            }
                            continue; //Vertex(node) may be changed to Edge, so try again.
                        }
                        Vertex prevV = node; // prevV is used to handle when vrtexAtIndex is found and to be removed from its previous
                        node = node.next;
                        for(;node != null; prevV = node, node = node.next) {
                            if(node.key.equals(key)) {
                                prevV.next = node.next; //Removing other than first node in the link
                                return node; 
                            }
                        } 
                        return null;  //Nothing found in the linked list.
                    }
                }
            } else { //instanceof Edge
                return returnVal.removeLink(key, hash, (level + 1));
            }
        }
    }

}



class Base10ToBaseX {
    public static enum Base {
        /**
         * Integer is represented in 32 bit in 32 bit machine.
         * There we can split this integer no of bits into multiples of 1,2,4,8,16 bits
         */
        BASE2(1,1,32), BASE4(3,2,16), BASE8(7,3,11)/* OCTAL*/, /*BASE10(3,2),*/ 
        BASE16(15, 4, 8){       
            public String getFormattedValue(int val){
                switch(val) {
                case 10:
                    return "A";
                case 11:
                    return "B";
                case 12:
                    return "C";
                case 13:
                    return "D";
                case 14:
                    return "E";
                case 15:
                    return "F";
                default:
                    return "" + val;
                }

            }
        }, /*BASE32(31,5,1),*/ BASE256(255, 8, 4), /*BASE512(511,9),*/ Base65536(65535, 16, 2);

        private int LEVEL_0_MASK;
        private int LEVEL_1_ROTATION;
        private int MAX_ROTATION;

        Base(int levelZeroMask, int levelOneRotation, int maxPossibleRotation) {
            this.LEVEL_0_MASK = levelZeroMask;
            this.LEVEL_1_ROTATION = levelOneRotation;
            this.MAX_ROTATION = maxPossibleRotation;
        }

        int getLevelZeroMask(){
            return LEVEL_0_MASK;
        }
        int getLevelOneRotation(){
            return LEVEL_1_ROTATION;
        }
        int getMaxRotation(){
            return MAX_ROTATION;
        }
        String getFormattedValue(int val){
            return "" + val;
        }
    }

    public static int getBaseXValueOnAtLevel(Base base, int on, int level) {
        if(level > base.getMaxRotation() || level < 1) {
            return 0; //INVALID Input
        }
        int rotation = base.getLevelOneRotation();
        int mask = base.getLevelZeroMask();

        if(level > 1) {
            rotation = (level-1) * rotation;
            mask = mask << rotation;
        } else {
            rotation = 0;
        }
        return (on & mask) >>> rotation;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.