Danh sách bỏ qua: Một giải pháp thay thế xác suất cho cây cân bằng. Một lần nữa về danh sách bỏ qua... Phân bổ yêu cầu không đồng đều

Một loại cấu trúc dữ liệu thú vị để triển khai hiệu quả từ điển có thứ tự ADT là Skip^cnucoK (danh sách bỏ qua). Cấu trúc dữ liệu này sắp xếp các đối tượng theo thứ tự ngẫu nhiên như sau:

sao cho việc tìm kiếm và cập nhật được hoàn thành trung bình trong thời gian 0(log n), trong đó n là số lượng đối tượng từ điển. Điều thú vị là khái niệm về độ phức tạp thời gian trung bình được sử dụng ở đây không phụ thuộc vào khả năng phân phối các khóa trong đầu vào. Ngược lại, nó được quyết định bởi việc sử dụng các bộ tạo số ngẫu nhiên trong quá trình thực hiện quy trình đầu vào để tạo điều kiện thuận lợi cho quá trình quyết định vị trí chèn một đối tượng mới. Thời gian thực hiện trong trường hợp này sẽ bằng trung bình thời gian thực hiện nhập bất kỳ số ngẫu nhiên nào được sử dụng làm đối tượng đầu vào.

Các phương pháp tạo số ngẫu nhiên được tích hợp trong hầu hết các máy tính hiện đại vì chúng được sử dụng rộng rãi trong các trò chơi máy tính, mật mã và mô phỏng máy tính. Một số phương pháp, được gọi là trình tạo số giả ngẫu nhiên, tạo ra các số giả ngẫu nhiên theo một số luật, bắt đầu từ cái gọi là hạt giống. Các phương pháp khác sử dụng phần cứng để trích xuất các số ngẫu nhiên "thực sự". Trong cả hai trường hợp, máy tính đều có những con số đáp ứng yêu cầu về số ngẫu nhiên trong phép phân tích đã cho.

Ưu điểm chính của việc sử dụng số ngẫu nhiên trong cấu trúc dữ liệu và tạo thuật toán là tính đơn giản và độ tin cậy của cấu trúc và phương pháp thu được. Bằng cách này, có thể tạo một cấu trúc dữ liệu ngẫu nhiên đơn giản được gọi là danh sách bỏ qua, cung cấp chi phí thời gian tìm kiếm logarit tương tự như thuật toán tìm kiếm nhị phân. Tuy nhiên, chi phí thời gian dự kiến ​​của danh sách bỏ qua sẽ lớn hơn thời gian tìm kiếm nhị phân trong bảng tra cứu. Mặt khác, khi cập nhật từ điển, danh sách bỏ qua nhanh hơn nhiều so với bảng tra cứu.

Giả sử danh sách bỏ qua S của từ điển D bao gồm một loạt các danh sách (iSo, S\, Si, З/j). Mỗi danh sách S\ lưu trữ một tập các đối tượng từ điển D theo các khóa theo thứ tự không giảm, cộng với các đối tượng có hai khóa đặc biệt, được viết là “-oo” và “+oo”, trong đó “-oo” có nghĩa là ít hơn và “+oo” có ý nghĩa nhiều hơn bất kỳ khóa nào có thể có trong D. Ngoài ra, các danh sách trong S đáp ứng các yêu cầu sau:

Danh sách S 0 chứa từng đối tượng của từ điển D (cộng với các đối tượng đặc biệt có khóa “-oo” và “+oo”);

Với / = 1, …, h – 1, danh sách Si chứa (ngoài “-oo” và “+oo”) một tập đối tượng được tạo ngẫu nhiên từ danh sách S t _ ь

Danh sách S h chỉ chứa “-oo” và “+oo”.

Một ví dụ về danh sách bỏ qua như vậy được hiển thị trong Hình. 8.9, thể hiện trực quan danh sách S với một danh sách ở cơ sở và liệt kê S\, ..., S^ ở trên nó. Chúng ta biểu thị chiều cao của danh sách S là h.

Cơm. 8,9. ví dụ về danh sách bỏ qua

Theo trực giác, các danh sách được sắp xếp theo cách này; vậy thì?/+/ chứa ít nhất mọi đối tượng thứ hai 5/. Như sẽ được hiển thị khi xem xét phương thức nhập, các đối tượng trong St+\ được chọn ngẫu nhiên từ các đối tượng trong Si theo cách mà mỗi đối tượng được chọn 5/ được bao gồm trong 5/ + \ với xác suất 1/2. Nói một cách hình tượng, việc đặt một vật vào Si + 1 hay không được xác định tùy thuộc vào việc đồng xu được tung rơi vào mặt nào - mặt ngửa hay mặt sấp. Vì vậy, chúng ta có thể giả sử rằng S\ chứa khoảng n/2 đối tượng, S2 - n/4, và theo đó, Sj - khoảng n/2' đối tượng. Nói cách khác, chiều cao h của danh sách S có thể bằng logn. Mặc dù vậy, việc giảm một nửa số lượng đối tượng từ danh sách này sang danh sách khác không phải là yêu cầu bắt buộc dưới dạng thuộc tính danh sách bỏ qua rõ ràng. Ngược lại, lựa chọn ngẫu nhiên là quan trọng hơn.

Bằng cách sử dụng tính trừu tượng hóa vị trí được áp dụng cho danh sách và cây, chúng ta có thể coi danh sách bỏ qua là một tập hợp hai chiều của các vị trí, được sắp xếp theo chiều ngang thành các cấp và theo chiều dọc thành các tháp. Mỗi cấp độ là một danh sách S^ và mỗi tháp là một tập hợp các vị trí lưu trữ cùng một đối tượng và nằm chồng lên nhau trong danh sách. Các vị trí trong danh sách bỏ qua có thể được duyệt qua bằng các thao tác sau:

after(/?): trả về vị trí cạnh cùng cấp; before(/?): trả về vị trí trước p ở cùng cấp độ; bên dưới(/?): trả về vị trí nằm bên dưới p trong cùng một tòa tháp; ở trên(/?): Trả về vị trí phía trên p trong cùng một tòa tháp.

Hãy xác định rằng các thao tác trên phải trả về null nếu vị trí được yêu cầu không tồn tại. Không đi sâu vào chi tiết, hãy lưu ý rằng có thể dễ dàng triển khai danh sách bỏ qua bằng cách sử dụng cấu trúc được kết nối theo cách mà các phương thức chuyển yêu cầu 0(1) thời gian (nếu có vị trí p trong danh sách). Cấu trúc được kết nối như vậy là một tập hợp các danh sách được liên kết đôi, các danh sách này lần lượt là các danh sách được liên kết đôi.

Cấu trúc danh sách bỏ qua cho phép áp dụng các thuật toán tìm kiếm đơn giản vào từ điển. Trên thực tế, tất cả các thuật toán tìm kiếm danh sách bỏ qua đều dựa trên phương pháp SkipSearch khá tinh tế, phương pháp này lấy một khóa và tìm đối tượng trong danh sách bỏ qua S với khóa lớn nhất (có thể là "-oo") nhỏ hơn hoặc bằng thành k. Giả sử chúng ta có khóa mong muốn. Phương thức SkipSearch đặt vị trí của p ở vị trí trên cùng bên trái trong danh sách bỏ qua S. Nghĩa là, p được đặt thành vị trí của đối tượng đặc biệt bằng phím "-oo " ở S^. Sau đó, điều sau đây được thực hiện:

1) nếu S.below(/?) là null thì quá trình tìm kiếm kết thúc - đối tượng lớn nhất trong S có khóa nhỏ hơn hoặc bằng khóa mong muốn k được tìm thấy ở cấp độ bên dưới. Nếu không, chúng ta sẽ đi xuống một cấp trong tháp này, đặt p S.be \ow(p);

2) từ vị trí p chúng ta di chuyển sang phải (tiến lên) cho đến khi chúng ta thấy mình ở vị trí cực bên phải của cấp độ hiện tại, trong đó keu(/?)< к. Такой шаг называется сканированием вперед (scan forward). Указанная позиция существует всегда, поскольку каждый уровень имеет специальные ключи «+оо» и «-оо». И на самом деле, после шага вправо по текущему уровню р может остаться на исходном месте. В любом случае после этого снова повторяется предыдущее действие.

Cơm. 8.10. Ví dụ về tìm kiếm trong danh sách bỏ qua. 50 vị trí được kiểm tra trong quá trình tìm kiếm chính được đánh dấu bằng ký hiệu nét đứt

Đoạn mã 8.4 chứa mô tả về thuật toán tìm kiếm danh sách bỏ qua. Có được phương thức như vậy, thật dễ dàng để thực hiện thao tác findElement(/:). Thao tác p^-SkipSearch(A;) được thực hiện và khóa đẳng thức(p)~ k được kiểm tra. Nếu ^bằng, phần tử /?.được trả về. Ngược lại, bản tin báo hiệu NO_SUCH_KEY sẽ được trả về.

Thuật toán SkipSearch(/:):

Đầu vào: phím tìm kiếm.

Đầu ra: Vị trí p trong S có đối tượng có khóa lớn nhất nhỏ hơn hoặc bằng k.

Giả sử p là vị trí trên cùng bên trái trong S (gồm ít nhất hai cấp độ), trong khi ở dưới (p) * null do

р bên dưới(p) (quét xuống) while key(after(p))< к do

Đặt p sau(p) (quét tiến) trả về p

Đoạn mã 8.4. Thuật toán tìm kiếm trong danh sách bỏ qua S

Hóa ra thời gian thực hiện ước tính của thuật toán SkipSearch là 0(log n). Trước khi giải thích điều này, chúng tôi trình bày cách thực hiện các phương pháp cập nhật danh sách bỏ qua

NHẢY là một hệ thống giám sát việc thực hiện các đơn hàng dựa trên MS SharePoint, cho phép bạn tự động hóa hoàn toàn quá trình làm việc với các đơn hàng. Đây là một giải pháp chu đáo, đột phá để sắp xếp công việc với các công việc lặt vặt. Nó phù hợp cho công việc ở cả các công ty lớn và phân bố theo địa lý cũng như các công ty cỡ trung bình do khả năng cung cấp cấu hình linh hoạt nhất cho tất cả các mô-đun.

Hệ thống SKIP dựa trên nền tảng Microsoft SharePoint, điều này tự động có nghĩa là nó có thể được tích hợp với các sản phẩm của Microsoft, bao gồm cả Microsoft Office.

Chức năng hệ thống

Hệ thống SKIP là một giải pháp “đóng hộp” và trong phiên bản này chứa một bộ chức năng cơ bản cần thiết để tự động hóa công việc với các đơn đặt hàng:

  • Phân công, thực hiện, kiểm soát mệnh lệnh;
  • Theo dõi tình trạng thực hiện lệnh;
  • Khả năng tạo các đơn hàng lồng nhau (“con”).

Danh sách hướng dẫn có chỉ báo màu

Đồng thời, chức năng được trình bày được triển khai theo cách cung cấp cho người dùng khả năng làm việc với hệ thống rộng nhất có thể:

  • Lập danh mục đơn hàng (một đơn hàng có thể được đặt trong các thư mục khác nhau);
  • Lọc danh sách đơn hàng;
  • Xuất danh sách đơn hàng sang MS Excel;
  • Báo cáo kỷ luật thực hiện;
  • Chỉ thị màu sắc của lệnh tùy theo thời gian thực hiện và trạng thái của lệnh;
  • Khả năng đính kèm số lượng tệp tùy ý ở bất kỳ định dạng nào vào Thẻ đặt hàng;
  • Tích hợp với lịch Outlook;
  • Thông báo có thể tùy chỉnh về nhiệm vụ và tiến độ công việc theo đơn đặt hàng;
  • Hệ thống thay thế nhân viên trong các kỳ nghỉ, chuyến công tác;
  • Tạo đơn hàng định kỳ (hoặc đơn hàng có lịch trình) cho các sự kiện có thời gian nhất định (cuộc họp, cuộc hẹn, v.v.);
  • Hiển thị thời hạn hoàn thành đơn hàng trên biểu đồ Gantt;
  • Và như thế

Danh sách nhiệm vụ với biểu đồ Gantt

Có một ý kiến ​​​​khá phổ biến rằng việc hoàn thành các nhiệm vụ kiểm tra khác nhau sẽ giúp nâng cao trình độ chuyên môn của bạn rất nhanh. Bản thân tôi đôi khi thích tìm ra một số câu hỏi trắc nghiệm khó và giải nó, để không ngừng lo lắng, như người ta nói. Khi tôi đang hoàn thành một nhiệm vụ mang tính cạnh tranh để thực tập tại một công ty, tôi thấy vấn đề này có vẻ buồn cười và thú vị, đây là đoạn văn ngắn của nó:
Hãy tưởng tượng rằng đồng nghiệp đang than vãn của bạn đến để nói về nhiệm vụ khó khăn của anh ấy - anh ấy không chỉ cần sắp xếp một tập hợp các số nguyên theo thứ tự tăng dần mà còn phải xuất ra tất cả các phần tử của tập hợp có thứ tự từ L đến R!
Bạn đã nói rằng đây là một nhiệm vụ cơ bản và bạn sẽ mất mười phút để viết lời giải bằng C#. Vâng, hoặc một giờ. Hoặc hai. Hoặc thanh sô cô la Alyonka

Giả định rằng các bản sao được cho phép trong tập hợp và số phần tử sẽ không nhiều hơn 10^6.

Có một số nhận xét về việc đánh giá giải pháp:

Mã của bạn sẽ được đánh giá và kiểm tra bởi ba lập trình viên:
  • Bill sẽ chạy giải pháp của bạn trong các bài kiểm tra không lớn hơn 10Kb.
  • Trong thử nghiệm của Stephen, số lượng yêu cầu sẽ không quá 10^5, trong khi số lượng yêu cầu thêm sẽ không quá 100.
  • Trong thử nghiệm của Mark, số lượng yêu cầu sẽ không quá 10^5.
Giải pháp có thể rất thú vị nên tôi nghĩ cần phải mô tả nó.

Giải pháp

Hãy để chúng tôi có một bộ lưu trữ trừu tượng.

Hãy biểu thị Add(e) - thêm một phần tử vào cửa hàng và Range(l, r) - lấy một lát từ phần tử l đến r.

Một tùy chọn lưu trữ tầm thường có thể như thế này:

  • Cơ sở lưu trữ sẽ là một mảng động được sắp xếp theo thứ tự tăng dần.
  • Với mỗi lần chèn, tìm kiếm nhị phân sẽ tìm vị trí mà phần tử phải được chèn.
  • Khi yêu cầu Range(l, r), chúng ta sẽ chỉ lấy một phần của mảng từ l đến r.
Hãy đánh giá độ phức tạp của phương pháp dựa trên một mảng động.

Phạm vi C(l, r) - việc lấy một lát có thể được ước tính là O(r - l).

C Add(e) - chèn trong trường hợp xấu nhất sẽ hoạt động trong O(n), trong đó n là số phần tử. Tại n ~ 10^6, việc chèn là nút cổ chai. Dưới đây trong bài viết một tùy chọn lưu trữ cải tiến sẽ được đề xuất.

Một ví dụ về mã nguồn có thể được xem.

Danh sách bỏ qua

Skiplist là một giải pháp thay thế ngẫu nhiên cho cây tìm kiếm dựa trên nhiều danh sách liên kết. Nó được phát minh bởi William Pugh vào năm 1989. Các thao tác tìm kiếm, chèn và xóa được thực hiện theo thời gian ngẫu nhiên logarit.

Cấu trúc dữ liệu này không được biết đến rộng rãi (nhân tiện, có khá nhiều bài đánh giá về nó được viết về nó trên Habré), mặc dù nó có các ước tính tiệm cận tốt. Vì tò mò nên tôi muốn thực hiện nó, nhất là khi tôi có một nhiệm vụ phù hợp.

Giả sử chúng ta có một danh sách liên kết đơn được sắp xếp:

Trong trường hợp xấu nhất, việc tìm kiếm được thực hiện trong O(n). Làm thế nào nó có thể được tăng tốc?

Trong một trong những video bài giảng mà tôi đã xem khi giải bài toán này, có một ví dụ tuyệt vời về các tuyến đường cao tốc ở New York:

  • Đường cao tốc kết nối một số ga
  • Tuyến thông thường kết nối tất cả các trạm
  • Có các tuyến thường xuyên giữa các trạm đường cao tốc thông thường
Ý tưởng là thế này: chúng ta có thể thêm một số cấp nhất định với số lượng nút nhất định trong đó và các nút trong các cấp phải được phân bổ đều. Hãy xem xét một ví dụ khi mỗi cấp độ nằm trên chứa một nửa số phần tử từ cấp độ cơ bản:


Ví dụ này cho thấy một SkipList lý tưởng, trên thực tế mọi thứ trông giống nhau, nhưng có một chút khác biệt :)

Tìm kiếm

Đây là cách tìm kiếm diễn ra. Giả sử chúng ta đang tìm kiếm phần tử thứ 72:

Chèn

Với phần chèn, mọi thứ phức tạp hơn một chút. Để chèn một phần tử, chúng ta cần hiểu vị trí cần chèn nó vào danh sách thấp nhất, đồng thời đẩy nó lên một số cấp cao nhất định. Nhưng mỗi yếu tố cụ thể nên được đẩy qua bao nhiêu cấp độ?

Đề xuất giải quyết theo cách này: khi chèn, chúng ta thêm một phần tử mới vào mức thấp nhất và bắt đầu tung một đồng xu, cho đến khi nó rơi ra, chúng ta đẩy phần tử đó lên cấp cao hơn tiếp theo.
Hãy thử chèn phần tử - 67. Trước tiên, hãy tìm vị trí cần chèn trong danh sách cơ bản:

Tôi cho rằng đồng xu rơi ra hai lần liên tiếp. Điều này có nghĩa là bạn cần đẩy phần tử lên hai cấp độ:

Truy cập theo chỉ mục

Để truy cập theo chỉ mục, đề xuất thực hiện như sau: đánh dấu mỗi chuyển đổi bằng số nút nằm bên dưới nó:

Khi chúng ta có quyền truy cập vào phần tử thứ i (nhân tiện, chúng ta nhận được nó ở dạng O(ln n)), việc tạo một lát cắt có vẻ không khó.

Giả sử cần tìm Range(5, 7). Đầu tiên chúng ta lấy phần tử ở chỉ số thứ năm:


Và bây giờ Phạm vi (5, 7):

Về việc thực hiện

Có vẻ như việc triển khai nút SkipList trông giống như thế này:
SkipListNode(
phần tử int;
SkipListNodeNext;
int Chiều rộng;
}
Trên thực tế, đó là những gì đã được thực hiện.

Nhưng trong C#, mảng cũng lưu trữ độ dài của nó và tôi muốn thực hiện điều này trong ít bộ nhớ nhất có thể (hóa ra, trong điều kiện của vấn đề, mọi thứ không được đánh giá nghiêm ngặt đến vậy). Đồng thời, tôi muốn đảm bảo rằng việc triển khai SkipList và Cây RB mở rộng chiếm cùng một lượng bộ nhớ.

Câu trả lời về cách giảm mức tiêu thụ bộ nhớ đã bất ngờ được tìm thấy khi kiểm tra kỹ hơn từ gói java.util.concurrent.

Danh sách bỏ qua 2D

Giả sử có một danh sách liên kết đơn gồm tất cả các phần tử trong một chiều. Phần thứ hai sẽ chứa các “đường tốc hành” để chuyển tiếp với các liên kết đến cấp độ thấp hơn.
Danh sáchNode(
phần tử int;
ListNodeNext;
}

Ngõ (
Làn đường bên phải;
Ngõ xuống;
Nút ListNode;
}

Đồng xu không trung thực

Bạn cũng có thể sử dụng đồng xu “không trung thực” để giảm mức tiêu thụ bộ nhớ: giảm khả năng đẩy một phần tử lên cấp độ tiếp theo. Bài viết của William Pugh đã xem xét một mặt cắt ngang của một số giá trị xác suất đẩy. Trong thực tế, khi xem xét các giá trị của ½ và ¼, thời gian tìm kiếm đạt được gần như nhau trong khi giảm mức tiêu thụ bộ nhớ.

Một chút về việc tạo số ngẫu nhiên

Tìm hiểu sâu hơn về ConcurrentSkipListMap, tôi nhận thấy các số ngẫu nhiên được tạo ra như sau:
int RandomLevel() (
int x = Hạt giống ngẫu nhiên;
x ^= x<< 13;
x ^= x >>> 17;
hạt giống ngẫu nhiên = x ^= x<< 5;
nếu ((x & 0x80000001) != 0)
trả về 0;
mức int = 1;
while (((x >>>= 1) & 1) != 0) ++level;
mức trả lại;
}
Bạn có thể đọc thêm về cách tạo số giả ngẫu nhiên bằng XOR trong bài viết này. Tôi không nhận thấy bất kỳ sự gia tăng cụ thể nào về tốc độ nên tôi không sử dụng nó.

Bạn có thể xem nguồn của kho lưu trữ kết quả.

Tất cả cùng nhau có thể được tải xuống từ googlecode.com (Dự án phân trang).

Kiểm tra

Ba loại lưu trữ đã được sử dụng:
  1. ArrayBased (mảng động)
  2. SkipListBased (SkipList với tham số ¼)
  3. RBTreeBased (cây đỏ-đen: việc triển khai của một người bạn của tôi, người đã thực hiện nhiệm vụ tương tự).
Ba loại thử nghiệm đã được thực hiện để chèn 10^6 phần tử:
  1. Các phần tử được sắp xếp theo thứ tự tăng dần
  2. Các mục được sắp xếp theo thứ tự giảm dần
  3. Yếu tố ngẫu nhiên
Các thử nghiệm được thực hiện trên máy có i5, ram 8gb, ssd và Windows 7 x64.
Kết quả:
Mảng RBtree Danh sách bỏ qua
Ngẫu nhiên 127033 mili giây 1020 mili giây 1737 mili giây
Đã đặt hàng 108 mili giây 457 mili giây 536 mili giây
Sắp xếp theo thứ tự giảm dần 256337 mili giây 358 mili giây 407 mili giây
Kết quả khá mong đợi. Bạn có thể thấy rằng việc chèn vào một mảng, khi một phần tử được chèn vào một nơi nào đó không phải ở cuối, là chậm nhất. Đồng thời, SkipList chậm hơn RBTree.

Các phép đo cũng được thực hiện: mỗi bộ nhớ chiếm bao nhiêu trong bộ nhớ với 10^6 phần tử được chèn vào đó. Chúng tôi đã sử dụng một studio profiler; để đơn giản, chúng tôi chạy đoạn mã sau:

lưu trữ var = ...
với (var i = 0; i< 1000000; ++i)
lưu trữ.Add(i);
Kết quả:
Mảng RBtree Danh sách bỏ qua
Tổng số byte được phân bổ 8.389.066 byte 24.000.060 byte 23.985.598 byte
Ngoài ra còn có những kết quả khá được mong đợi: việc lưu trữ trên một mảng động chiếm ít dung lượng bộ nhớ nhất và SkipList và RBTree chiếm lượng bộ nhớ tương đương.

Kết thúc có hậu với “Alenka”

Một đồng nghiệp hay than vãn, theo điều khoản của nhiệm vụ, đặt cho tôi một thanh sô cô la. Giải pháp của tôi đã được chấp nhận với số điểm tối đa. Tôi hy vọng bài viết này sẽ hữu ích cho ai đó. Nếu bạn có bất kỳ câu hỏi nào, tôi sẽ vui lòng trả lời.

P.S.: Tôi đã từng thực tập tại SKB Kontur. Điều này là để tránh trả lời những câu hỏi tương tự =)

7 câu trả lời

Vì bạn đã đề cập đến Danh sách vừa có thể lập chỉ mục (tôi cho rằng bạn muốn truy xuất nhanh hơn) vừa cho phép trùng lặp, tôi khuyên bạn nên sử dụng bộ tùy chỉnh với LinkedList hoặc ArrayList.

Bạn cần có một bộ cơ sở, chẳng hạn như HashSet và tiếp tục thêm các giá trị vào đó. Nếu bạn gặp một bản sao, giá trị của bộ này sẽ trỏ đến danh sách. Vì vậy, bạn sẽ có cả khả năng tìm kiếm nhanh và tất nhiên, bạn sẽ lưu các đối tượng của mình dưới dạng bộ sưu tập psuedo.

Điều này sẽ mang lại cho bạn hiệu quả khai thác tốt. Lý tưởng nhất là nếu Khóa của bạn không bị trùng lặp, bạn sẽ đạt được tốc độ tìm kiếm O(1).

Câu trả lời này đã muộn 3 năm, nhưng tôi hy vọng nó sẽ hữu ích cho những ai muốn có danh sách bỏ qua Java kể từ bây giờ :)

Giải pháp này cho phép sao chép theo yêu cầu của bạn. Tôi đang làm theo hướng dẫn đại khái ở đây http://igoro.com/archive/skip-lists-are-fascinating, nên những khó khăn cũng tương tự như vậy xóa bỏ chi phí O(nlogn) - tôi đã làm như thế nào "Đừng cố sử dụng các nút được kết nối đôi, tôi đoán điều này sẽ dẫn đến xóa bỏ tới O(đăng nhập).

Mã chứa: một giao diện, một danh sách bỏ qua triển khai giao diện và một lớp nút. Nó cũng là chung chung.

Bạn có thể tùy chỉnh tham số CẤP ĐỘđể thực hiện, nhưng hãy nhớ sự cân bằng giữa không gian và thời gian.

> tìm kiếm(dữ liệu T); ) lớp công khai SkipList > triển khai SkipableList ( public static void main(String args) ( SkipList sl = SkipList mới<>(); dữ liệu int = (4,2,7,0,9,1,3,7,3,4,5,6,0,2,8); for (int i: data) ( sl.insert(i); ) sl.print(); sl.search(4); sl.delete(9); sl.print(); sl.insert(69); sl.print(); sl.search(69); ) cuối cùng riêng tư SkipNode đầu = SkipNode mới<>SkipNode = SkipNode mới<>(dữ liệu); vì (int i = 0; tôi< LEVELS; i++) { if (rand.nextInt((int) Math.pow(2, i)) == 0) { //insert with prob = 1/(2^i) insert(SkipNode, i); } } } @Override public boolean delete(T target) { System.out.println("Deleting " + target.toString()); SkipNodenạn nhân = tìm kiếm(mục tiêu, sai); if (nạn nhân == null) trả về sai; nạn nhân.data = null; vì (int i = 0; tôi< LEVELS; i++) { head.refreshAfterDelete(i); } System.out.println(); return true; } @Override public SkipNodesearch(T data) ( return search(data, true); ) @Override public void print() ( for (int i = 0; i< LEVELS; i++) { head.print(i); } System.out.println(); } private void insert(SkipNode kết quả = null; for (int i = LEVELS-1; i >= 0; i--) ( if ((result = head.search(data, i, print)) != null) ( if (print) ( System.out.println ("Đã tìm thấy " + data.toString() + " ở cấp độ " + i + ", nên đã dừng"); System.out.println(); ) break; ) ) kết quả trả về; ) ) lớp SkipNode > tiếp theo = (Bỏ quaNode hiện tại = this.getNext (cấp độ); while (current != null && current.getNext(level) != null) ( if (current.getNext(level).data == null) ( SkipNode kết quả = null; SkipNode < 1) { if (current.data.equals(data)) { result = current; break; } current = current.getNext(level); } return result; } void insert(SkipNodeSkipNode, cấp độ int) ( SkipNode hiện tại = this.getNext (cấp độ); if (current == null) ( this.setNext(SkipNode, level); return; ) if (SkipNode.data.compareTo(current.data)< 1) { this.setNext(SkipNode, level); SkipNode.setNext(current, level); return; } while (current.getNext(level) != null && current.data.compareTo(SkipNode.data) < 1 && current.getNext(level).data.compareTo(SkipNode.data) < 1) { current = current.getNext(level); } SkipNodengười kế nhiệm = current.getNext(level); current.setNext(Bỏ quaNode, cấp độ); SkipNode.setNext(người kế nhiệm, cấp độ); ) void print(int level) ( System.out.print("level " + level + ": ["); int length = 0; SkipNode hiện tại = this.getNext (cấp độ); while (current != null) ( length++; System.out.print(current.data.toString() + " "); current = current.getNext(level); ) System.out.println("], length: " + chiều dài); ) )

Bạn có thể sử dụng đoạn mã dưới đây để tạo cho riêng mình thuyền trưởng cơ bản:

1) Làm bắt đầukết thúcđể thể hiện sự bắt đầu và kết thúc của danh sách bỏ qua.

2) Thêm các nút và gán con trỏ tới nút tiếp theo dựa trên

Nếu (nút chẵn) thì chỉ định con trỏ làn nhanh với con trỏ tiếp theo nếu không chỉ gán con trỏ cho nút tiếp theo

Mã Javađối với danh sách bỏ qua cơ bản (bạn có thể thêm các tính năng bổ sung):

Lớp công khai MyClass ( public static void main(String args) ( Skiplist Skiplist=new Skiplist(); Node n1=new Node(); Node n2=new Node(); Node n3=new Node(); Node n4=new Node (); Nút n5=Nút mới(); Nút n6=Nút mới(); n1.setData(1); n2.setData(2); n3.setData(3); n4.setData(4); n5.setData (5); n6.setData(6); Skiplist.insert(n1); Skiplist.insert(n2); Skiplist.insert(n3); Skiplist.insert(n4); Skiplist.insert(n5); Skiplist.insert( n6); /*in tất cả các nút*/ Skiplist.display(); System.out.println(); /* chỉ in nút làn đường nhanh*/ Skiplist.displayFast(); ) ) lớp Node( dữ liệu int riêng tư; Nút riêng tư one_next; //chứa con trỏ tới nút tiếp theo Private Node two_next; //con trỏ tới nút sau nút tiếp theo public int getData() ( return data; ) public void setData(int data) ( this.data = data; ) public Nút getOne_next() ( return one_next; ) public void setOne_next(Node one_next) ( this.one_next = one_next; ) Nút công khai getTwo_next() ( return two_next; ) public void setTwo_next(Node two_next) ( this.two_next = two_next; ) ) class Skiplist( Nút bắt đầu; // bắt đầu con trỏ để bỏ qua danh sách Nút đầu; Nút temp_next; // con trỏ để lưu trữ nút làn nhanh được sử dụng lần cuối Nút kết thúc; // kết thúc danh sách bỏ qua int length; public Skiplist())( start = new Node(); end=new Node(); length=0; temp_next=start; ) public void chèn(Nút nút)( /*if danh sách bỏ qua trống */ if(length==0)( start.setOne_next ( node); node.setOne_next(end); temp_next.setTwo_next(end); head=start; length++; ) else( length++; Node temp=start.getOne_next(); Node prev=start; while(temp != end) ( prev=temp; temp=temp.getOne_next(); ) /*thêm con trỏ làn nhanh cho cả số nút không có nút*/ if(length%2==0)( prev.setOne_next(node); node.setOne_next(end ) ; temp_next.setTwo_next(node); temp_next=node; node.setTwo_next(end); ) /*không có nút lẻ nào sẽ không chứa con trỏ làn nhanh*/ else( prev.setOne_next(node); node.setOne_next(end) ; ) ) ) public void display())( System.out.println("--Truyền tải đơn giản--"); Node temp=start.getOne_next(); while(temp != end)( System.out.print( temp.getData()+"=>"); temp=temp.getOne_next(); ) ) public void displayFast())( System.out.println("--Fast Lane Traversal--"); Node temp=start.getTwo_next(); while(temp !=end)( System.out.print(temp . getData()+"==>"); temp=temp.getTwo_next(); ) ) )

Phần kết luận:

Bỏ qua đơn giản -

1 = > 2 = > 3 = > 4 = > 5 = > 6 = >

Chuyển tiếp nhanh một bản nhạc -

2 == > 4 == > 6 == >

Khi bạn tạo ConcurrentSkipListSet, bạn chuyển một bộ so sánh cho hàm tạo.

Bộ danh sách bỏ qua đồng thời mới<>(Ví dụ mới()); lớp công khai Ví dụComparator thực hiện Trình so sánh (// ý định của bạn)

Bạn có thể tạo một bộ so sánh để làm cho SkipListSet của bạn hoạt động giống như một danh sách thông thường.

Tôi không khẳng định rằng đây là việc thực hiện của riêng tôi. Tôi chỉ không thể nhớ mình đã tìm thấy nó ở đâu. Nếu bạn biết, hãy cho tôi biết và tôi sẽ cập nhật. Công việc này phù hợp với tôi:

Lớp công khai SkipList > thực hiện Iterable ( Nút _head = Nút mới<>(không, 33); riêng tư cuối cùng Random Rand = new Random(); int riêng _levels = 1; kích thước AtomicInteger riêng tư = new AtomicInteger(0); ///

/// Chèn một giá trị vào danh sách bỏ qua. /// public void Insert(T value) ( ​​// Xác định cấp độ của nút mới. Tạo số ngẫu nhiên R. // số // 1-bit trước khi chúng ta gặp 0-bit đầu tiên là cấp độ của nút . / / Vì R là // 32-bit nên cấp độ tối đa có thể là 32. int level = 0;size.incrementAndGet();for (int R = rand.nextInt(); (R & 1) == 1 ; R >>= 1) ( level++; if (level == _levels) ( _levels++; break; ) ) // Chèn nút này vào danh sách bỏ qua Nút newNode = Nút mới<>(giá trị, cấp độ + 1); Nút cur = _head; for (int i = _levels - 1; i >= 0; i--) ( for (; cur.next[i] != null; cur = cur.next[i]) ( if (cur.next[i] .getValue().compareTo(value) > 0) break; ) if (i<= level) { newNode.next[i] = cur.next[i]; cur.next[i] = newNode; } } } /// /// Trả về xem một giá trị cụ thể đã tồn tại trong danh sách bỏ qua hay chưa /// boolean công khai chứa(giá trị T) ( ​​Node cur = _head; for (int i = _levels - 1; i >= 0; i--) ( for (; cur.next[i] != null; cur = cur.next[i]) ( if (cur.next[i] .getValue().compareTo(value) > 0) break;if (cur.next[i].getValue().compareTo(value) == 0) trả về true; ) ) trả về false; ) /// /// Cố gắng loại bỏ một lần xuất hiện của một giá trị cụ thể khỏi danh sách bỏ qua ///. Trả về /// liệu giá trị có được tìm thấy trong danh sách bỏ qua hay không. /// loại bỏ boolean công khai(giá trị T) ( ​​Node cur = _head; tìm thấy boolean = sai; for (int i = _levels - 1; i >= 0; i--) ( for (; cur.next[i] != null; cur = cur.next[i]) ( if (cur.next[i] .getValue().compareTo(value) == 0) ( Found = true; cur.next[i] = cur.next[i].next[i]; break; ) if (cur.next[i].getValue ().compareTo(value) > 0) break; ) ) if (tìm thấy) size.decrementAndGet(); tìm thấy trở lại; ) @SuppressWarnings(( "rawtypes", "unchecked")) @Override public Iterator iterator() ( return new SkipListIterator(this, 0); ) public int size() ( return size.get(); ) public Double toArray() ( Double a = new Double; int i = 0; for (T t: this) ( a[i] = (Double) t; i++; ) return a; ) ) lớp Nút > ( Nút công cộng Kế tiếp; giá trị N công khai; @SuppressWarnings("unchecked") Nút công khai(giá trị N, mức int) ( this.value = value; next = Nút mới; ) N công khai getValue() ( giá trị trả về; ) Nút công khai getNext() ( return next; ) Nút công khai getNext(int cấp) ( return next; ) public void setNext(Node tiếp theo) ( this.next = next; ) ) lớp SkipListIterator > triển khai Iterator ( Danh sách bỏ qua danh sách; Nút hiện hành; mức int; công khai SkipListIterator(SkipList list, int level) ( this.list = list; this.current = list._head; this.level = level; ) public boolean hasNext() ( return current.getNext(level) != null; ) public E next() ( current = current.getNext(level); return current.getValue(); ) public void delete() ném ra ngoại lệ UnsupportedOperationException ( ném new UnsupportedOperationException(); ) )

Đã sửa lỗi trong quá trình triển khai do @PoweredByRice cung cấp. Nó ném ra một NPE trong trường hợp nút từ xa là nút đầu tiên. Các cập nhật khác bao gồm tên biến được đổi tên và in ngược thứ tự danh sách bỏ qua.

Nhập java.util.Random; giao diện SkipableList > ( int LEVELS = 5; xóa boolean(mục tiêu T); void print(); void chèn(dữ liệu T); SkipNode tìm kiếm(dữ liệu T); ) lớp SkipNode > ( Dữ liệu N; @SuppressWarnings("unchecked") SkipNode tiếp theo = (Bỏ quaNode ) SkipNode mới; SkipNode(N data) ( this.data = data; ) void RefreshAfterDelete(int level) ( SkipNode hiện tại = cái này; while (current != null && current.getNext(level) != null) ( if (current.getNext(level).data == null) ( SkipNode người kế nhiệm = current.getNext(level).getNext(level); current.setNext(người kế nhiệm, cấp độ); trở lại; ) current = current.getNext(level); ) ) void setNext(SkipNode tiếp theo, cấp độ int) ( this.next = next; ) SkipNode getNext(int level) ( return this.next; ) SkipNode search(N data, int level, boolean print) ( if (print) ( System.out.print("Đang tìm kiếm: " + data + " at "); print(level); ) SkipNode kết quả = null; SkipNode hiện tại = this.getNext (cấp độ); while (current != null && current.data.compareTo(data)< 1) { if (current.data.equals(data)) { result = current; break; } current = current.getNext(level); } return result; } void insert(SkipNodeSkipNode, cấp độ int) ( SkipNode hiện tại = this.getNext (cấp độ); if (current == null) ( this.setNext(skipNode, level); return; ) if (skipNode.data.compareTo(current.data)< 1) { this.setNext(skipNode, level); skipNode.setNext(current, level); return; } while (current.getNext(level) != null && current.data.compareTo(skipNode.data) < 1 && current.getNext(level).data.compareTo(skipNode.data) < 1) { current = current.getNext(level); } SkipNodengười kế nhiệm = current.getNext(level); current.setNext(skipNode, cấp độ); SkipNode.setNext(người kế nhiệm, cấp độ); ) void print(int level) ( System.out.print("level " + level + ": [ "); int length = 0; SkipNode hiện tại = this.getNext (cấp độ); while (current != null) ( length++; System.out.print(current.data + " "); current = current.getNext(level); ) System.out.println("], length: " + length); ) ) lớp công khai SkipList > triển khai SkipableList ( SkipNode cuối cùng riêng tư đầu = SkipNode mới<>(vô giá trị); riêng tư cuối cùng Random Rand = new Random(); @Override public void chèn(T data) ( SkipNode SkipNode = SkipNode mới<>(dữ liệu); vì (int i = 0; tôi< LEVELS; i++) { if (rand.nextInt((int) Math.pow(2, i)) == 0) { //insert with prob = 1/(2^i) insert(skipNode, i); } } } @Override public boolean delete(T target) { System.out.println("Deleting " + target); SkipNodenạn nhân = tìm kiếm(mục tiêu, đúng); if (nạn nhân == null) trả về sai; nạn nhân.data = null; vì (int i = 0; tôi< LEVELS; i++) { head.refreshAfterDelete(i); } System.out.println("deleted..."); return true; } @Override public SkipNodesearch(T data) ( return search(data, true); ) @Override public void print() ( for (int i = LEVELS-1; i >= 0 ; i--) ( head.print(i); ) System.out.println(); ) chèn khoảng trống riêng tư(SkipNode SkipNode, int level) ( head.insert(SkipNode, level); ) riêng tư SkipNode tìm kiếm (dữ liệu T, in boolean) ( SkipNode kết quả = null; for (int i = LEVELS-1; i >= 0; i--) ( if ((result = head.search(data, i, print)) != null) ( if (print) ( System.out.println ("Đã tìm thấy " + data.toString() + " ở cấp độ " + i + ", nên đã dừng"); System.out.println(); ) break; ) ) trả về kết quả; ) public static void main(String args) ( SkipList sl = SkipList mới<>(); dữ liệu int = (4,2,7,0,9,1,3,7,3,4,5,6,0,2,8); for (int i: data) ( sl.insert(i); ) sl.print(); sl.search(4); sl.delete(4); System.out.println("Chèn 10"); sl.insert(10); sl.print(); sl.search(10); ) )