Java 심층 기사 ~ 04. List 데이터 구조 구현 (JDK1.8)

Java 심층 기사 ~ 04. List 데이터 구조 구현 (JDK1.8)

이전 기사

머리말

       일반적으로 프로그램은 항상 실행 후에 만 ​​알려진 특정 조건을 기반으로 개체를 만듭니다. 그 전에는 필요한 개체의 수나 개체 유형도 알 수 없었습니다. 이 문제를 해결하려면 언제 어디서나 필요한 개체를 만들어야합니다. 이런 식으로 어레이는 확실히 작동하지 않습니다. 배열의 크기가 고정되어 있고 변경할 수 없기 때문입니다. 따라서 대부분의 프로그래밍 언어에는 문제를 해결할 수있는 방법이 있습니다. 예를 들어 C ++에는 STL이 있고 Java에는 컨테이너 클래스 집합이 있습니다.

       C ++ STL 컨테이너

       기본 유형의 컬렉션에는 각각 List, Set, Map 및 Queue가 있습니다. 컨테이너는 객체를 저장하는 매우 완벽한 방법을 제공하며이를 사용하여 많은 문제를 해결할 수 있습니다.

명부

       List는 특정 순서로 요소를 유지할 수 있으며, List 인터페이스는 Collection을 기반으로 많은 메소드를 추가하여 List 중간에 요소를 삽입 및 제거 할 수 있습니다.

       과거에는 List 데이터 구조와 관련된 블로그도 작성했습니다. 누구나 클릭하여 볼 수 있습니다 ~ hehe

       선형 테이블의 기본 구현 및 개념

       시퀀스 테이블 작업

       이중 연결 목록 운영

       스택 및 큐의 기본 개념

       시퀀스 스택 및 체인 스택

       사용자 지정 스택을 사용하여 이진 트리의 순회 최적화

목록 유형

       기본 주문 저장 : ArrayList. 이름에서 알 수 있듯이 이것은 List의 배열이고 맨 아래 계층은 동적 배열이며 데이터 구조는 시퀀스 테이블과 유사합니다. 기본 원칙은 위에서 언급 한 StringBuilder 와 유사 하지만 또한 다릅니다. ArrayList의 장점은 랜덤 액세스의 효율성이 높다는 것입니다. 그러나 중간 삽입 및 삭제는 약간 느립니다. 구체적인 이유는 블로그 "Operation of Sequence Table"에 언급되어 있습니다.

       체인 데이터 저장소 : LinkedList. 그것의 장점은 삽입 및 삭제의 효율성이 높지만 중간에있는 모든 요소에 대한 액세스에는 순회가 필요하므로 임의 액세스의 효율성은 ArrayList만큼 좋지 않습니다. 에서 선형 형태의 기본 개념 달성 내부의 특정 이유를.

ArrayList

       ArrayList의 맨 아래 계층은 동적 배열이며 처음 새로 만들 때 크기가 10 인 빈 목록이 생성됩니다.

	public Arst() {
    
    
	        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
	    }

       ArrayList의 배열 용량이 충분하지 않을 경우 배열의 길이가 확장됩니다. StringBuilder와의 차이점은 매번 StringBuilder의 확장 량이 현재 길이의 두 배라는 것입니다. ArrayList는 현재 확장 길이의 0.5 배입니다. 따라서 리소스 측면에서 매번 ArrayList의 추가 용량은 StringBuilder의 용량보다 적습니다. ArrayList의 생성 방법은 초기 크기를 수동으로 설정할 수도 있습니다. 사용자가 ArrayList를 사용하기 전에 저장할 최소 데이터 양을 이미 알고있는 경우 초기 길이를 최소값으로 설정하여 확장 횟수를 줄이고 효율성을 미묘하게 향상시킬 수 있습니다.

	private void grow(int minCapacity) {
    
    
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

추가 방법

       ArrayList의 add 메서드는 끝에 직접 요소를 추가하거나 인덱스를 기반으로 요소를 추가 할 수 있습니다. 먼저 용량이 충분한 지 판단하고, 용량이 부족하면 확장하고, 용량이 충분하면 어레이의 인덱스 위치 바로 뒤에 추가하는 과정입니다. ArrayList는 StringBuilder와 동일하며 배열의 길이는 컨테이너의 길이와 동일하지 않습니다. 색인을 기반으로 요소를 추가 할 때 일부 사람들은 루프를 생각할 수 있습니다. 그러나 효율성을 위해 System.arraycopy를 사용하여 저장해야하는 값을 저장할 지정된 위치의 공간을 확보 할 수 있습니다.

꼬리 삽입
	public boolean add(E e) {
    
    
	        ensureCapacityInternal(size + 1);
	        elementData[size++] = e;
	        return true;
	    }
색인 삽입
	public void add(int index, E element) {
    
    
	        rangeCheckForAdd(index);
	
	        ensureCapacityInternal(size + 1);  // Increments modCount!!
	        System.arraycopy(elementData, index, elementData, index + 1,
	                         size - index);
	        elementData[index] = element;
	        size++;
	    }

제거 방법

       remove 메서드는 인덱스를 기반으로 요소를 삭제하거나 객체를 기반으로 요소를 삭제할 수 있습니다. 인덱스를 사용하여 요소를 삭제하면 인덱스 뒤의 요소가 왼쪽으로 이동하여 덮습니다. 개체를 기반으로 한 요소를 삭제하면 해당 개체와 동일한 첫 번째 요소가 삭제됩니다. 즉, 'A'요소를 삭제한다고 가정합니다. 이 컨테이너에 'A'가 여러 개 있어도 마지막에 첫 번째 A 만 삭제되며, 작동 원리는 루프를 사용하여 색인을 찾은 다음 색인 삭제 체계를 사용하는 것입니다. 따라서 개체 삭제의 효율성은 인덱스 삭제보다 낮습니다.

색인 삭제
	public E remove(int index) {
    
    
	        rangeCheck(index);
	
	        modCount++;
	        E oldValue = elementData(index);
	
	        int numMoved = size - index - 1;
	        if (numMoved > 0)
	            System.arraycopy(elementData, index+1, elementData, index,
	                             numMoved);
	        elementData[--size] = null; 
	
	        return oldValue;
	    }
개체 삭제
	public boolean remove(Object o) {
    
    
	        if (o == null) {
    
    
	            for (int index = 0; index < size; index++)
	                if (elementData[index] == null) {
    
    
	                    fastRemove(index);
	                    return true;
	                }
	        } else {
    
    
	            for (int index = 0; index < size; index++)
	                if (o.equals(elementData[index])) {
    
    
	                    fastRemove(index);
	                    return true;
	                }
	        }
	        return false;
	    }

get 메소드

       ArrayList의 get 메소드는 ArrayList의 맨 아래 계층이 배열이기 때문에 할 말이 없으며, 인덱스가 유효하면 배열을 직접 반환 할 수 있습니다.

	E elementData(int index) {
    
    
	        return (E) elementData[index];
	    }

indexOf 메서드

       이 방법은 주로 루프를 사용하여 탐색 한 다음 비교하고 객체에 따라 첫 번째 인덱스를 찾는 것입니다.

	public int indexOf(Object o) {
    
    
	        if (o == null) {
    
    
	            for (int i = 0; i < size; i++)
	                if (elementData[i]==null)
	                    return i;
	        } else {
    
    
	            for (int i = 0; i < size; i++)
	                if (o.equals(elementData[i]))
	                    return i;
	        }
	        return -1;
	    }

LinkedList

       LinkedList는 체인 스토리지 결과이고 하단 레이어는 이중 링크 목록입니다. 링크드리스트의 노드는 메모리 어디에나 흩어져있을 수 있으며, 링크드리스트에 대한 모든 노드가 필요로하는 공간을 한꺼번에 나눌 필요가없고 수요에 따라 일시적으로 나눌 필요가 없습니다. 따라서 연결 목록은 저장 공간의 동적 할당을 지원합니다. 블로그 "선형 테이블의 기본 개념"에서 소개했습니다. 따라서 LinkedList 개체를 처음 만들 때 ArrayList와 다릅니다. LinkedList는 객체가 처음 새 것이면 비어 있습니다.

추가 방법

       ArrayList와 동일합니다. add 메서드는 꼬리에 요소를 직접 추가하거나 색인을 기반으로 요소를 추가 할 수 있습니다. 인덱스에 따라 요소를 추가 할 때 더 이상 인덱스를 찾고 배열과 같은 장소를 만들 필요가 없지만 포인터를 직접 변경할 수 있습니다. C 언어를 배운 사람들이 이해하는 것이 더 쉬울 수 있습니다. 인덱스가 추가되면 인덱스의 위치가 마지막이되고 마지막에 추가하는 방식이 호출됩니다. 그렇지 않은 경우 해당 인덱스의 이전 위치를 찾아 이전 위치의 다음 위치에 추가합니다.

끝에 추가
	void linkLast(E e) {
    
    
	        final Node<E> l = last;
	        final Node<E> newNode = new Node<>(l, e, null);
	        last = newNode;
	        if (l == null)
	            first = newNode;
	        else
	            l.next = newNode;
	        size++;
	        modCount++;
	    }
	private static class Node<E> {
    
    
	        E item;
	        Node<E> next;
	        Node<E> prev;
	
	        Node(Node<E> prev, E element, Node<E> next) {
    
    
	            this.item = element;
	            this.next = next;
	            this.prev = prev;
	        }
	    }
색인 추가
	public void add(int index, E element) {
    
    
	        checkPositionIndex(index);
	
	        if (index == size)
	            linkLast(element);
	        else
	            linkBefore(element, node(index));
	    }
	void linkBefore(E e, Node<E> succ) {
    
    
	        // assert succ != null;
	        final Node<E> pred = succ.prev;
	        final Node<E> newNode = new Node<>(pred, e, succ);
	        succ.prev = newNode;
	        if (pred == null)
	            first = newNode;
	        else
	            pred.next = newNode;
	        size++;
	        modCount++;
	    }

제거 방법

       LinkedList에서 remove에는 매개 변수가없는 솔루션이 있으며 해당 솔루션은 첫 번째 요소를 직접 삭제하는 것입니다. 링크드리스트 삭제의 원칙은 이전 요소의 포인터를 요소의 다음 포인터로 향하는 것이기 때문에 ( 단일 링크드리스트의 동작에서 언급 했음 ) 중간에 잡힌 것이 해제됩니다.하지만 헤드 요소를 삭제하고 싶을 때 때때로 이런 종류의 작업을 수행 할 수 없으므로 Java에서 다른 솔루션이 채택됩니다. 요소를 삭제하는 객체는 루프에서 인덱스를 찾은 다음 인덱싱 솔루션을 사용하여 요소를 삭제하는 것입니다.

헤더 요소 삭제
	private E unlinkFirst(Node<E> f) {
    
    
	        final E element = f.item;
	        final Node<E> next = f.next;
	        f.item = null;
	        f.next = null; 
	        first = next;
	        if (next == null)
	            last = null;
	        else
	            next.prev = null;
	        size--;
	        modCount++;
	        return element;
	    }
인덱스 삭제 요소
	E unlink(Node<E> x) {
    
    
	        // assert x != null;
	        final E element = x.item;
	        final Node<E> next = x.next;
	        final Node<E> prev = x.prev;
	
	        if (prev == null) {
    
    
	            first = next;
	        } else {
    
    
	            prev.next = next;
	            x.prev = null;
	        }
	
	        if (next == null) {
    
    
	            last = prev;
	        } else {
    
    
	            next.prev = prev;
	            x.next = null;
	        }
	
	        x.item = null;
	        size--;
	        modCount++;
	        return element;
	    }
개체 삭제 요소
	public boolean remove(Object o) {
    
    
	        if (o == null) {
    
    
	            for (Node<E> x = first; x != null; x = x.next) {
    
    
	                if (x.item == null) {
    
    
	                    unlink(x);
	                    return true;
	                }
	            }
	        } else {
    
    
	            for (Node<E> x = first; x != null; x = x.next) {
    
    
	                if (o.equals(x.item)) {
    
    
	                    unlink(x);
	                    return true;
	                }
	            }
	        }
	        return false;
	    }

get 메소드

       링크드리스트의 장점은 추가 및 삭제시 순회없이 수행 할 수 있고 포인터 만 변경하면된다는 점이지만, 단점은 임의의 요소에 액세스하려면 해당 요소를 반복해야한다는 것입니다. 이 점에서 효율성은 ArrayList보다 낮습니다.

	Node<E> node(int index) {
    
    
	        if (index < (size >> 1)) {
    
    
	            Node<E> x = first;
	            for (int i = 0; i < index; i++)
	                x = x.next;
	            return x;
	        } else {
    
    
	            Node<E> x = last;
	            for (int i = size - 1; i > index; i--)
	                x = x.prev;
	            return x;
	        }
	    }

스택

       스택은 벡터를 상속합니다. Vector는 또한 List 인터페이스 아래의 구현 클래스이며 그 원리는 ArrayList를 사용하는 거의 동적 배열입니다. 차이점은 Vector의 스레드가 더 동기화되고 ArrayList는 그렇지 않다는 것입니다. 따라서 Vector는 스레드로부터 안전하지만 비효율적 인 반면 ArrayList는 스레드로부터 안전하지만 효율적입니다. 벡터는 거의 사용되지 않습니다.

       스택도 스택입니다. 스택은 한쪽 끝에서만 삽입하거나 삭제할 수있는 선형 테이블입니다. 이 중 삽입 또는 삭제가 가능한 끝은 스택의 상단 (TOP)입니다. 스택의 맨 위는 맨 위 포인터라는 위치 표시기로 표시됩니다. 동적으로 변경됩니다. 테이블의 다른 쪽 끝은 스택의 맨 아래이고 스택의 맨 아래는 고정됩니다. 스택 삽입 및 삭제 작업을 일반적으로 스택 및 팝이라고합니다. 스택의 정의에서 스택의 주요 기능이 선입 선출 (first in, last out)임을 알 수 있습니다.

       스택은 과거 데이터 구조 연구에서 여러 번 사용되었습니다. 이진 트리를 탐색해야 할 때 스택을 사용하여 이진 트리를 탐색하는 것이 재귀보다 훨씬 효율적입니다. 연결된 목록 반전을 달성해야 할 때 스택의 선입 선출 기능은 매우 편리 할 수 ​​있습니다.

       실제로 스택은 실제로 제한된 선형 목록입니다. 다음 세 가지 기사는 스택의 기본 개념과 구현이므로 여기서 반복하지 않겠습니다.

       스택 및 큐의 기본 개념

       시퀀스 스택 및 체인 스택

       사용자 지정 스택을 사용하여 이진 트리의 순회 최적화

추천

출처blog.csdn.net/qq_41424688/article/details/108680434