Triển khai Trie hiệu quả cho chuỗi unicode


12

Tôi đã tìm kiếm một triển khai String trie hiệu quả. Hầu như tôi đã tìm thấy mã như thế này:

Triển khai tham chiếu trong Java (mỗi wikipedia)

Tôi không thích những triển khai này vì hai lý do:

  1. Họ chỉ hỗ trợ 256 ký tự ASCII. Tôi cần phải bao gồm những thứ như cyrillic.
  2. Chúng cực kỳ kém hiệu quả.

Mỗi nút chứa một mảng gồm 256 tham chiếu, là 4096 byte trên máy 64 bit trong Java. Mỗi nút này có thể có tối đa 256 mã con với 4096 byte tham chiếu mỗi nút. Vì vậy, một Trie đầy đủ cho mỗi chuỗi ký tự ASCII 2 sẽ yêu cầu hơn 1MB. Ba chuỗi ký tự? 256MB chỉ cho các mảng trong các nút. Và như thế.

Tất nhiên tôi không có ý định có tất cả 16 triệu ba chuỗi ký tự trong Trie của mình, vì vậy rất nhiều không gian bị lãng phí. Hầu hết các mảng này chỉ là các tham chiếu null vì dung lượng của chúng vượt xa số lượng các phím được chèn thực tế. Và nếu tôi thêm unicode, các mảng thậm chí còn lớn hơn (char có 64k giá trị thay vì 256 trong Java).

Có bất kỳ hy vọng để làm cho một trie hiệu quả cho chuỗi? Tôi đã xem xét một vài cải tiến đối với các loại triển khai này:

  • Thay vì sử dụng mảng tham chiếu, tôi có thể sử dụng một mảng kiểu nguyên nguyên, chỉ mục thành một mảng tham chiếu đến các nút có kích thước gần bằng số nút thực tế.
  • Tôi có thể chia các chuỗi thành 4 phần bit cho phép các mảng nút có kích thước 16 với chi phí của một cây sâu hơn.

Câu trả lời:


2

Bạn đang sử dụng bộ ba này để làm gì? Tổng số từ mà bạn dự định nắm giữ là bao nhiêu và độ thưa thớt của các ký tự cấu thành của chúng là bao nhiêu? Và quan trọng nhất, là một trie thậm chí thích hợp (so với một bản đồ đơn giản của tiền tố để liệt kê các từ)?

Ý tưởng của bạn về một bảng trung gian và thay thế các con trỏ bằng các chỉ mục sẽ hoạt động, miễn là bạn có một bộ từ ngắn tương đối nhỏ và một bộ ký tự thưa thớt. Nếu không, bạn có nguy cơ hết không gian trong bảng trung gian của bạn. Và trừ khi bạn đang xem một nhóm từ cực kỳ nhỏ, bạn sẽ không thực sự tiết kiệm được nhiều dung lượng đó: 2 byte cho một đoạn ngắn so với 4 byte cho một tham chiếu trên máy 32 bit. Nếu bạn đang chạy trên JVM 64 bit, mức tiết kiệm sẽ nhiều hơn.

Ý tưởng của bạn về việc chia các ký tự thành các khối 4 bit có thể sẽ không giúp bạn tiết kiệm nhiều, trừ khi tất cả các ký tự dự kiến ​​của bạn nằm trong một phạm vi cực kỳ hạn chế (có thể OK đối với các từ giới hạn ở chữ hoa US-ASCII, không có khả năng với một văn bản Unicode chung ).

Nếu bạn có một bộ ký tự thưa thớt, thì đó HashMap<Character,Map<...>>có thể là cách triển khai tốt nhất của bạn. Có, mỗi mục sẽ lớn hơn nhiều, nhưng nếu bạn không có nhiều mục, bạn sẽ có được một chiến thắng chung. (như một lưu ý phụ: Tôi luôn nghĩ thật buồn cười khi bài viết trên Wikipedia về Tries cho thấy - có thể vẫn vậy - một ví dụ dựa trên cấu trúc dữ liệu băm, hoàn toàn bỏ qua sự đánh đổi không gian / thời gian của lựa chọn đó)

Cuối cùng, bạn có thể muốn tránh một trie hoàn toàn. Nếu bạn đang xem một tập hợp các từ thông thường bằng ngôn ngữ của con người (10.000 từ được sử dụng tích cực, với các từ dài 4-8 ký tự), có lẽ bạn sẽ NHIỀU tốt hơn với một HashMap<String,List<String>, trong đó khóa là toàn bộ tiền tố.


- Tham chiếu là 8 byte trên 32 bit, 16 byte trên máy 64 bit - Đó là chức năng tự động hoàn thành - Phần lớn các ký tự trong chuỗi nằm trong phạm vi ASCII, nhưng có một vài ký tự Trung Âu được đưa vào. Đây là lý do tôi muốn phân nhánh nhỏ hơn hơn 256, vì nó sẽ cắt ra số lượng lớn ký tự. Tôi không thấy HashMap <Chuỗi, Danh sách <Chuỗi >> tốt hơn hoặc nhanh hơn hoặc ít tiêu tốn bộ nhớ hơn, mặc dù thực sự dễ viết và sử dụng. Nhưng tôi sẽ chấp nhận cho ý tưởng HashMap <Nhân vật, Bản đồ>. Sẽ ổn đối với ký tự trên 128 (hiếm trong trường hợp của tôi - sẽ không tốt cho văn bản Trung Quốc).
RokL

4

nếu bạn mã hóa chuỗi thành UTF8, bạn có thể sử dụng bộ ba nhánh 256 tiêu chuẩn và vẫn tương thích unicode

ngoài ra, bạn nên lưu ý rằng chỉ có 70 hoặc hơn các ký tự trong số 128 ký tự ascii có thể (tất cả được mã hóa thành 1 byte trong UTF8) sẽ được tìm thấy nhiều nhất mà bạn có thể tối ưu hóa cho điều đó (như bao gồm các ký tự phổ biến thay cho các ký tự điều khiển không được sử dụng )


Tôi biết rằng UTF8 có thể được đại diện như thế. Tuy nhiên, điều này vẫn không giải quyết được mức tiêu thụ bộ nhớ vẫn còn khá cao. Việc hoán đổi các ký tự thành phạm vi 256 cơ bản sẽ đòi hỏi khá nhiều câu chuyển đổi, tôi nghi ngờ rằng nó sẽ có giá trị. Theo như UTF-8 ... đây thực sự là một vấn đề tôi đang cân nhắc. Chuỗi Java sử dụng ký tự UTF-16, mà tôi có thể dễ dàng nhận được, tôi có thể mã hóa các byte này theo byte. Hoặc tôi có thể chuyển đổi sang UTF-8 và sử dụng nó. Tại thời điểm này, tôi không rõ ràng nếu chi phí chuyển đổi từ UTF-16 sang UTF-8 có bị cấm hay không.
RokL

ngôn ngữ bạn hình dung sử dụng ngôn ngữ này trong hầu hết thời gian là gì? cố gắng tối ưu hóa cho mọi thứ là không thể (hoặc nó đã được thực hiện rồi) vì vậy tối ưu hóa cho trường hợp phổ biến
ratchet freak

1
Đây là một trong số rất ít trường hợp sử dụng trong đó CESU-8 sẽ thích hợp hơn UTF-8: lợi thế rất lớn ở đây là việc lấy từ một mật mã UTF-8 đến mã hóa CESU-8 tương ứng (trong khi bạn cần để giải mã 1-2 điểm mã UTF-16 để đến điểm mã UTF-8 tương ứng).
Joachim Sauer

1
@ratchetfreak Java. Mặc dù tôi nghĩ rằng câu hỏi có thể được khái quát cho hầu hết các ngôn ngữ. Tôi đoán trong C bạn có thể chỉ cần bỏ con trỏ byte*để mã hóa bất kỳ loại nào trong một trie bitwise.
RokL

@UMad Tôi có nghĩa là các ngôn ngữ mà chuỗi đầu vào sẽ có trong (tiếng Anh, tiếng Pháp, tiếng Đức, ...)
ratchet freak
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.