Khi nào thì chọn cây RB, cây B-Tree hay cây AVL?


88

Là một lập trình viên khi nào tôi nên cân nhắc sử dụng cây RB, cây B hay cây AVL? Những điểm chính cần được xem xét trước khi quyết định lựa chọn là gì?

Ai đó có thể vui lòng giải thích với một kịch bản cho từng cấu trúc cây tại sao nó được chọn thay vì những cấu trúc khác với tham chiếu đến các điểm chính?


10
Chà, tôi đánh giá cao câu hỏi này - hiện được trình bày với sự lựa chọn giữa fastutil IntAVLTreeSet so với IntRBTreeSet.
Yang

Câu trả lời:


114

Hãy chấm cái này với một chút muối:

B-tree khi bạn đang quản lý hơn hàng nghìn mục và bạn phân trang chúng từ đĩa hoặc một số phương tiện lưu trữ chậm.

Cây RB khi bạn thực hiện các thao tác chèn, xóa và truy xuất khá thường xuyên trên cây.

Cây AVL khi các lần chèn và lần xóa của bạn không thường xuyên liên quan đến các lần truy xuất của bạn.


34
Chỉ cần thêm một số chi tiết: Cây B có thể có số lượng con thay đổi cho phép nó chứa nhiều bản ghi nhưng vẫn duy trì một cây có chiều cao ngắn. RB Tree có các quy tắc ít nghiêm ngặt hơn xung quanh việc tái cân bằng giúp việc chèn / xóa nhanh hơn cây AVL. Ngược lại, cây AVL được cân bằng chặt chẽ hơn nên việc tra cứu nhanh hơn cây RB.
pschang

Cây RB cũng có hiệu suất tốt hơn O (1) trên cân bằng lại, điều này làm cho chúng phù hợp hơn với các cấu trúc dữ liệu liên tục với cuộn lùi và cuộn về phía trước.

20

Tôi nghĩ cây B + là một cấu trúc dữ liệu vùng chứa có thứ tự mục đích chung tốt, ngay cả trong bộ nhớ chính. Ngay cả khi bộ nhớ ảo không phải là một vấn đề, thì tính thân thiện với bộ nhớ cache thường xảy ra và cây B + đặc biệt tốt cho truy cập tuần tự - hiệu suất tiệm cận giống như một danh sách được liên kết, nhưng thân thiện với bộ nhớ cache gần với một mảng đơn giản. Tất cả điều này và O (log n) tìm kiếm, chèn và xóa.

Tuy nhiên, cây B + có vấn đề - chẳng hạn như các mục di chuyển xung quanh các nút khi bạn thực hiện chèn / xóa, làm mất hiệu lực con trỏ đến các mục đó. Tôi có một thư viện vùng chứa có chức năng "bảo trì con trỏ" - các con trỏ tự gắn vào nút lá mà chúng hiện đang tham chiếu trong danh sách được liên kết, vì vậy chúng có thể được sửa hoặc vô hiệu tự động. Vì hiếm khi có nhiều hơn một hoặc hai con trỏ, nó hoạt động tốt - nhưng tất cả các công việc đều giống nhau.

Một điều nữa là cây B + về cơ bản chỉ có vậy. Tôi đoán bạn có thể loại bỏ hoặc tạo lại các nút không phải lá tùy thuộc vào việc bạn có cần chúng hay không, nhưng với các nút cây nhị phân, bạn sẽ linh hoạt hơn rất nhiều. Cây nhị phân có thể được chuyển đổi thành một danh sách được liên kết và quay lại mà không cần sao chép các nút - bạn chỉ cần thay đổi các con trỏ sau đó nhớ rằng bạn đang coi nó như một cấu trúc dữ liệu khác. Trong số những thứ khác, điều này có nghĩa là bạn có thể hợp nhất O (n) cây khá dễ dàng - chuyển đổi cả hai cây thành danh sách, hợp nhất chúng, sau đó chuyển đổi trở lại một cây.

Tuy nhiên, một điều khác là phân bổ và giải phóng bộ nhớ. Trong cây nhị phân, điều này có thể được tách ra khỏi các thuật toán - người dùng có thể tạo một nút sau đó gọi thuật toán chèn và việc xóa có thể trích xuất các nút (tách chúng khỏi cây, nhưng không giải phóng bộ nhớ). Trong B-tree hoặc B + -tree, điều đó rõ ràng là không hoạt động - dữ liệu sẽ nằm trong một nút nhiều mục. Viết các phương thức chèn "lập kế hoạch" hoạt động mà không cần sửa đổi các nút cho đến khi chúng biết cần có bao nhiêu nút mới và chúng có thể được cấp phát là một thách thức.

Đỏ đen so với AVL? Tôi không chắc nó tạo ra sự khác biệt lớn nào. Thư viện của riêng tôi có một lớp "công cụ" dựa trên chính sách để thao tác các nút, với các phương thức cho danh sách liên kết đôi, cây nhị phân đơn giản, cây splay, cây đỏ đen và cây treap, bao gồm các chuyển đổi khác nhau. Một số phương pháp đó chỉ được thực hiện bởi vì tôi cảm thấy nhàm chán vào lúc này hay lúc khác. Tôi không chắc mình thậm chí đã thử nghiệm các phương pháp treap. Lý do tôi chọn cây đỏ đen chứ không phải AVL là vì cá nhân tôi hiểu các thuật toán tốt hơn - điều đó không có nghĩa là chúng đơn giản hơn, đó chỉ là một chút lịch sử mà tôi quen thuộc hơn với chúng.

Một điều cuối cùng - ban đầu tôi chỉ phát triển các thùng chứa cây B + của mình như một thử nghiệm. Đó là một trong những thử nghiệm chưa bao giờ thực sự kết thúc, nhưng nó không phải là điều tôi khuyến khích người khác lặp lại. Nếu tất cả những gì bạn cần là một vùng chứa có thứ tự, câu trả lời tốt nhất là sử dụng một vùng chứa mà thư viện hiện có của bạn cung cấp - ví dụ: std :: map, v.v. trong C ++. Thư viện của tôi đã phát triển qua nhiều năm, phải mất khá nhiều thời gian để làm cho nó ổn định và gần đây tôi mới phát hiện ra rằng nó không di động về mặt kỹ thuật (phụ thuộc vào một chút hành vi không xác định của WRT offsetof).



0

Khi chọn cấu trúc dữ liệu, bạn đang đánh đổi các yếu tố như

  • tốc độ truy xuất v tốc độ cập nhật
  • cấu trúc đối phó tốt như thế nào với các hoạt động trong trường hợp xấu nhất, chẳng hạn như chèn các bản ghi đến một thứ tự được sắp xếp
  • không gian lãng phí

Tôi sẽ bắt đầu bằng cách đọc các bài báo trên Wikipedia do Robert Harvey tham khảo.

Thực tế, khi làm việc bằng các ngôn ngữ như Java, lập trình viên trung bình có xu hướng sử dụng các lớp thu thập được cung cấp. Nếu trong một hoạt động điều chỉnh hiệu suất, một người phát hiện ra rằng hiệu suất thu thập có vấn đề thì người ta có thể tìm kiếm các triển khai thay thế. Đây hiếm khi là điều đầu tiên mà một sự phát triển do doanh nghiệp dẫn đầu phải xem xét. Rất hiếm khi người ta cần thực hiện các cấu trúc dữ liệu như vậy bằng tay, thường có các thư viện có thể được sử dụng.


1
Công bằng mà nói, OP hỏi when should I consider using, không phải when should I consider implementing. Trong khi đoạn cuối cùng là đúng, nó không cung cấp nhiều giá trị trong bối cảnh của câu hỏi này. Ngay cả với các thư viện, bạn cũng cần phải hiểu các thuật toán để chọn một cách hiệu quả cấu trúc nào phù hợp nhất với nhu cầu kinh doanh của bạn.
Dan Bechard
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.