많은 데이터 구조에서 삽입보다 삭제가 구현하기가 더 어려운 이유는 무엇입니까?


33

많은 (대부분의) 데이터 구조에 대한 삽입보다 삭제가 구현하기가 훨씬 어려운 특정 이유를 생각할 수 있습니까?

빠른 예 : 링크 된 목록. 삽입은 사소한 것이지만 삭제에는 몇 가지 특별한 경우가있어 상당히 어려워집니다. AVL 및 Red-black과 같은 자체 균형 이진 검색 트리는 고통스러운 삭제 구현의 전형적인 예입니다.

나는 그것이 대부분의 사람들이 생각하는 방식과 관련이 있다고 말하고 싶습니다 : 우리가 건설적으로 물건을 정의하는 것이 더 쉬워서 쉽게 삽입 할 수 있습니다.


4
무엇에 대해 pop, extract-min?
coredump

5
"구현하는 것이 어렵다"는 프로그래밍 (데이터 구조와 알고리즘의 속성)보다 심리학 (인식의인지와 강점과 약점)의 문제이다.
outis

1
코어 덤프가 언급 한 것처럼 스택은 추가만큼 삭제하기가 쉬워야합니다. 정렬). 또한 삽입이 빈번하고 삭제가 적다고 가정되는 사용 사례가 있지만 삭제 수가 삽입을 초과하는 매우 마술적인 데이터 구조가 될 것입니다. [1] 메모리 누수를 피하기 위해 현재 보이지 않는 개체에 대한 참조를 무효화해야합니다. Liskov의 교과서가 없었기 때문에 기억합니다
Foon

43
"잠깐만 요,이 샌드위치에 마요네즈를 더 추가해 주시겠습니까?" "물론 문제 없습니다." "겨자도 모두 제거 할 수 있을까요?" "Uh ......"
cobaltduck

3
뺄셈이 덧셈보다 더 복잡한 이유는 무엇입니까? 곱셈보다 더 복잡한 나눗셈 (또는 소인수 분해)? 지수보다 뿌리가 더 복잡합니까?
mu는

답변:


69

그것은 단지 마음의 상태 이상입니다. 삭제가 더 어려운 물리적 (즉, 디지털) 이유가 있습니다.

삭제하면 예전의 위치에 구멍이 생깁니다. 결과 엔트로피의 기술 용어는 "조각화"입니다. 연결된 목록에서 제거 된 노드를 "패치"하고 사용중인 메모리를 할당 해제해야합니다. 이진 트리에서는 트리의 균형이 맞지 않습니다. 메모리 시스템에서 새로 할당 된 블록이 삭제로 남겨진 블록보다 큰 경우 메모리가 잠시 동안 사용되지 않습니다.

간단히 말해서 삽입 할 위치를 선택하기 때문에 삽입이 더 쉽습니다. 어떤 항목이 삭제 될지 미리 예측할 수 없으므로 삭제가 더 어렵습니다.


3
조각화는 메모리 내 또는 다이어그램 구조에서 포인터 및 간접 지시가 작동하는 문제가 아닙니다. 인 메모리에서는 간접 노드로 인해 개별 노드가 어디에 있는지는 중요하지 않습니다. 목록의 경우 내부 노드 (다이어그램에 구멍이있는 위치)를 삭제하면 삽입보다 약간 적은 작업이 필요합니다 (1 포인터 할당 및 1 자유 할당 대 1 할당 및 2 포인터 할당). 트리의 경우 노드를 삽입하면 삭제만큼 트리의 균형을 맞출 수 있습니다. 조각화가 중요하지 않은 브리토가 언급하는 어려움을 일으키는 것은 가장 중요한 경우입니다.
outis

12
삽입과 삭제가 예측 가능성이 다르다는 데 동의하지 않습니다. 목록 노드를 "패치하는 것"은 동일한 노드가 대신 삽입되는 경우와 반대로 발생합니다. 어느 시점에서든 어느 방향 으로든 불확실성이 없으며 요소에 본질적인 구조가없는 컨테이너 (예 : 균형 이진 트리, 요소 오프셋 사이의 엄격한 관계가있는 배열)에는 "구멍"이 전혀 없습니다. 따라서, 나는 당신이 여기서 무엇을 말하는지 모르겠습니다.
sqykly

2
매우 흥미롭지 만 논쟁이 빠졌다고 말하고 싶습니다. 단순 / 빠른 삭제를 중심으로 데이터 구조를 문제없이 구성 할 수 있습니다. 그것은 덜 일반적이며, 아마도 덜 유용합니다.
luk32

@sqykly 중간 삽입과 중간 관계가 똑같이 어렵 기 때문에 목록이 잘못된 선택 예라고 생각합니다. 하나는 다른 하나는 재 할당 된 메모리를 할당합니다. 하나는 구멍을 열고 다른 하나는 구멍을 밀봉합니다. 따라서 모든 경우가 add보다 복잡한 것은 아닙니다.
ydobonebi

36

삽입하는 것보다 삭제하기 어려운 이유는 무엇입니까? 데이터 구조는 삭제보다는 삽입을 염두에두고 설계되었습니다.

이것을 고려하십시오-데이터 구조에서 무언가를 삭제하려면 처음에 있어야합니다. 따라서 먼저 추가해야합니다. 즉, 삽입 할 때만 큼 삭제가 많이 발생합니다. 삽입을 위해 데이터 구조를 최적화하면 삭제에 최적화 된 것처럼 최소한의 이점을 얻을 수 있습니다.

또한 각 요소를 순차적으로 삭제하는 데 어떤 용도가 있습니까? 한 번에 모두 지우는 함수를 호출하는 것이 어떻습니까? 또한 데이터 구조는 실제로 무언가를 포함 할 때 가장 유용합니다. 따라서 삽입만큼 삭제가 많은 경우 실제로는 일반적이지 않습니다.

무언가를 최적화 할 때 가장 많이하는 작업과 가장 많은 시간이 걸리는 작업을 최적화하려고합니다. 일반적인 사용법에서 데이터 구조의 요소 삭제는 삽입보다 자주 발생하지 않습니다.


4
내가 상상할 수있는 하나의 유스 케이스가 있습니다. 초기 삽입과 개별 소비를 위해 준비된 데이터 구조. 물론 그것은 드문 경우이며 알고리즘 적으로 그리 흥미롭지는 않습니다. 왜냐하면 말했듯이 그러한 연산은 삽입을 무의식적으로 지배 할 수 없기 때문입니다. 배치 삽입이 비용을 상당히 좋게하고 삭제를 위해 빠르고 간단하게 할 수 있다는 희망이있을 수 있으므로, 복잡하지만 실용적인 배치 삽입과 간단하고 빠른 개별 삭제가있을 수 있습니다. 확실히 매우 드문 실제적인 필요.
luk32

1
음, 예는 역순 벡터 일 수 있다고 생각합니다. k요소를 매우 빠르게 추가 할 수 있습니다 : 역 정렬 입력 및 기존 벡터와 병합- O(k log k + n). 그런 다음 삽입이 상당히 복잡한 구조이지만 최상위 u요소를 사용하는 것이 간단하고 빠릅니다. 마지막 u으로 벡터의 끝을 이동하십시오. 그러나 누군가가 그런 것을 필요로한다면 나는 저주받을 것입니다. 나는 이것이 당신의 주장을 적어도 강화하기를 바랍니다.
luk32

가장 많이하는 것이 아니라 평균적인 사용 패턴에 맞게 최적화하고 싶지 않습니까?
Shiv

간단한 FIFO 작업 대기열은 일반적으로 대부분 비어 있습니다. 잘 디자인 된 큐는 삽입과 삭제 모두에 대해 잘 최적화됩니다 (예 : O (1)). 그리고 아주 좋은 큐는 빠른 동시 작업을 지원하지만 다른 문제입니다.
Kevin

6

어렵지 않습니다.

이중 연결 목록을 사용하면 삽입 할 때 메모리를 할당 한 다음 헤드 또는 이전 노드, 꼬리 또는 다음 노드와 연결됩니다. 삭제하면 정확히 동일한 연결을 해제 한 다음 메모리를 비 웁니다. 이 모든 작업은 대칭입니다.

이것은 두 경우 모두 삽입 / 삭제할 노드가 있다고 가정합니다. (삽입의 경우, 이전에 삽입 할 노드가 있으므로 삽입이 약간 더 복잡한 것으로 생각 될 수 있습니다.) 삭제할 노드가 아닌 페이로드 를 삭제하려고하면 물론 페이로드에 대한 목록을 먼저 검색해야하지만 삭제의 단점은 아닙니다. 그렇지 않습니까?

균형 잡힌 나무도 마찬가지입니다. 나무는 일반적으로 삽입 직후와 삭제 직후에 균형을 유지해야합니다. 하나의 밸런싱 루틴 만 시도하고 삽입 또는 삭제 여부에 관계없이 각 작업 후에 적용하는 것이 좋습니다. 항상 트리 균형을 유지하는 삽입과 두 트리가 동일한 균형 조정 루틴을 공유하지 않고 항상 트리 균형을 유지하는 삭제를 구현하려고하면 불필요하게 인생이 복잡해집니다.

요컨대, 하나가 다른 하나보다 더 어려워 야 할 이유가 없으며, 그것이 사실이라면, 당신이 생각하는 것이 더 자연스러워지는 (매우 인간적인) 경향의 희생자 일 가능성이 있습니다. 빼기보다 건설적으로, 즉, 필요한 것보다 복잡한 방식으로 삭제를 구현할 수 있음을 의미합니다. 그러나 그것은 인간의 문제입니다. 수학적 관점에서는 문제가 없습니다.


1
동의하지 않아야합니다. AVL 삭제 알고리즘은 삽입보다 복잡합니다. 특정 노드 삭제의 경우 전체 트리를 재조정해야 할 수도 있습니다.이 트리는 일반적으로 재귀 적으로 수행되지만 비재 귀적으로 수행 될 수도 있습니다. 삽입을 위해이 작업을 수행 할 필요는 없습니다. 모든 트리 리 밸런싱을 모든 경우에 피할 수있는 알고리즘 개선 사항을 알지 못합니다.
Dennis

@Dennis : AVL 트리가 규칙이 아닌 예외를 따를 수 있습니다.
outis

@ outis IIRC, 모든 균형 검색 트리에는 삽입보다 복잡한 삭제 루틴이 있습니다.
Raphael

무엇에 대한 폐쇄 해시 해시 테이블? 삽입은 (상대적으로) 간단합니다. "인덱스 X에 있어야 할 것은 현재 인덱스 Y에 있고 우리는 찾아서 다시 넣어야합니다." 문제.
Kevin

3

런타임 측면 에서 Wikipedia 의 데이터 구조 작업 시간 복잡도 비교 를 살펴보면 삽입 및 삭제 작업의 복잡도는 동일합니다. 삭제 된 구조 요소에 대한 참조가있는 색인에 의한 삭제가 프로파일 링 된 삭제 조작입니다. 항목 별 삽입입니다. 실제로 삭제 시간이 길어지면 일반적으로 색인이 아닌 삭제할 항목이 있으므로 찾기 작업이 필요하기 때문입니다. 배치 위치가 항목에 의존하지 않거나 삽입 중에 위치가 암시 적으로 결정되므로 테이블의 대부분의 데이터 구조에는 삽입에 대한 추가 찾기가 필요하지 않습니다.

인지 복잡성에 관해서는 질문에 대한 답이 있습니다. 삭제는 삽입보다 더 많은 것을 가질 수 있습니다 (일반적인 경우 아직 설정되지 않았습니다). 그러나 특정 설계에서는 이러한 에지 사례 중 적어도 일부를 피할 수 있습니다 (예 : 링크 된 목록에 센티넬 노드가 있음).


2
"대부분의 데이터 구조는 삽입물을 찾을 필요가 없습니다." -같은? 사실 반대 주장을 할 것입니다. (삽입 위치를 "찾습니다". 나중에 같은 요소를 다시 찾는 것만 큼 비쌉니다.)
Raphael

@Raphael :이 답변은 링크 된 작업 복잡성의 컨텍스트에서 읽어야합니다. 여기에는 찾기 작업이 삭제의 일부로 포함되지 않습니다. 귀하의 질문에 대한 답변으로, 나는 일반적인 이름으로 구조를 분류했습니다. 배열, 목록, 트리, 해시 테이블, 스택, 큐, 힙 및 세트 중에서 트리 및 세트는 삽입을 찾아야합니다. 다른 항목은 항목에 연결되지 않은 색인을 사용하거나 (기본 스택, 대기열 및 힙의 경우, 하나의 색인 만 노출되고 찾기가 지원되지 않음) 항목에서 계산합니다. 그래프는 사용 방법에 따라 어느 쪽이든 갈 수 있습니다.
outis

... 나무는 나무로 여겨 질 수 있습니다. 그러나 자체 구조로 분류되는 경우 삽입 중에 "찾기"가 있는지 여부는 더 논쟁의 여지가 있으므로 포함시키지 않습니다. 데이터 구조 목록은 인터페이스와 구현을 고려하지 않습니다. 또한 계산 방법은 크게 분류 방법에 따라 다릅니다. 더 객관적인 진술을 생각할 수 있는지 살펴 보겠습니다.
outis

사전 / 설정 인터페이스를 염두에두고 있음을 인정합니다 (CS에서 공통). 어쨌든, 그 표는 여러 곳에서 잘못 이해되고 있으며 (iirc) 심지어 CS 잘못된 정보의 구덩이 인 Wikipedia입니다. : /
Raphael

0

언급 된 모든 문제 외에도 데이터 참조 무결성이 포함됩니다. SQL에서 데이터베이스와 같은 데이터 구조를 가장 적절하게 구축하려면 Oracle 참조 무결성이 매우 중요합니다.
실수로 그것을 발명하지 않은 많은 것들을 파괴하지 않도록하십시오.
예를 들어 삭제시 캐스케이드는 삭제하려는 항목을 삭제할뿐 아니라 관련 데이터 정리를 트리거합니다.
이것은 정크 데이터로부터 데이터베이스를 정리하고 데이터의 무결성을 그대로 유지합니다.
예를 들어 두 번째 테이블에 관련 레코드로 부모와 종류의 테이블이 있습니다.
부모는 메인 테이블입니다. 참조 무결성을 강화하지 않은 경우 모든 테이블에서 레코드를 삭제할 수 있으며 나중에 하위 테이블에 데이터가 있고 상위 테이블에 데이터가 없기 때문에 전체 가족 정보를 얻는 방법을 알 수 없습니다.
따라서 참조 무결성 검사를 통해 하위 테이블의 레코드가 정리 될 때까지 상위 테이블에서 레코드를 삭제할 수 없습니다.
이것이 대부분의 데이터 소스에서 데이터를 삭제하기가 더 어려운 이유입니다.


데이터베이스가 아닌 연결된 목록, 해시 테이블 등과 같은 메모리 내 구조에 대한 질문이 있었지만 참조 무결성은 메모리 내 구조에서도 중요한 문제입니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.