Tại sao xóa thường khó thực hiện hơn nhiều so với chèn trong nhiều cấu trúc dữ liệu?


33

Bạn có thể nghĩ ra bất kỳ lý do cụ thể nào tại sao việc xóa thường khó thực hiện hơn đáng kể so với việc chèn cho nhiều cấu trúc dữ liệu (hầu hết?) Không?

Ví dụ nhanh: danh sách liên kết. Chèn là không đáng kể, nhưng xóa có một vài trường hợp đặc biệt làm cho nó khó khăn hơn đáng kể. Các cây tìm kiếm nhị phân tự cân bằng như AVL và Red-black là những ví dụ kinh điển về việc thực hiện xóa đau đớn.

Tôi muốn nói rằng nó phải làm theo cách mà hầu hết mọi người nghĩ: chúng ta dễ dàng xác định mọi thứ một cách xây dựng hơn, điều này dẫn đến việc chèn vào dễ dàng.


4
Thế còn pop, extract-min?
coredump

5
"Khó thực hiện hơn" là vấn đề tâm lý học (nhận thức và điểm mạnh và điểm yếu của tâm trí con người) hơn là lập trình (tính chất của cấu trúc dữ liệu & thuật toán).
outis

1
Theo tôi nghĩ, coredump đã ám chỉ, các ngăn xếp ít nhất cũng dễ xóa như add (đối với ngăn xếp được hỗ trợ mảng, popping chỉ là một con trỏ giảm dần [1] trong khi việc đẩy có thể yêu cầu sao chép toàn bộ mảng nếu bạn đạt tối đa mảng). Ngoài ra, có một số trường hợp sử dụng trong đó người ta cho rằng việc chèn vào sẽ thường xuyên và xóa ít hơn nhưng đó sẽ là một cấu trúc dữ liệu rất kỳ diệu trong đó số lần xóa vượt quá số lần chèn. [1] Có lẽ bạn cũng nên hủy tham chiếu vô hình bây giờ đến đối tượng được bật để tránh rò rỉ bộ nhớ, điều mà tôi nhớ vì sách giáo khoa của Liskov đã không
Foon

43
"Người phục vụ, bạn có thể vui lòng thêm nhiều mayo vào bánh sandwich này không?" "Chắc chắn, không có vấn đề, thưa ngài." "Bạn cũng có thể loại bỏ tất cả mù tạt?" "Uh ......"
cobaltduck

3
Tại sao phép trừ phức tạp hơn phép cộng? Phép chia (hoặc thừa số nguyên tố) phức tạp hơn phép nhân? Rễ phức tạp hơn lũy thừa?
mu quá ngắn

Câu trả lời:


69

Nó không chỉ là một trạng thái của tâm trí; có những lý do vật lý (tức là kỹ thuật số) tại sao việc xóa khó hơn.

Khi bạn xóa, bạn để lại một cái lỗ nơi một cái gì đó được sử dụng. Thuật ngữ kỹ thuật cho entropy kết quả là "phân mảnh". Trong một danh sách được liên kết, điều này đòi hỏi bạn phải "vá xung quanh" nút bị loại bỏ và giải phóng bộ nhớ mà nó đang sử dụng. Trong cây nhị phân, nó gây ra sự mất cân bằng của cây. Trong các hệ thống bộ nhớ, nó khiến bộ nhớ không được sử dụng trong một thời gian nếu các khối được phân bổ mới lớn hơn các khối bị bỏ lại sau khi xóa.

Nói tóm lại, việc chèn sẽ dễ dàng hơn vì bạn có thể chọn nơi bạn sẽ chèn. Xóa khó hơn vì bạn không thể dự đoán trước mục nào sẽ bị xóa.


3
Sự phân mảnh không phải là một vấn đề trong đó con trỏ và sự gián tiếp xuất hiện, cho cấu trúc trong bộ nhớ hoặc trong sơ đồ. Trong bộ nhớ, không có vấn đề nơi các nút riêng lẻ tồn tại do sự gián tiếp. Đối với danh sách, việc xóa một nút bên trong (đó là nơi bạn có lỗ hổng trong sơ đồ) liên quan đến các thao tác ít hơn một chút so với chèn (gán 1 con trỏ và 1 phân bổ miễn phí so với 1 phân bổ và 2 gán con trỏ). Đối với cây, việc chèn một nút có thể làm mất cân bằng cây cũng giống như xóa. Đó là các trường hợp cạnh gây ra những khó khăn mà brito đề cập đến, trong đó sự phân mảnh không thành vấn đề.
outis

12
Tôi không đồng ý rằng việc chèn và xóa khác nhau về khả năng dự đoán. "Vá xung quanh" một nút danh sách chính xác là những gì xảy ra ngược lại nếu cùng một nút được chèn vào. Không có sự không chắc chắn về bất kỳ hướng nào tại bất kỳ điểm nào và trong bất kỳ vùng chứa nào không có cấu trúc nội tại với các phần tử của nó (ví dụ: cây nhị phân cân bằng, một mảng có mối quan hệ chặt chẽ giữa các phần tử bù) không có "lỗ hổng" nào cả. Vì vậy, tôi sợ tôi không biết bạn đang nói gì ở đây.
sqykly

2
Rất thú vị, nhưng tôi muốn nói rằng các đối số bị bỏ lỡ. Bạn có thể tổ chức các cấu trúc dữ liệu xung quanh việc xóa đơn giản / nhanh chóng mà không gặp vấn đề gì. Nó chỉ ít phổ biến hơn, có lẽ cũng ít hữu ích hơn.
luk32

@sqykly Tôi nghĩ rằng danh sách là ví dụ lựa chọn tồi vì chèn giữa và quan hệ giữa đều khó khăn như nhau. Một trường hợp phân bổ bộ nhớ trong đó phân bổ lại. Một cái mở ra một cái lỗ nơi cái kia niêm phong một cái lỗ. Vì vậy, không phải tất cả các trường hợp là xóa phức tạp hơn add.
ydobonebi

36

Tại sao nó có xu hướng khó xóa hơn là chèn? Cấu trúc dữ liệu được thiết kế nhiều hơn với sự chèn vào trong tâm trí hơn là xóa và đúng như vậy.

Hãy xem xét điều này - để xóa một cái gì đó khỏi cấu trúc dữ liệu, nó phải ở đó ngay từ đầu. Vì vậy, bạn cần thêm nó trước, có nghĩa là nhiều nhất bạn có nhiều lần xóa như bạn đã chèn. Nếu bạn tối ưu hóa cấu trúc dữ liệu để chèn, bạn được đảm bảo nhận được ít nhất lợi ích như thể nó đã được tối ưu hóa để xóa.

Ngoài ra, những gì sử dụng có trong tuần tự xóa từng yếu tố? Tại sao không chỉ gọi một số chức năng xóa tất cả cùng một lúc (có thể chỉ bằng cách tạo một chức năng mới)? Ngoài ra, cấu trúc dữ liệu hữu ích nhất khi chúng thực sự chứa thứ gì đó. Vì vậy, trường hợp có nhiều lần xóa như trong phần chèn, trong thực tế, sẽ không phổ biến lắm.

Khi bạn tối ưu hóa một cái gì đó, bạn muốn tối ưu hóa những thứ mà nó làm nhiều nhất và mất nhiều thời gian nhất. Trong sử dụng bình thường, việc xóa các thành phần của cấu trúc dữ liệu xảy ra ít thường xuyên hơn so với việc chèn.


4
Có một trường hợp sử dụng tôi có thể tưởng tượng. Một cấu trúc dữ liệu được chuẩn bị để chèn ban đầu và sau đó là tiêu dùng riêng lẻ. Tất nhiên đó là một trường hợp hiếm khi, và về mặt thuật toán không thú vị lắm, vì như bạn đã nói, một hoạt động như vậy không thể chi phối việc chèn một cách không có triệu chứng. Có thể có một số hy vọng trong thực tế rằng việc chèn hàng loạt có thể đã khấu hao chi phí khá tốt và nhanh chóng và đơn giản để xóa, do đó, nó sẽ có các thao tác chèn hàng loạt phức tạp nhưng thực tế và xóa từng đơn giản và nhanh chóng. Chắc chắn là một nhu cầu thực tế rất không phổ biến.
luk32

1
Ummm, tôi nghĩ một ví dụ có thể là một vectơ có thứ tự ngược lại. Bạn có thể thêm một loạt kcác phần tử khá nhanh: nhập ngược sắp xếp và hợp nhất với vectơ hiện có - O(k log k + n). Sau đó, bạn có một cấu trúc với chèn khá phức tạp nhưng tiêu thụ ucác yếu tố hàng đầu là tầm thường và nhanh chóng. Chỉ cần thực hiện cuối cùng uvà di chuyển cuối vector. Mặc dù, nếu bất cứ ai cần một thứ như vậy, tôi sẽ bị nguyền rủa. Tôi hy vọng điều này ít nhất củng cố lập luận của bạn.
luk32

Bạn có nên tối ưu hóa cho mẫu sử dụng trung bình hơn là những gì bạn làm nhiều nhất không?
Shiv

Một hàng đợi công việc đơn giản của FIFO thường sẽ cố gắng để trống hầu hết thời gian. Một hàng đợi được thiết kế tốt sẽ được tối ưu hóa tốt (ví dụ O (1)) cho cả chèn và xóa (và một hàng rất tốt cũng sẽ hỗ trợ các hoạt động đồng thời nhanh, nhưng đó là một vấn đề khác).
Kevin

6

Nó không khó hơn.

Với các danh sách được liên kết đôi, khi bạn chèn, bạn sẽ phân bổ bộ nhớ và sau đó bạn sẽ liên kết với đầu hoặc nút trước đó và với đuôi hoặc nút tiếp theo. Khi bạn xóa, bạn sẽ hủy liên kết từ chính xác, và sau đó giải phóng bộ nhớ. Tất cả các hoạt động này là đối xứng.

Điều này giả định rằng trong cả hai trường hợp, bạn có nút để chèn / xóa. (Và trong trường hợp chèn, bạn cũng có nút để chèn trước đó, do đó, theo cách nào đó, việc chèn có thể được coi là phức tạp hơn một chút.) Nếu bạn đang cố gắng xóa không phải là nút để xóa, nhưng tải trọng của nút, tất nhiên trước tiên bạn sẽ phải tìm kiếm danh sách tải trọng, nhưng đó không phải là một sự thiếu sót, phải không?

Với các cây cân bằng, áp dụng tương tự: một cây thường cần cân bằng ngay sau khi chèn và cũng ngay sau khi xóa. Đó là một ý tưởng tốt để thử và chỉ có một thói quen cân bằng, và áp dụng nó sau mỗi thao tác, bất kể đó là chèn hay xóa. Nếu bạn đang cố gắng thực hiện thao tác chèn luôn làm cho cây cân bằng, và cũng là xóa luôn làm cho cây cân bằng, mà không có hai điều kiện chia sẻ cùng một thói quen cân bằng, bạn sẽ làm phức tạp cuộc sống của mình.

Nói tóm lại, không có lý do tại sao một người nên khó hơn người kia, và nếu bạn đang tìm thấy điều đó, thì thực tế có thể bạn là nạn nhân của xu hướng (rất con người) khi thấy nó tự nhiên hơn khi nghĩ xây dựng hơn trừ, có nghĩa là bạn có thể thực hiện xóa theo cách phức tạp hơn mức cần thiết. Nhưng đó là vấn đề của con người. Từ quan điểm toán học, không có vấn đề.


1
Tôi phải không đồng ý. Thuật toán xóa AVL phức tạp hơn chèn. Đối với việc xóa nút nhất định, bạn có thể phải cân bằng lại toàn bộ cây, thường được thực hiện đệ quy nhưng cũng có thể được thực hiện không đệ quy. Bạn không phải làm điều này để chèn. Tôi không nhận thức được các tiến bộ thuật toán trong đó có thể tránh được việc tái cân bằng toàn bộ cây như vậy trong mọi trường hợp.
Dennis

@Dennis: có thể là cây AVL tuân theo ngoại lệ thay vì quy tắc.
outis

@outis IIRC, tất cả các cây tìm kiếm cân bằng có thói quen xóa phức tạp hơn (hơn là chèn).
Raphael

Những gì về bảng băm băm kín ? Việc chèn là (tương đối) đơn giản, việc xóa ít nhất là khó khái niệm hơn vì bạn phải sửa tất cả "điều đáng lẽ phải có ở chỉ số X hiện đang ở chỉ số Y và chúng ta phải đi tìm nó và đưa nó trở lại" các vấn đề.
Kevin

3

Về mặt thời gian chạy, xem xét so sánh độ phức tạp thời gian của hoạt động cấu trúc dữ liệu trên Wikipedia, lưu ý các hoạt động chèn và xóa có cùng độ phức tạp. Thao tác xóa được lược tả có xóa theo chỉ mục, trong đó bạn có một tham chiếu đến thành phần cấu trúc sẽ bị xóa; chèn là theo mục. Thời gian chạy lâu hơn để xóa trong thực tế là vì bạn thường có một mục để xóa và không phải là chỉ mục của nó, vì vậy bạn cũng cần một thao tác tìm. Hầu hết các cấu trúc dữ liệu trong bảng không yêu cầu tìm thêm cho phần chèn vì vị trí vị trí không phụ thuộc vào mục hoặc vị trí được xác định ngầm trong quá trình chèn.

Đối với sự phức tạp về nhận thức, có một câu trả lời trong câu hỏi: trường hợp cạnh. Xóa có thể có nhiều trong số họ hơn chèn (điều này vẫn chưa được thiết lập trong trường hợp chung). Tuy nhiên, ít nhất một số trường hợp cạnh này có thể tránh được trong một số thiết kế nhất định (ví dụ: có nút sentinel trong danh sách được liên kết).


2
"Hầu hết các cấu trúc dữ liệu không yêu cầu tìm kiếm để chèn." -- nhu la? Trên thực tế, tôi đã đưa ra yêu cầu ngược lại. (Bạn "tìm thấy" vị trí chèn, cũng đắt như tìm lại phần tử tương tự sau này.)
Raphael

@Raphael: Câu trả lời này nên được đọc trong ngữ cảnh của bảng phức tạp hoạt động được liên kết, không bao gồm thao tác tìm kiếm như là một phần của việc xóa. Để trả lời câu hỏi của bạn, tôi phân loại cấu trúc theo tên chung. Trong số các mảng, danh sách, cây, bảng băm, ngăn xếp, hàng đợi, đống và bộ, cây và bộ yêu cầu tìm kiếm để chèn; những cái khác sử dụng một chỉ mục không được kết nối với vật phẩm (đối với các ngăn xếp, hàng đợi và đống cơ bản, chỉ có 1 chỉ mục được hiển thị và việc tìm kiếm không được hỗ trợ) hoặc tính toán nó từ vật phẩm. Đồ thị có thể đi theo bất kỳ cách nào, tùy thuộc vào cách chúng được sử dụng.
outis

... Tries có thể được coi là cây; tuy nhiên, nếu được phân loại là cấu trúc của riêng họ, liệu có "tìm thấy" trong khi chèn hay không là vấn đề tranh luận, vì vậy tôi không bao gồm nó. Lưu ý danh sách cấu trúc dữ liệu không đưa giao diện và thực hiện vào tài khoản. Ngoài ra, cách bạn đếm phụ thuộc phần lớn vào cách bạn phân loại. Tôi sẽ xem liệu tôi có thể nghĩ ra một tuyên bố khách quan hơn không.
outis

Tôi sẽ thừa nhận rằng tôi đã có giao diện từ điển / thiết lập (như phổ biến trong CS). Dù sao, bảng đó là sai lệch và (iirc) thậm chí sai ở một số nơi - Wikipedia, hố của thông tin sai lệch CS. : /
Raphael

0

Trên tất cả các vấn đề được đề cập có sự toàn vẹn tham chiếu dữ liệu liên quan. Đối với hầu hết các cấu trúc dữ liệu xây dựng đúng như cơ sở dữ liệu trong SQL, tính toàn vẹn tham chiếu của Oracle là rất quan trọng.
Để đảm bảo rằng bạn không vô tình phá hủy nó, nhiều thứ khác nhau được phát minh ra.
Ví dụ, tầng khi xóa không chỉ xóa những gì bạn từng cố xóa mà còn kích hoạt dọn dẹp dữ liệu liên quan.
Điều này làm sạch cơ sở dữ liệu từ dữ liệu rác cũng như giữ nguyên vẹn dữ liệu.
Ví dụ, bạn có các bảng có bố mẹ và các loại như các bản ghi liên quan trong bảng thứ hai.
Trường hợp cha mẹ là bảng chính. Nếu bạn không củng cố tính toàn vẹn tham chiếu, bạn có thể xóa bất kỳ bản ghi nào trong bất kỳ bảng nào và sau này bạn sẽ không biết cách lấy thông tin gia đình đầy đủ vì bạn có dữ liệu trong bảng con và không có gì trong bảng cha.
Đó là lý do tại sao kiểm tra tính toàn vẹn tham chiếu sẽ không cho phép bạn xóa bản ghi khỏi bảng cha cho đến khi các bản ghi từ bảng con được dọn sạch.
Và đó là lý do tại sao trong hầu hết các nguồn dữ liệu, việc xóa dữ liệu sẽ khó khăn hơn.


Tôi nghĩ rằng câu hỏi đã hỏi về các cấu trúc trong bộ nhớ như danh sách được liên kết, bảng băm, v.v. chứ không phải cơ sở dữ liệu, nhưng tính toàn vẹn tham chiếu là một vấn đề lớn ngay cả với các cấu trúc trong bộ nhớ.
supercat
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.