JavaScript 데이터 구조 및 알고리즘 학습 노트(1부)

1. 스택 구조 구현(Stack)

I. 소개

1.1 데이터 구조가 무엇인지 이해합니까?

데이터 구조는 데이터가 컴퓨터에 저장되고 구성되는 방식입니다.

고려해야 할 주요 사항: 삽입 및 조회.

일반적인 데이터 구조:

배열(배열)

스택

연결된 목록

그래프

해시 테이블(해시)

대기줄

나무

더미

1.2 알고리즘이란?

알고리즘:

제한된 명령어 세트, 각 명령어의 설명은 언어에 의존하지 않습니다.

약간의 입력을 받습니다(어떤 경우에는 입력이 필요하지 않습니다).

입력 생성;

한정된 수의 단계 후에 종료해야 합니다.

일반적인 이해: 문제를 해결하기 위한 방법/단계 논리.

2. 스택 구조(Stack)

2.1 소개

배열은 선형 구조이며 배열의 모든 위치에서 요소를 삽입하고 삭제할 수 있습니다. 스택과 큐는 보다 일반적인 제한된 선형 구조입니다. 아래 그림과 같이:

7d84b47a08d741559e8c1edef78e4260.png

스택의 특징은 선입선출, 후입선출(LIFO: last in first out)입니다.

프로그램의 스택 구조:

함수 호출 스택: A(B(C(D()))): 즉, A 함수는 B를 호출하고 B는 C를 호출하고 C는 D를 호출합니다. B가 실행되면 B도 스택에 푸시되고 함수 C와 D도 실행될 때 스택에 푸시됩니다. 따라서 현재 스택의 순서는 A->B->C->D(스택 탑)이고, 함수 D가 실행된 후 스택이 팝되고 해제되며 팝업 스택의 순서는 D입니다. ->C->B->A;

재귀: 중지 조건이 없는 재귀가 스택 오버플로를 일으키는 이유는 무엇입니까? 예를 들어 함수 A는 자신을 계속 호출하는 재귀 함수이며(함수가 아직 실행되지 않았기 때문에 함수가 스택에서 제거되지 않음) 동일한 함수 A를 스택에 계속 푸시하여 결국 스택을 생성합니다. 오버플로(스택 오버플로트).

스택에 대한 일반적인 작업:

  • push(element): 스택의 맨 위 위치에 새 요소를 추가합니다.
  • pop(): 스택의 맨 위에 있는 요소를 제거하고 동시에 제거된 요소를 반환합니다.
  • peek(): 스택을 변경하지 않고 스택 맨 위에 있는 요소를 반환합니다(이 메서드는 스택 맨 위에 있는 요소를 제거하지 않고 반환합니다).
  • isEmpty(): 스택에 요소가 없으면 true를 반환하고 그렇지 않으면 false를 반환합니다.
  • size(): 스택의 요소 수를 반환합니다. 이 방법은 배열의 길이 속성과 유사합니다.
  • toString(): 스택 구조의 내용을 문자열로 반환합니다.

2.2 캡슐화 스택 클래스

// 캡슐화 스택 클래스

    함수 스택(){

      // 스택의 속성

      this.항목 =[]

      // 스택 관련 작업

      // 1.push(): 요소를 스택에 푸시합니다.

      //방법 1(권장하지 않음): 개체에 메서드 추가, 다른 개체는 재사용할 수 없음

      // this.push = () => { }

      //방법 2(권장): 여러 개체에서 재사용할 수 있는 Stack 클래스에 메서드 추가

      Stack.prototype.push = 함수(요소) {

        this.items.push(요소)

      }

      // 2.pop(): 스택에서 요소 제거

      Stack.prototype.pop = () => {

        this.items.pop() 반환

      }

      // 3.peek(): 스택의 맨 위 요소 보기

      Stack.prototype.peek = () => {

        this.items[this.items.length - 1] 반환

      }

      // 4.isEmpty(): 스택이 비어 있는지 확인

      Stack.prototype.isEmpty = () => {

      // this.length가 아니라(Stack 객체의 길이가 아님, Stack 클래스에는 길이 속성이 없음) Stack 클래스에 정의된 배열 항목에는 길이 속성이 있음

        this.items.length == 0 반환 

      }

      // 5.size(): 스택의 요소 수 가져오기

      Stack.prototype.size = () => {

        this.items.length 반환

      }

      // 6.toString(): 스택에 있는 데이터를 문자열로 출력

      Stack.prototype.toString = () => {

        //원하는 출력 형식: 20 10 12 8 7

        let resultString = ''

        for (let i of this.items){

          resultString += i + ' '

        }

        결과 문자열 반환

      }

    }

둘째, JavaScript는 대기열 구조(Queue)를 구현합니다.

1. 대기열 소개

큐는 선입선출(FIFO: first in first out)을 특징으로 하는 제한된 선형 테이블입니다 .
 
제한 사항은 테이블의 프런트 엔드에서만 삭제 작업을 허용한다는 것입니다.
테이블의 백엔드(후면)에 작업을 삽입합니다.
bad5549b89544fdfa9fbe99fec3bf332.png

 대기열 클래스의 구현:

큐의 구현은 스택의 구현과 동일하며 두 가지 옵션이 있습니다.

어레이 구현을 기반으로 합니다.

연결 목록 기반 구현;

대기열에 대한 일반 작업:

  • enqueue(element): 하나 이상의 새 항목을 대기열 끝에 추가합니다.
  • dequeue(): 큐의 첫 번째(즉, 큐의 앞) 항목을 제거하고 제거된 요소를 반환합니다.
  • front(): 대기열의 첫 번째 요소를 반환합니다. 첫 번째로 추가되고 가장 먼저 제거됩니다. 큐는 변경하지 않습니다(요소는 제거되지 않고 요소 정보만 반환되며 Stack 클래스의 peek 메서드와 매우 유사함).
  • isEmpty(): 큐에 요소가 없으면 true를 반환하고 그렇지 않으면 false를 반환합니다.
  • size(): 배열의 길이 속성과 유사하게 대기열에 포함된 요소 수를 반환합니다.
  • toString(): 대기열의 콘텐츠를 문자열 형식으로 변환합니다.

둘, 캡슐화 큐 클래스

// 배열 캡슐화 큐 클래스 기반

    함수 큐() {

    // 속성

      this.항목 = []

    // 방법

    // 1.enqueue(): 큐에 요소 추가

    Queue.prototype.enqueue = 요소 => {

      this.items.push(요소)

    }

    // 2.dequeue(): 큐에서 앞 요소 삭제

    Queue.prototype.dequeue = () => {

      this.items.shift() 반환

    }

    // 3.front(): 프런트 엔드 요소 보기

    Queue.prototype.front = () => {

      this.items[0] 반환

    }

    // 4.isEmpty: 큐가 비어 있는지 확인

    Queue.prototype.isEmpty = () => {

      this.items.length == 0 반환;

    }

    // 5.size(): 큐에 있는 요소 수 보기

    Queue.prototype.size = () => {

      this.items.length 반환

    }

    // 6.toString(): 큐에 있는 요소를 문자열로 출력

    Queue.prototype.toString = () => {

      let resultString = ''

        for (let i of this.items){

          resultString += i + ' '

        }

        결과 문자열 반환

      }

    }

    // 대기열 적용: 인터뷰 질문: 드럼 연주 및 꽃 전달
    let passGame = (nameList, num) => {       //1. 대기열 구조 생성       let queue = new Queue()

      //2. 큐에 모든 사람을 하나씩 추가
      // 이것은 ES6의 for 루프 작성 방법이며, i는 nameList[i]와 동일합니다.
      for(let i of nameList){         queue.enqueue(i)       }

      // 3. 카운트 시작
     while(queue.size() > 1){//큐에 1명만 남으면 카운트 중지
      //숫자가 아니면 큐 끝에 다시 합류
      //When it is num, add it 대기열에서 삭제
      // 3.1.num 이전의 사람들은 대기열의 끝에 다시 넣습니다
      . 0; i<num-1; i++ ){         queue.enqueue( queue.dequeue())       }       // 3.2.num 이 사람에 해당하므로 대기열에서 직접 삭제       /*         아이디어는 이것입니다. 배열과 같은 첨자 값을 가지며 특정 요소를 직접 가져올 수 없으므로 put을 사용하십시오. dequeue 메서드를 사용하여 직접 삭제할 수 있습니다. */ queue.dequeue       (       )      }







      //4. 남은 사람 가져오기
      console.log(queue.size()); //104
      let endName = queue.front()
      console.log('마지막 남은 사람:' + endName); // 106    
      
      return nameList .indexOf(endName);
    }

    //5. 테스트 드럼 패스
    let names = ['lily', 'lucy', 'Tom', 'Lilei', 'Tony']
    console.log(passGame(names, 3)); //113

배열, 스택 및 대기열에서 배열의 푸시 방법 형식:

122c9be18ea1438ba1c2a546d6bc4382.png

3. JavaScript는 컬렉션과 사전을 구현합니다.

1. 컬렉션 구조

1.1 소개

컬렉션의 일반적인 구현은 JavaScript의 Object 클래스를 사용하여 여기에서 캡슐화되는 해시 테이블 입니다.
 
컬렉션은 일반적 으로 반복될 수 없는 정렬되지 않은 요소 집합으로 구성됩니다.
 
  • 수학에서 자주 언급되는 집합의 요소는 반복될 수 있지만 컴퓨터에서 집합의 요소는 반복될 수 없습니다.
컬렉션은 특별한 배열입니다 .
 
  • 특별한 점은 내부의 요소가 순서가 없고 반복될 수 없다는 것입니다 .
  • 순서가 없다는 것은 첨자 값으로 접근할 수 없다는 것을 의미 하고, 중복이 없다는 것은 컬렉션에 같은 객체의 사본이 하나만 있다는 것을 의미합니다 .

컬렉션 클래스를 구현합니다 .
 
  • ES6의 Set 클래스는 컬렉션 클래스입니다. 여기서는 컬렉션의 기본 구현을 이해하기 위해 Set 클래스를 다시 패키징합니다.
  • JavaScript에서 Object 클래스의 핵심은 컬렉션 클래스 Set를 캡슐화하는 데 사용할 수 있는 컬렉션입니다.
 
컬렉션 일반 작업 :
 
  • add(value): 컬렉션에 새 항목을 추가합니다.
  • remove(value): 컬렉션에서 값을 제거합니다.
  • has(value): 값이 컬렉션에 있으면 true를 반환하고 그렇지 않으면 false를 반환합니다.
  • clear(): 컬렉션의 모든 항목을 제거합니다.
  • size(): 배열의 length 속성과 유사하게 컬렉션에 포함된 요소의 수를 반환합니다.
  • values(): 컬렉션의 모든 값을 포함하는 배열을 반환합니다.
  • 다른 방법들...

1.2 코드 구현

//캡슐화 컬렉션 클래스

    함수 세트() {

      //속성

      this.항목 = {}

      //방법

      //One.has 메서드

      Set.prototype.has = 값 => {

        this.items.hasOwnProperty(값)를 반환합니다.

      }

      //two.add 메소드

      Set.prototype.add = 값 => {

        // 요소가 세트에 이미 포함되어 있는지 확인

        if (this.has(값)) {

          거짓 반환

        }

        // 컬렉션에 요소 추가

        this.items[value] = value//속성 키와 값이 모두 값임을 나타냅니다.

        return true//추가가 성공했음을 나타냅니다.

      }

      //3.제거 방법

      Set.prototype.remove = (값) => {

        //1. 요소가 컬렉션에 포함되어 있는지 확인

        if (!this.has(값)) {

          거짓 반환

        }

        //2. 속성에서 요소 제거

        this.items[값] 삭제

        참을 반환

      }

      //four.clear 메소드

      Set.prototype.clear = () => {

        //원본 객체에는 참조점이 없으며 자동으로 재활용됩니다.

        this.항목 = {}

      }

      //5.크기 메소드

      Set.prototype.size = () => {

        Object.keys(this.items).length 반환

      }

      // 컬렉션의 모든 값을 가져옵니다.

      //Six.values ​​메서드

      Set.prototype.values ​​= 함수() {

        Object.keys(this.items) 반환

      }

    }

1.3 컬렉션 간의 작업

  • Union : 두 개의 컬렉션이 주어지면 두 컬렉션의 모든 요소를 ​​포함하는 새 컬렉션을 반환합니다.
  • Intersection : 두 집합이 주어지면 두 집합에 공통인 요소를 포함하는 새 집합을 반환합니다.
  • 차이점 : 두 세트가 주어지면 첫 번째 세트에는 존재하고 두 번째 세트에는 없는 모든 요소를 ​​포함하는 새 세트를 반환합니다.
  • 부분 집합 : 주어진 집합이 다른 집합의 부분 집합인지 확인합니다.

d33a4b56e8064e2293d49f0cb4c7bb90.png

 조합의 구현 :

구현 아이디어: 집합 A와 집합 B의 합집합을 나타내는 집합 C를 만들고 먼저 집합 A의 모든 요소를 ​​집합 C에 추가한 다음 집합 B를 순회하여 집합 C에 없는 요소인 경우 집합에 추가 씨 중간.

Set.prototype.union = otherSet => {

      // this: 컬렉션 객체 A

      // otherSet: 컬렉션 객체 B

      //1. 새 컬렉션 생성

      let unionSet = 새 세트()

      //2. A 컬렉션의 모든 요소를 ​​새 컬렉션에 추가

      let values ​​= this.values()

      // for(값의 let i){

      // unionSet.add(i)

      // }

      for(let i = 0;i < values.length;i++){

        unionSet.add(값[i])

      }

      //3. B컬렉션에서 요소를 꺼내서 새로운 컬렉션에 추가해야 하는지 판단

      값 = otherSet.values()

      // for(값의 let i){

      // //컬렉션의 add 메소드가 반복되는 요소를 판단했으므로 여기에 직접 추가 가능

      // unionSet.add(i)

      // }

      for(let i = 0;i < values.length;i++){

        unionSet.add(값[i])

      }

      유니온셋 반환

    }

교차 구현 :

구현 아이디어: 컬렉션 A를 트래버스하고 획득한 요소가 컬렉션 B에도 존재하는 경우 다른 컬렉션 C에 요소를 추가합니다.

Set.prototype.intersection = otherSet => {

      // 이: 컬렉션 A

      // otherSet : 집합 B

      //1. 새 컬렉션 생성

      let IntersectionSet = new Set()

      //2. A에서 원소를 가져와 집합 B에 동시에 존재하는지 판단하고, 존재한다면 새로운 집합에 넣는다.

      let values ​​= this.values()

      for(let i =0 ; i < values.length; i++){

        let item = values[i]

        if (otherSet.has(item)) {

          IntersectionSet.add(항목)

        }

      }

      교차 집합 반환

    }

차집합 구현 :

구현 아이디어: 컬렉션 A를 트래버스하고 획득한 요소가 컬렉션 B에 없으면 다른 컬렉션 C에 요소를 추가합니다.

Set.prototype.diffrence = otherSet => {

        //이: 컬렉션 A

        //otherSet: 집합 B

        //1. 새 컬렉션 생성

        var diffrenceSet = 새 세트()

        //2. A 컬렉션의 각 요소를 꺼내 B에 동시에 존재하는지 판단하고, 존재하지 않으면 새로운 컬렉션에 추가

        변수 값 = this.values()

        for(var i = 0;i < values.length; i++){

          var 항목 = 값[i]

          if (!otherSet.has(item)) {

            diffrenceSet.add(항목)

          }

        }

        diffrenceSet 반환

      }

하위 집합 구현 :

구현 아이디어: 트래버스 집합 A, 획득한 요소 중 하나가 집합 B에 존재하지 않는 경우 집합 A가 집합 B의 하위 집합이 아님을 의미하고 false를 반환합니다.

Set.prototype.subset = otherSet => {

        //이: 컬렉션 A

        //otherSet: 집합 B

        //컬렉션 A의 모든 요소를 ​​순회합니다. 컬렉션 A의 요소가 컬렉션 B에 존재하지 않는 경우 false를 반환하고, false를 반환하지 않고 전체 컬렉션 A를 순회하면 true를 반환합니다.

        let values ​​= this.values()

        for(let i = 0; i < values.length; i++){

          let item = values[i]

          if(!otherSet.has(항목)){

            거짓 반환

          }

        }

        참을 반환

      }

2. 사전 구조

2.1 소개

사전의 특징:
 
  • 사전은 키-값 쌍을 저장하며 주요 기능은 일대일 대응입니다.
  • 사전에서 키는 반복될없고 정렬되지 않은 반면 값은 반복 될 수 있습니다 .
사전과 지도의 관계 :
 
  • 일부 프로그래밍 언어는 이 매핑 관계를 사전 이라고 부릅니다 . 예를 들어 Swift의 Dictonary 및 Python의 dict가 있습니다.
  • 일부 프로그래밍 언어에서는 이 매핑 관계를 Map 이라고 합니다 (예: Java의 HashMap&TreeMap 등).

사전 클래스의 일반적인 작업:

  • set(key,value): 사전에 새 요소를 추가합니다.
  • remove(key): 키 값을 이용하여 사전에서 키 값에 해당하는 데이터 값을 제거합니다.
  • has(key): 이 사전에 키 값이 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
  • get(key): 키 값을 통해 특정 값을 찾아 반환합니다.
  • clear(): 이 사전의 모든 요소를 ​​삭제합니다.
  • size(): 사전에 포함된 요소의 수를 반환합니다. 배열의 길이 속성과 비슷합니다.
  • keys(): 사전에 포함된 모든 키 이름을 배열로 반환합니다.
  • values(): 사전에 포함된 모든 값을 배열로 반환합니다.

2.2 패키지 사전

JavaScript의 객체 구조를 기반으로 사전 클래스를 구현할 수 있습니다.
 
//딕셔너리 클래스 캡슐화
함수 사전(){
  //사전 속성
  this.항목 = {}
 
  //사전 연산 방식
  //1. 사전에 키-값 쌍 추가
  Dictionary.prototype.set = 함수(키, 값){
    this.items[키] = 값
  }
 
  //2. 사전에 키가 있는지 확인
  Dictionary.prototype.has = 함수(키){
    this.items.hasOwnProperty(키)를 반환합니다.
  }
 
  //3. 사전에서 요소 제거
  Dictionary.prototype.remove = 함수(키){
    //1. 사전에 키가 존재하는지 확인
    if(!this.has(key)) 거짓을 반환
 
    //2. 사전에서 키 삭제
    this.items[키] 삭제
    참을 반환
  }
 
  //4. 키에 따라 값 가져오기
  Dictionary.prototype.get = 함수(키){
    this.has(key)를 반환합니까? this.items[키] : 정의되지 않음
  }
 
  //5. 모든 키 가져오기
  Dictionary.prototype.keys = 함수(){
    Object.keys(this.items) 반환
  }
 
  //6.크기 메소드
  Dictionary.prototype.keys = 함수(){
    this.keys().length 반환
  }
 
  //세븐.클리어 메소드
  Dictionary.prototype.clear = 함수(){
    this.항목 = {}
  }
}

넷째, JavaScript는 해시 테이블을 구현합니다.

1. 해시 테이블 소개

1.1 해시 테이블 알기

해시 테이블은 일반적으로 배열을 기반으로 구현 되지만 배열보다 더 많은 이점이 있습니다.
 
  • 해시 테이블은 매우 빠른 삽입-삭제-찾기 작업을 제공할 수 있습니다 .
  • 데이터가 아무리 많아도 값을 삽입하고 삭제하는 데 걸리는 시간은 매우 짧다. 즉, O(1) 타임 클래스다. 실제로 이를 수행하는 데 몇 가지 기계 명령만 있으면 됩니다.
  • 해시 테이블의 속도는 트리보다 빠르며 원하는 요소를 거의 즉시 찾을 수 있습니다. 그러나 인코딩하는 것이 tree 보다 훨씬 간단합니다 .
해시 테이블에는 다음과 같은 단점도 있습니다.
 
  • 해시 테이블의 데이터는 순서가 잘못되어 그 안의 요소를 고정된 방식(예: 작은 것에서 큰 것으로)으로 순회할 수 없습니다.
  • 일반적으로 해시 테이블의 키는 반복될 수 없으며 동일한 키를 배치하여 다른 요소를 저장할 수 없습니다.
해시 테이블이란 무엇입니까?
 
  • 해시 테이블은 배열, 연결 목록, 트리와 달리 구조와 원리를 그래픽으로 표현할 수 있어 이해하기 쉽지 않습니다.
  • 해시 테이블의 구조는 배열 이지만 그 마법은 첨자 값의 변환 에 있으며, 이 변환을 해시 함수 라고 할 수 있으며 해시 함수를 통해 HashCode를 얻을 수 있습니다 .

해시 테이블은 최종적으로 데이터를 기반으로 구현되지만 해시 테이블은 해시 함수를 통해 문자열을 해당 첨자 값으로 변환하고 문자열과 첨자 값 사이에 해당 관계를 설정할 수 있습니다 .

1.2 해시 방식

문자열을 해당 첨자 값으로 변환하기 위해서는 코딩 시스템이 필요하며 , 코딩 시스템을 통해 문자를 숫자로 변환하는 방법에는 여러 가지가 있습니다.
 

해시 테이블의 일부 개념 :

  • 해싱 : 배열 범위 내 에서 큰 숫자를 첨자 로 변환하는 과정을 해싱이라고 합니다.
  • 해시 함수 : 우리는 일반적으로 단어를 큰 숫자로 변환 하고 큰 숫자를 해싱하는 코드 구현을 해시 함수라고 하는 함수에 넣습니다.
  • 해시 테이블 : 최종 데이터가 삽입되는 배열 의 전체 구조를 캡슐화 하고 결과는 해시 테이블입니다.

아직 해결해야 할 문제:

  • 해싱 후 첨자가 여전히 반복 될 수 있습니다 . 이 문제를 해결하는 방법은 무엇입니까? 이 상황을 갈등 이라고 합니다 . 갈등은 불가피하며 우리는 갈등을 해결할 수만 있습니다.

1.3 충돌 해결 방법

  • 옵션 1: 체인 주소 방식(지퍼 방식) ;
아래 그림과 같이 각 숫자에 대해 나머지 10 을 취하고 나머지 범위는 배열의 첨자 값으로 0에서 9까지 입니다. 또한 배열의 각 첨자 값에 해당하는 위치는 더 이상 숫자가 아니라 나머지 연산 후 동일한 나머지를 얻는 숫자로 구성된 배열 또는 연결 목록 입니다 .
 
46002defa2254402828fb7c32c2b38d6.png

 

이와 같이 첨자 값에 따라 전체 배열 또는 연결 리스트를 구한 다음 배열 또는 연결 리스트에서 계속 검색할 수 있습니다. 또한 일반적으로 상충되는 요소가 너무 많지 않습니다.

요약 : 충돌을 해결하기 위한 체인 주소 방식의 방법은 각 어레이 단위가 단일 데이터가 아닌 체인 을 저장하는 것으로 , 이 체인에서 공통적으로 사용되는 데이터 구조는 어레이 또는 연결 리스트 이며 , 두 데이터를 검색하는 효율성 구조는 동등합니다(체인의 요소가 일반적으로 너무 많지 않기 때문).

  • 솔루션 2: 주소 열기 방법 ;

주소 열기 방법이 작동하는 주요 방법은 빈 셀을 찾아 충돌하는 데이터 항목을 배치하는 것입니다.

fb28712788124c81a396d6e7f21e919a.png

빈 셀의 위치를 ​​감지하는 다양한 방법에 따라 선형 감지, 2차 감지 및 재해싱의 세 가지 방법으로 나눌 수 있습니다.

1.4 빈 셀을 찾는 방법

선형 감지
13 삽입 시 :
 
  • 해싱(modulo 10) 후 첨자 값 index=3이 얻어지지만 데이터 33은 이미 이 위치에 배치되었습니다. 선형 감지는 인덱스 위치 + 1 에서 시작하여 13을 하나씩 배치할 적절한 위치를 찾는 것입니다 .소위 적합한 위치는 빈 위치를 말합니다 .예를 들어 위 그림에서 인덱스=4의 위치는 다음과 같습니다. 적절한 위치.
13을 쿼리할 때:
 
  • 먼저 13을 해싱하여 index=3을 얻고, index=3에 저장된 데이터와 질의할 데이터 13이 같으면 바로 반환한다.
  • 동일하지 않은 경우 인덱스+1 위치부터 선형 검색하여 데이터 13을 하나씩 검색합니다.
  • 13을 삽입할 때 다른 위치를 삽입하기 위해 빈 위치 를 건너뛰지 않기 때문에 쿼리가 빈 위치를 찾는 한 전체 해시 테이블은 쿼리 프로세스 중에 순회하지 않습니다 .
13을 제거할 때:
 
  • 삭제 작업은 위의 두 가지 경우와 유사하지만 데이터 항목을 삭제할 때 위치의 첨자 내용을 null로 설정할 수 없습니다 . 그렇지 않으면 다른 후속 쿼리 작업에 영향을 미칩니다. 위치를 만나면 검색이 중지됩니다.
  • 일반적으로 특정 위치에서 데이터 항목을 삭제할 때 특수 처리 (예: -1로 설정)를 수행할 수 있으므로 검색 중에 -1을 만나면 검색을 계속해야 합니다.
선형 프로빙 문제:
 
  • 집계 인 선형 감지에는 심각한 문제가 있습니다 .
  • 예를 들어 해시 테이블에 요소가 삽입되지 않은 경우 23, 24, 25, 26 , 27을 삽입하면 첨자 값이 3, 4, 5, 6, 7인 위치가 모두 배치됨 데이터를 채우는 단위를 집계 라고 합니다 .
  • 집계는 삽입/쿼리/삭제 여부에 관계없이 해시 테이블의 성능에 영향을 미칩니다.
  • 예를 들어 13을 삽입할 때 연속 단위 3~7은 데이터를 삽입할 수 없다는 것을 알게 되며 삽입 과정에서 이러한 상황을 여러 번 경험해야 합니다. 2차 프로빙 방법으로 이 문제를 해결할 수 있습니다.

9d44f1f87aa044318c8ad97a06366244.png

 이차 탐지

위에서 언급한 선형 감지의 문제점 :

  • 이전 데이터가 계속해서 삽입되는 경우 새로 삽입되는 데이터는 먼 거리를 감지 해야 할 수 있습니다 .
  • 2차 감지는 선형 감지를 기반으로 최적화 됩니다 .
  • Linear detection : step size가 1인 detection 이라고 볼 수 있다. 예를 들어, 아래 표의 x 값부터 시작하여 아래 첨자 값 x+1, x에 따라 순차적으로 detection을 하는 것이 linear detection이다. +2, x+3 등;
  • 2차 검출 : 단계 크기는 예를 들어 아래 첨자 값 x: x+12, x+22, x+33에서 시작하여 최적화됩니다. 이러한 방식으로 상대적으로 긴 거리가 한 번에 감지되어 데이터 집계의 영향을 피할 수 있습니다.

2차 탐지 문제 :

  • 13-163-63-3-213과 같이 데이터 분포가 큰 데이터 세트를 삽입할 때 이 상황은 다양한 단계 크기로 일종의 집계를 발생시킵니다 (이 상황의 확률은 선형 감지보다 높지만) 집계는 작아야 함) 성능에도 영향을 미칩니다.

재해싱

열린 주소 지정에서 빈 셀을 찾는 가장 좋은 솔루션은 리해싱입니다.

  • 2차 감지의 단계 크기는 1, 4, 9, 16 등으로 고정되어 있습니다.
  • 이제 방법이 필요합니다. 각 키워드에 대해 동일한 감지 단계 대신 키워드(데이터)에 의존하는 감지 시퀀스를 생성합니다.
  • 이러한 방식으로 서로 다른 키워드는 동일한 배열 첨자 에 매핑되더라도 서로 다른 검색 시퀀스를 사용할 수 있습니다 .
  • 재해싱 방법은 다음과 같습니다. 키워드에 대해 다른 해시 함수를 사용하고 다시 해싱을 수행 하고 이 해싱 결과를 키워드의 단계 크기로 사용합니다 .

두 번째 해싱은 다음 두 가지 사항을 충족해야 합니다.

  • 첫 번째 해시 함수와 다릅니다 . 그렇지 않으면 해시 후 결과가 여전히 원래 위치에 있습니다.
  • 출력은 0 일 수 없습니다 . 그렇지 않으면 각 감지는 정지 상태의 무한 루프가 됩니다.

탁월한 해시 함수:

  • stepSize = 상수 - (키 % 상수) ;
  • 여기서 constant는 소수 이고 배열의 용량보다 작습니다.
  • 예: stepSize = 5 - (키 % 5), 요구 사항을 충족하고 결과는 0이 될 수 없습니다.

해싱의 효율성

해시 테이블에서 삽입 및 검색 작업을 수행하는 것은 매우 효율적입니다.

  • 충돌이 없으면 효율성 이 높아집니다.
  • 충돌이 발생 하면 액세스 시간은 후속 프로브 길이에 따라 달라집니다.
  • 평균 프로브 길이와 평균 액세스 시간은 필 팩터 에 따라 다르며 필 팩터가 커질수록 프로브 길이가 점점 길어집니다.

채우기 비율 개념 이해:

  • 채우기 비율은 전체 해시 테이블의 길이에 대한 현재 해시 테이블에 이미 포함된 데이터 항목의 비율을 나타냅니다 .
  • 채우기 비율 = 총 데이터 항목 / 해시 테이블 길이 ;
  • 열린 주소 방법 의 채움 계수는 최대 1입니다 . 빈 셀만 요소로 채울 수 있기 때문입니다.
  • 체인 주소 방식 의 채움 계수는 1보다 클 수 있습니다 . 원하는 만큼 지퍼 방식을 무한정 확장할 수 있기 때문입니다.

1.5 서로 다른 탐지 방법의 성능 비교

  • 선형 프로빙 :
채움 계수가 증가함에 따라 평균 감지 길이가 기하급수적으로 증가하고 성능이 좋지 않음을 알 수 있습니다. 실제로 최적의 필 팩터는 스토리지 효율성과 속도 간의 균형에 따라 달라지며, 필 팩터가 작을수록 스토리지 효율성은 감소하고 속도는 증가합니다.
 
cf11bfc68b914be5ad25bea561139bce.png

 

  • 보조 프로빙 및 재해싱 성능 :

2차 프로빙은 리해싱과 유사하며 선형 프로빙보다 성능이 약간 더 좋습니다. 채우기 계수가 증가함에 따라 평균 감지 길이가 기하급수적으로 증가하고 필요한 감지 횟수도 기하급수적으로 증가하며 성능이 높지 않음을 아래 그림에서 알 수 있습니다.

2d5156209c6146ae85499912f6caf9f9.png

  • 체인 주소 방법의 성능 :

채우기 계수가 증가함에 따라 평균 감지 길이가 선형으로 증가하는 것을 볼 수 있으며 이는 상대적으로 완만합니다. 개발에 사용되는 체인 주소 방법은 여러 가지가 있는데, 예를 들어 Java의 HashMap에서 체인 주소 방법을 사용합니다.

76e9da5218dd43e8af5197a2dda4b258.png

 1.6 뛰어난 해시 함수

해시 테이블의 장점은 속도이므로 해시 함수는 고성능을 소모하는 복잡한 알고리즘을 사용할 수 없습니다. 속도를 향상시키는 한 가지 방법은 해시 함수에서 곱셈과 나눗셈을 최소화하는 것 입니다 .
 
고성능 해시 함수는 다음 두 가지 이점이 있어야 합니다.
 
  • 빠른 계산 ;
  • 균일 분포 ;
빠른 계산
Horner's rule : 중국에서는 Horner's rule을 Qin Jiushao의 알고리즘이라고도 합니다.구체적인 알고리즘은 다음과 같습니다.
5061e86d9df64f7cbc8be2a82fe5ca5f.png

 

다항식의 값을 계산할 때 가장 안쪽 괄호에서 먼저 1차 다항식의 값을 계산한 다음 안쪽에서 바깥쪽으로 층별로 1차 다항식의 값을 계산합니다. 이 알고리즘은 n차 다항식 f(x) 값을 n차 다항식 값으로 변환합니다.

변신 전 :

  • 곱셈 횟수: n(n+1)/2회;
  • 추가 횟수: n회;

변신 후 :

  • 곱셈 횟수: n회;
  • 추가 횟수: n회;

시간 복잡도를 나타내기 위해 큰 O를 사용하는 경우 O(N)로 변환되기 전에 O(N^2)에서 직접 축소됩니다.

공평하게 나눠진

데이터가 해시 테이블에 고르게 분포되도록 하기 위해 상수를 사용해야 할 때 소수 (예: 해시 테이블의 길이, N의 거듭제곱의 밑수 등)를 사용하려고 합니다.

Java의 HashMap은 체인 주소 방법을 사용하며 해싱 공식은 index = HashCode(key) & (Length-1) 입니다.

즉, 데이터를 바이너리로 변환하고 나머지 연산 대신 AND 연산을 수행하는 것입니다. 이렇게 하면 컴퓨터가 직접 바이너리 데이터를 연산하므로 더 효율적입니다. 하지만 자바스크립트는 빅데이터라는 AND 연산을 할 때 문제가 생기기 때문에 여전히 자바스크립트를 이용해 해싱을 구현할 때 나머지 연산을 사용한다.

2. 예비 패키지 해시 테이블

해시 테이블의 일반적인 작업은 다음과 같습니다.
 
  • 넣기(키, 값): 작업을 삽입하거나 수정합니다.
  • get(key): 해시 테이블의 특정 위치에 있는 요소를 가져옵니다.
  • remove(key): 해시 테이블의 특정 위치에 있는 요소를 삭제합니다.
  • isEmpty(): 해시 테이블에 요소가 없으면 trun을 반환하고, 해시 테이블의 길이가 0보다 크면 false를 반환합니다.
  • size(): 해시 테이블에 포함된 요소의 수를 반환합니다.
  • resize(value): 해시 테이블을 확장합니다.

2.1 해시 함수의 간단한 구현

먼저 Horner's rule을 이용하여 hashCode의 값을 계산하고, 나머지 연산을 통해 해싱을 구현하는데 여기서 배열의 크기만 지정하면 된다.

//해시 함수 설계

    //1. 문자열을 상대적으로 큰 숫자로 변환: hashCede

    //2. 큰 수의 hasCode를 배열의 범위(크기)로 압축합니다.

    함수 hashFunc(str, 크기){

      //1. hashCode 변수 정의

      해시코드 = 0

      //2. Horner의 규칙, hashCode 값 계산

      //cats -> 유니코드 인코딩

      for(let i = 0 ;i < str.length; i++){

        // str.charCodeAt(i)//문자에 해당하는 유니코드 인코딩을 가져옵니다.

        해시코드 = 37 * 해시코드 + str.charCodeAt(i)

      }

      //3. 나머지 연산

      let index = hashCode % 크기

      리턴 인덱스

    }

2.2 해시 테이블 생성

캡슐화된 해시 테이블의 배열 구조 모델:
886a3196ebc747f6a66b168389f14f59.png

먼저 해시 테이블 클래스 HashTable을 생성하고 필요한 속성과 위에서 구현한 해시 함수를 추가한 다음 다른 메서드를 구현합니다.

//해시 테이블 클래스 캡슐화

    함수 HashTable() {

      //속성

      this.저장소 = []

      this.count = 0//저장된 요소 수 계산

      //충진 계수: loadFactor > 0.75이면 용량 확장이 필요하고 loadFactor < 0.25이면 용량을 줄여야 합니다.

      this.limit = 7//초기 길이

      //방법

      //해시 함수

      HashTable.prototype.hashFunc = 함수(str, 크기){

      //1. hashCode 변수 정의

      해시코드 = 0

      //2. Horner의 규칙, hashCode 값 계산

      //cats -> 유니코드 인코딩

      for(let i = 0 ;i < str.length; i++){

        // str.charCodeAt(i)//문자에 해당하는 유니코드 인코딩을 가져옵니다.

        해시코드 = 37 * 해시코드 + str.charCodeAt(i)

      }

      //3. 나머지 연산

      let index = hashCode % 크기

      리턴 인덱스

    }

2.3.put(키,값)

해시 테이블의 삽입 및 수정 작업은 동일한 기능입니다. 사용자가 <key, value>를 전달할 때 키가 존재하지 않으면 삽입 작업이고 키가 이미 존재하면 삽입 작업이기 때문입니다. 수정 작업입니다.
9ae02cb1245841d59a132321c32a019d.png

구현 아이디어 :

  • 먼저 키에 따라 인덱스 값 인덱스를 가져옵니다. 목적은 스토리지의 해당 위치에 데이터를 삽입하는 것입니다.
  • 그런 다음 인덱스 값에 따라 버킷을 꺼내고 버킷이 없으면 버킷을 먼저 생성한 다음 인덱스 값 위치에 놓습니다.
  • 그런 다음 원래 값을 추가할지 수정할지 판단합니다. 이미 값이 있으면 값을 수정하고 없으면 후속 작업을 수행합니다.
  • 마지막으로 새 데이터 작업을 수행합니다.

코드 구현 :

//작업 삽입 및 수정

    HashTable.prototype.put = 함수(키, 값){

      //1. 키에 따라 해당 인덱스를 얻습니다.

      let index = this.hashFunc(키, this.limit)

      //2. 인덱스에 따라 해당 버킷을 꺼냅니다.

      let 버킷 = this.storage[인덱스]

      //3. 버킷이 null인지 확인

      if (버킷 == null) {

        양동이 = []

        this.storage[index] = 버킷

      }

      //4. 데이터 수정 여부 결정

      for (let i = 0; i < 버킷.길이; i++) {

        let 튜플 = 버킷[i];

        if (튜플[0] == 키) {

          튜플[1] = 값

          반환//반환 값 없음

        }

      }

      //5. 작업 추가

      버킷.푸시([키, 값])

      this.count += 1

    }

2.4.get(키)

구현 아이디어 :
 
  • 먼저 키에 따라 해시 함수를 통해 스토리지에서 해당 인덱스 값 인덱스를 얻습니다.
  • 그런 다음 색인 값에 따라 해당 버킷을 가져옵니다.
  • 다음으로 획득한 버킷이 null인지 판단하고 null이면 직접 null을 반환합니다.
  • 그런 다음 버킷의 각 키가 수신 키와 동일한지 여부를 선형으로 트래버스합니다. 같으면 해당 값을 직접 반환합니다.
  • 마지막으로 버킷을 순회한 후에도 여전히 해당 키를 찾지 못하면 null을 반환합니다.

//작업 가져오기

    HashTable.prototype.get = 함수(키){

      //1. 키에 따라 해당 인덱스를 얻습니다.

      let index = this.hashFunc(키, this.limit)

      //2. 인덱스에 따라 해당 버킷 가져오기

      let 버킷 = this.storage[인덱스]

      //3. 버킷이 null인지 확인

      if (버킷 == null) {

        null 반환

      }

      //4. 버킷이 있으면 선형 검색을 수행합니다.

      for (let i = 0; i < 버킷.길이; i++) {

        let 튜플 = 버킷[i];

        if (tuple[0] == key) {//tuple[0]은 키를 저장하고, tuple[1]은 값을 저장합니다.

          리턴 튜플[1]

        }

      }

      //5. 여전히 찾을 수 없으면 null을 반환합니다.

      null 반환

    }

2.5.제거(키)

구현 아이디어 :
 
  • 먼저 키에 따라 해시 함수를 통해 스토리지에서 해당 인덱스 값 인덱스를 얻습니다.
  • 그런 다음 색인 값에 따라 해당 버킷을 가져옵니다.
  • 다음으로 획득한 버킷이 null인지 판단하고 null이면 직접 null을 반환합니다.
  • 그런 다음 버킷을 선형으로 검색하고 해당 데이터를 찾아 삭제합니다.
  • 마지막으로 여전히 찾을 수 없으면 null을 반환합니다.

암호:

//작업 삭제

    HashTable.prototype.remove = 함수(키){

      //1. 키에 따라 해당 인덱스를 얻습니다.

      let index = this.hashFunc(키, this.limit)

      //2. 인덱스에 따라 해당 버킷 가져오기

      let 버킷 = this.storage[인덱스]

      //3. 버킷이 null인지 확인

      if (버킷 == null) {

        null 반환

      }

      //4. 버킷이 있으면 선형 검색을 수행하고 삭제합니다.

      for (let i = 0; i < 버킷.길이; i++) {

        let 튜플 = 버킷[i]

        if (튜플[0] == 키) {

          버킷.스플라이스(i,1)

          this.count -= 1 

          리턴 튜플[1]

        }

    }

      //5. 여전히 찾지 못함, null 반환

      null 반환

    }

2.6 다른 방법의 구현

다른 메소드는 다음과 같습니다: isEmpty(), size() :
 
암호:
// 해시 테이블이 null인지 판단
  HashTable.prototype.isEmpty = 함수(){
    반환 this.count == 0
  }
 
  // 해시 테이블의 요소 수를 가져옵니다.
  HashTable.prototype.size = 함수(){
    반환 this.count
  }

3. 해시 테이블의 확장

3.1 확장과 압축
확장해야 하는 이유는 무엇입니까?
 
  • 앞서 해쉬 테이블에서 길이 7인 배열을 사용했는데 , 체인 주소 방식을 사용하기 때문에 로드 팩터(loadFactor)가 1보다 클 수 있으므로 이 해쉬 테이블은 새로운 데이터를 제한 없이 삽입할 수 있습니다.
  • 그러나 데이터의 양이 증가 함에 따라 저장소의 각 인덱스에 해당하는 버킷 배열(연결된 목록)이 점점 길어지게 되어 해시 테이블 의 효율성이 떨어집니다.
용량 확장은 언제 필요합니까?
 
  • 일반적인 상황은 loadFactor > 0.75 일 때 용량을 확장하는 것 입니다.
확장하는 방법?
 
  • 단순 확장은 직접 두 배가 될 수 있습니다 (소수에 대해서는 나중에 설명).
  • 확장 후에는 모든 데이터 항목을 동기식으로 수정해야 합니다 .

구현 아이디어 :

  • 먼저 원래 저장소를 가리키는 oldStorage와 같은 변수를 정의합니다.
  • 그런 다음 용량이 더 큰 새 어레이를 만들고 this.storage가 가리키도록 합니다.
  • 마지막으로 oldStorage의 각 버킷에 있는 각 데이터를 꺼내 this.storage가 가리키는 새 배열에 차례로 추가합니다.

88c3f09bc1a441958b835911de6281c9.png

 //해시 테이블 확장

  HashTable.prototype.resize = 함수(newLimit){

    //1. 기존 스토리지 어레이 콘텐츠 저장

    let oldStorage = this.storage

    //2. 모든 속성 재설정

    this.저장소 = []

    this.카운트 = 0

    this.limit = newLimit

    //3. oldStorage의 모든 버킷 탐색

    for (let i = 0; i < oldStorage.length; i++) {

      //3.1 해당 버킷 꺼내기

      const 버킷 = oldStorage[i];

      //3.2 버킷이 null인지 판단

      if (버킷 == null) {

        계속하다

      }      

      //3.3 버킷에 데이터가 있으면 데이터를 빼서 다시 삽입

      for (let j = 0; j < 버킷.길이; j++) {

        const 튜플 = 버킷[j];

        this.put(tuple[0], tuple[1])//삽입된 데이터의 키와 값

      }

    }

  }

위에서 정의한 해시 테이블의 크기 조정 방법은 해시 테이블의 확장을 실현할 수 있을 뿐만 아니라 해시 테이블 용량의 압축도 실현할 수 있습니다.

Loading factor = 해시 테이블의 데이터 / 해시 테이블의 길이 , 즉 loadFactor = count / HashTable.length.

  • 일반적으로 채움 계수 laodFactor > 0.75 이면 해시 테이블이 확장됩니다. 해시 테이블의 add 메서드(push 메서드)에 다음 코드를 추가합니다.

//확장 작업이 필요한지 판단

      if(this.count > this.limit * 0.75){

        this.resize(this.limit * 2)

      }

  • 채움 계수 laodFactor < 0.25 이면 해시 테이블 용량이 압축됩니다. 해시 테이블의 삭제 메서드(remove 메서드)에 다음 코드를 추가합니다.

//용량 줄이기

    if (이.제한 > 7 && 이.카운트 < 이.한도 * 0.25) {

      this.resize(Math.floor(this.limit / 2))

    }

3.2 소수를 용량으로 선택

확장된 해시 테이블의 용량은 소수입니다.
 
구현 아이디어 :
 
2번의 확장 후 루프에서 isPrime을 호출하여 얻은 용량이 소수인지 판단하고 그렇지 않은 경우 소수가 될 때까지 +1 합니다.
 
코드 구현 :
 
  • 1단계: 먼저 소수를 판단하기 위한 isPrime 메서드와 소수를 얻기 위한 getPrime 메서드를 HashTable 클래스에 추가해야 합니다.

// 들어오는 숫자가 소수인지 판단

  HashTable.prototype.isPrime = 함수(숫자){

      if (숫자 <= 1) {

        거짓 반환

      }

      //1. 숫자의 제곱근 구하기: Math.sqrt(num)

      //2. 루프 판단

      for(var i = 2; i<= Math.sqrt(num); i++ ){

        if(숫자 % i == 0){

          거짓을 반환합니다.

        }

      }

        true를 반환합니다.

    }

    // 소수를 구하는 메소드

    HashTable.prototype.getPrime = 함수(숫자){

       //7*2=14,+1=15,+1=16,+1=17(소수)

      동안 (!this.isPrime(num)) {

        넘버++

      }

      반환 번호

    }

  • 2단계: 요소를 추가하는 put 메서드와 요소를 삭제하는 remove 메서드에서 배열 확장과 관련된 작업을 수정합니다.

put 메서드에 다음 코드를 추가합니다.

//확장 작업이 필요한지 판단

      if(this.count > this.limit * 0.75){

        let newSize = this.limit * 2

        let newPrime = this.getPrime(newSize)

        this.resize(newPrime)

      }

remove 메서드에 다음 코드를 추가합니다.

//용량 줄이기

          if (이.제한 > 7 && 이.카운트 < 이.한도 * 0.25) {

            let newSize = Math.floor(this.limit / 2)

            let newPrime = this.getPrime(newSize)

            this.resize(newPrime)

          }

넷째, 해시 테이블의 완전한 구현

//해시 테이블 클래스 캡슐화

    함수 HashTable() {

      //속성

      this.저장소 = []

      this.count = 0//저장된 요소 수 계산

      //충진 계수: loadFactor > 0.75이면 용량 확장이 필요하고 loadFactor < 0.25이면 용량을 줄여야 합니다.

      this.limit = 7//초기 길이

      //방법

      //해시 함수

      HashTable.prototype.hashFunc = 함수(str, 크기){

      //1. hashCode 변수 정의

      해시코드 = 0

      //2. Horner의 규칙, hashCode 값 계산

      //cats -> 유니코드 인코딩

      for(let i = 0 ;i < str.length; i++){

        // str.charCodeAt(i)//문자에 해당하는 유니코드 인코딩을 가져옵니다.

        해시코드 = 37 * 해시코드 + str.charCodeAt(i)

      }

      //3. 나머지 연산

      let index = hashCode % 크기

      리턴 인덱스

    }

 

    //1. 삽입 및 수정 작업

    HashTable.prototype.put = 함수(키, 값){

      //1. 키에 따라 해당 인덱스를 얻습니다.

      let index = this.hashFunc(키, this.limit)

      //2. 인덱스에 따라 해당 버킷을 꺼냅니다.

      let 버킷 = this.storage[인덱스]

      //3. 버킷이 null인지 확인

      if (버킷 == null) {

        양동이 = []

        this.storage[index] = 버킷

      }

      //4. 데이터 수정 여부 결정

      for (let i = 0; i < 버킷.길이; i++) {

        let 튜플 = 버킷[i];

        if (튜플[0] == 키) {

          튜플[1] = 값

          반환//반환 값 없음

        }

      }

      //5. 작업 추가

      버킷.푸시([키, 값])

      this.count += 1

      //6. 확장 작업이 필요한지 여부를 결정합니다.

      if(this.count > this.limit * 0.75){

        let newSize = this.limit * 2

        let newPrime = this.getPrime(newSize)

        this.resize(newPrime)

      }

    }

    //2. 작업 가져오기

    HashTable.prototype.get = 함수(키){

      //1. 키에 따라 해당 인덱스를 얻습니다.

      let index = this.hashFunc(키, this.limit)

      //2. 인덱스에 따라 해당 버킷 가져오기

      let 버킷 = this.storage[인덱스]

      //3. 버킷이 null인지 확인

      if (버킷 == null) {

        null 반환

      }

      //4. 버킷이 있으면 선형 검색을 수행합니다.

      for (let i = 0; i < 버킷.길이; i++) {

        let 튜플 = 버킷[i];

        if (tuple[0] == key) {//tuple[0]은 키를 저장하고, tuple[1]은 값을 저장합니다.

          리턴 튜플[1]

        }

      }

      //5. 여전히 찾을 수 없으면 null을 반환합니다.

      null 반환

    }

    //3. 삭제 작업

    HashTable.prototype.remove = 함수(키){

      //1. 키에 따라 해당 인덱스를 얻습니다.

      let index = this.hashFunc(키, this.limit)

      //2. 인덱스에 따라 해당 버킷 가져오기

      let 버킷 = this.storage[인덱스]

      //3. 버킷이 null인지 확인

      if (버킷 == null) {

        null 반환

      }

      //4. 버킷이 있으면 선형 검색을 수행하고 삭제합니다.

      for (let i = 0; i < 버킷.길이; i++) {

        let 튜플 = 버킷[i]

        if (튜플[0] == 키) {

          버킷.스플라이스(i,1)

          this.count -= 1 

          리턴 튜플[1]

          //6. 용량 줄이기

          if (이.제한 > 7 && 이.카운트 < 이.한도 * 0.25) {

            let newSize = Math.floor(this.limit / 2)

            let newPrime = this.getPrime(newSize)

            this.resize(newPrime)

          }

        }

    }

      //5. 여전히 찾지 못함, null 반환

      null 반환

    }

  /*------------------기타 방법-----------------------------------*/

  // 해시 테이블이 null인지 판단

  HashTable.prototype.isEmpty = 함수(){

    반환 this.count == 0

  }

  // 해시 테이블의 요소 수를 가져옵니다.

  HashTable.prototype.size = 함수(){

    반환 this.count

  }

  //해시 테이블 확장

  HashTable.prototype.resize = 함수(newLimit){

    //1. 기존 스토리지 어레이 콘텐츠 저장

    let oldStorage = this.storage

 

    //2. 모든 속성 재설정

    this.저장소 = []

    this.카운트 = 0

    this.limit = newLimit

    //3. oldStorage의 모든 버킷 탐색

    for (let i = 0; i < oldStorage.length; i++) {

      //3.1 해당 버킷 꺼내기

      const 버킷 = oldStorage[i];

      //3.2 버킷이 null인지 판단

      if (버킷 == null) {

        계속하다

      }      

      //3.3 버킷에 데이터가 있으면 데이터를 빼서 다시 삽입

      for (let j = 0; j < 버킷.길이; j++) {

        const 튜플 = 버킷[j];

        this.put(tuple[0], tuple[1])//삽입된 데이터의 키와 값

      }

    }

  }

  // 들어오는 숫자가 소수인지 판단

  HashTable.prototype.isPrime = 함수(숫자){

      if (숫자 <= 1) {

        거짓 반환

      }

      //1. 숫자의 제곱근 구하기: Math.sqrt(num)

      //2. 루프 판단

      for(var i = 2; i<= Math.sqrt(num); i++ ){

        if(숫자 % i == 0){

          거짓을 반환합니다.

        }

      }

        true를 반환합니다.

    }

    // 소수를 구하는 메소드

    HashTable.prototype.getPrime = 함수(숫자){

       //7*2=14,+1=15,+1=16,+1=17(소수)

      동안 (!this.isPrime(num)) {

        넘버++

      }

      반환 번호

    }

 }

다섯째, JavaScript는 단방향 연결 목록을 구현합니다.

1. 단방향 연결 리스트 소개

연결 목록은 배열과 마찬가지로 일련의 요소를 저장하는 데 사용할 수 있지만 연결 목록과 배열의 구현 메커니즘은 완전히 다릅니다. 연결된 목록의 각 요소는 요소 자체를 저장하는 노드다음 요소를 가리키는 참조 (일부 언어에서는 포인터 또는 연결이라고 함)로 구성됩니다.

259f815052744e8dbfc6366a607a272f.jpg

37206d282ec14c27b2aeea4d53859218.png 

  • head 속성은 연결된 목록의 첫 번째 노드를 가리킵니다.
  • 연결된 목록의 마지막 노드는 null을 가리킵니다.
  • 연결된 목록에 노드가 없으면 head는 직접 null을 가리킵니다.

배열의 단점:

  • 배열 생성은 일반적으로 연속 메모리 공간 (전체 메모리 블록)을 적용해야 하며 크기는 고정되어 있습니다. 따라서 원래 어레이가 용량 요구 사항을 충족할 수 없으면 확장해야 합니다 (일반적으로 2배와 같은 더 큰 어레이를 신청한 다음 원래 어레이의 요소를 복사).
  • 배열의 시작 또는 중간에 데이터를 삽입하는 것은 비용이 많이 들고 많은 수의 요소 이동이 필요합니다.

연결 리스트의 장점:

  • 연결된 목록의 요소는 메모리에서 연속적인 공간일 필요가 없으며 컴퓨터의 메모리를 충분히 활용하여 유연한 동적 메모리 관리를 실현할 수 있습니다 .
  • 연결 목록은 생성될 때 크기를 결정할 필요가 없으며 크기는 무한정 확장 될 수 있습니다 .
  • 연결된 목록이 데이터를 삽입하고 삭제할 때 시간 복잡도는 O(1)에 도달할 수 있으며 이는 배열보다 훨씬 효율적입니다.

연결 리스트의 단점:

  • 연결 리스트가 임의의 위치에 있는 요소에 접근할 때는 처음부터 접근해야 합니다 (모든 요소에 접근하기 위해 첫 번째 요소를 건너뛸 수 없습니다).
  • 요소는 첨자 값을 통해 직접 접근할 수 없으며 해당 요소를 찾을 때까지 처음부터 하나씩 접근해야 합니다.
  • 다음 노드로 이동하는 것은 쉽지만 이전 노드 로 돌아가는 것은 어렵습니다 .

연결된 목록의 일반적인 작업:

  • append(element): 연결된 목록의 끝에 새 항목을 추가합니다.
  • insert(position, element): 연결 리스트의 특정 위치에 새 항목을 삽입합니다.
  • get(position): 해당 위치에 있는 요소를 가져옵니다.
  • indexOf(요소): 연결 리스트에 있는 요소의 인덱스를 반환합니다. 연결된 목록에 요소가 없으면 -1을 반환합니다.
  • update(position, element): 특정 위치에 있는 요소를 수정합니다.
  • removeAt(position): 연결된 목록의 특정 위치에서 항목을 제거합니다.
  • remove(element): 연결 리스트에서 항목을 제거합니다.
  • isEmpty(): 연결된 목록에 요소가 없으면 trun을 반환하고 연결 목록의 길이가 0보다 크면 false를 반환합니다.
  • size(): 배열의 길이 속성과 유사하게 연결 목록에 포함된 요소의 수를 반환합니다.
  • toString(): 연결된 목록 항목은 Node 클래스를 사용하므로 JavaScript 개체에서 상속된 기본 toString 메서드를 다시 작성하여 요소의 값만 출력하도록 해야 합니다.

2. 단방향 연결 목록 클래스 캡슐화

2.0 단방향 연결 리스트 클래스 만들기

먼저 단방향 연결 목록 클래스 Linklist를 만들고 기본 특성을 추가한 다음 단방향 연결 목록의 공통 메서드를 구현합니다.

// 연결 리스트 클래스 캡슐화

    함수 LinkList(){

      // 내부 클래스 캡슐화: 노드 클래스

      함수 노드(데이터){

        this.data = 데이터;

        this.next = null;

      }

      // 속성

      // 속성 head는 연결된 목록의 첫 번째 노드를 가리킵니다.

      this.head = null;

      this.길이 = 0;

      // 1. 추가 방법 구현

      LinkList.prototype.append = 데이터 => {

        //1. 새 노드 생성

        let newNode = 새 노드(데이터)

        //2. 새 노드 추가

        //Case 1: 노드가 하나뿐인 경우

        if(이.길이 == 0){

          this.head = newNode

        //Case 2: 노드 수가 1보다 큰 경우 연결 리스트 끝에 새 노드 추가  

        }또 다른 {              

          //현재 변수가 첫 번째 노드를 가리키도록 합니다.

          let current = this.head

          //current.next(다음 노드가 비어 있지 않음)가 비어 있지 않으면 current가 마지막 노드를 가리킬 때까지 계속 반복합니다.

          동안 (current.next){

            현재 = 현재.다음

          }

          // 마지막 노드의 다음 노드가 새 노드를 가리킴

          current.next = newNode

        }

        //3. 새 노드 추가 후 길이+1

        this.길이 += 1

      }

// 2. toString 메소드 구현

      LinkList.prototype.toString = () => {

        // 1. 변수 정의

        let current = this.head

        let listString = ""

 

        // 2. 노드를 하나씩 가져오는 루프

        동안(현재){ 

          listString += 현재.데이터 + " "

          current = current.next//노드의 데이터를 이어붙인 후 다음 노드로 현재 포인트를 만드는 것을 잊지 마세요.

        }

        목록 문자열 반환

      }

 

      // 3. 삽입 메소드 구현

      LinkList.prototype.insert = (위치, 데이터) => {

      //positionon의 의미 이해: position=0은 삽입 후 새 경계점이 첫 번째 노드가 됨을 의미하고, position=2는 새 경계점이 삽입 후 세 번째 노드가 됨을 의미합니다.

        //1. 위치에 대한 범위 외 판단: 들어오는 위치는 음수가 될 수 없으며 LinkList의 길이를 초과할 수 없습니다.

        if(위치 < 0 || 위치 > this.length){

          거짓 반환

        }

        //2. 데이터를 기반으로 newNode 생성

        let newNode = 새 노드(데이터)

 

        //3. 새 노드 삽입

        //케이스 1: 위치 삽입 position=0

        if(위치 == 0){

          // 새 노드가 첫 번째 노드를 가리키도록 합니다.

          newNode.next = this.head

          // 헤드가 새 노드를 가리키도록 합니다.

          this.head = newNode

        //Case 2: 삽입 위치 position>0 (이 경우 위치=길이 포함)

        } 또 다른{

          인덱스 = 0

          이전 = null

          let current = this.head

          //1단계: while 루프를 사용하여 변수 현재 위치 위치의 다음 노드를 가리킵니다(while 루프 작성 참고).

          동안(인덱스++ < 위치){

          //2단계: current가 다음 노드를 가리키기 전에 이전이 current가 가리키는 노드를 가리키도록 한다.

            이전 = 현재

            현재 = 현재.다음

          }

          // 3단계: current 변수를 통해 newNode가 위치의 다음 노드를 가리키도록 합니다(current는 현재 위치 position의 다음 노드를 가리키고 있음).

          newNode.next = 현재

          //4단계: 이전 변수를 사용하여 newNode를 가리키는 위치에 있는 이전 노드를 만듭니다.

          이전.다음 = 새노드

          

  //linked list의 노드를 직접 조작할 수는 없지만 변수를 통해 이 노드를 가리켜 간접적으로 노드를 조작할 수 있습니다.

        }

        //4. 새 노드가 삽입된 후 길이+1이 필요합니다.

        this.길이 += 1;

        참을 반환

      }

//4. get 메서드 구현

      LinkList.prototype.get = (위치) => {

        //1. 아웃 오브 바운드 판정

        // 위치 = 길이인 경우 null을 얻으므로 0 =< 위치 < 길이

        if(위치 < 0 || 위치 >= this.length){

          null 반환

        }

        //2. 지정된 위치에서 다음 노드의 데이터 가져오기

        // 노드를 간접적으로 조작하기 위해 변수를 사용하기도 합니다.

        let current = this.head

        인덱스 = 0

        동안(인덱스++ < 위치){

          현재 = 현재.다음

        }

        current.data 반환

      }

 

      //5. indexOf 메서드 구현

      LinkList.prototype.indexOf = 데이터 => {

        //1. 변수 정의

        let current = this.head

        인덱스 = 0

 

        //2. 검색 시작: current가 null을 가리키지 않는 한 계속 반복됩니다.

        동안(현재){

          if(전류.데이터 == 데이터){

            리턴 인덱스

          }

          현재 = 현재.다음

          지수 += 1

        } 

        //3. 연결 리스트를 탐색한 후 찾지 못하면 -1을 반환

        -1 반환

      }

//6. 업데이트 메소드 구현

      LinkList.prototype.update = (위치, newData) => {

        //1. 아웃 오브 바운드 판정

        //변경된 노드는 null이 될 수 없으므로 위치는 길이와 같을 수 없습니다.

        if(위치 < 0 || 위치 >= this.length){

          거짓 반환

        }

        //2. 올바른 노드 찾기

        let current = this.head

        인덱스 = 0

        동안(인덱스++ < 위치){

          현재 = 현재.다음

        }

        //3. 위치에 있는 다음 노드의 데이터를 newData로 변경

        current.data = newData

        // 수정이 성공했음을 나타내기 위해 true를 반환합니다.

        참을 반환

      }

 

      // 7. removeAt 메서드 구현

      LinkList.prototype.removeAt = 위치 => {

        //1. 아웃 오브 바운드 판정

        if (위치 < 0 || 위치 >= this.length) {

          null 반환

        }

        //2. 요소 삭제

        //Case 1: position = 0인 경우(첫 번째 노드 삭제)

        let current = this.head

        경우 (위치 ==0 ) {

        //사례 2: 위치 > 0

          this.head = this.head.next

        }또 다른{

          인덱스 = 0

          이전 = null

          동안 (인덱스++ < 위치) {

            이전 = 현재

            현재 = 현재.다음

          }

          //루프 종료 후 current는 position 다음 노드를 가리키고 이전은 current 앞 노드를 가리킴

          //이전 노드의 다음 노드가 현재 노드의 다음 노드를 가리키도록 합니다.

          이전.다음 = 현재.다음

        }

        //3, 길이-1

        this.길이 -= 1

        //삭제된 노드의 데이터 반환, 상단에 current가 정의됨

        current.data 반환

      }

/*-------------다른 메소드 구현--------------*/

      //8. 제거 메소드 구현

      LinkList.prototype.remove = (데이터) => {

        //1. 목록에서 데이터 위치 가져오기

        let position = this.indexOf(데이터)

        //2. 위치 정보에 따라 노드 삭제

        this.removeAt(위치)를 반환합니다.

      }

      //9. isEmpty 메소드 구현

      LinkList.prototype.isEmpty = () => {

        반환 this.length == 0

      }

      //10. 크기 메서드 구현

      LinkList.prototype.size = () => {

        반환 this.length

      }

    }

여섯, JavaScript는 이중 연결 목록을 구현합니다.

1. 이중 연결 리스트 소개

이중 연결 리스트: 처음부터 끝까지 , 끝에서 헤드까지 순회 할 수 있습니다 . 즉, 연결 목록 연결 프로세스는 양방향이며 실현 원칙은 노드가 순방향 연결 참조역방향 연결 참조를 모두 갖는다는 것입니다 .

이중 연결 리스트의 단점:

  • 노드가 삽입되거나 삭제될 때마다 2개가 아닌 4개의 참조가 처리되어야 하므로 구현하기가 더 어렵습니다.
  • 단방향 연결 목록과 비교할 때 더 큰 메모리 공간을 차지합니다.
  • 그러나 이러한 단점은 이중 연결 목록의 편리함에 비하면 사소한 것입니다.

이중 연결 리스트의 구조:

5c0078c63c794216b5148cb1082140a4.png

  • 이중 연결 리스트에는 첫 번째 노드를 가리키는 헤드 포인터가 있을 뿐만 아니라 마지막 노드를 가리키는 테일 포인터도 있습니다.
  • 각 노드는 세 부분으로 구성됩니다. 항목은 데이터를 저장하고 prev 는 이전 노드를 가리키고 next 는 다음 노드를 가리킵니다.
  • 이중 연결 목록의 첫 번째 노드의 prev는 null 을 가리킵니다 .
  • 이중 연결 목록의 마지막 노드의 다음 노드는 null 을 가리킵니다 .

이중 연결 목록에 대한 일반적인 작업(방법):

  • append(element): 연결된 목록의 끝에 새 항목을 추가합니다.
  • inset(position, element): 연결된 목록의 특정 위치에 새 항목을 삽입합니다.
  • get(요소): 해당 위치의 요소를 가져옵니다.
  • indexOf(element): 연결 리스트에 있는 요소의 인덱스를 반환합니다. 연결 목록에 요소가 없으면 -1을 반환합니다.
  • update(position, element): 특정 위치에 있는 요소를 수정합니다.
  • removeAt(position): 연결된 목록의 특정 위치에서 항목을 제거합니다.
  • isEmpty(): 연결된 목록에 요소가 없으면 trun을 반환하고 연결 목록의 길이가 0보다 크면 false를 반환합니다.
  • size(): 배열의 길이 속성과 유사하게 연결 목록에 포함된 요소의 수를 반환합니다.
  • toString(): 연결된 목록 항목은 Node 클래스를 사용하므로 JavaScript 개체에서 상속된 기본 toString 메서드를 다시 작성하여 요소의 값만 출력하도록 해야 합니다.
  • forwardString(): 정방향 순회 노드의 문자열 형식을 반환합니다.
  • backwordString(): 역순으로 통과한 노드의 문자열 형식을 반환합니다.

2. 이중 연결 리스트 클래스 캡슐화

2.0 이중 연결 리스트 클래스 만들기

먼저 이중 연결 목록 클래스 DoubleLinklist를 만들고 기본 특성을 추가한 다음 이중 연결 목록의 공통 메서드를 구현합니다.

//이중 연결 리스트 클래스 캡슐화

    함수 DoubleLinklist(){

      //내부 클래스 캡슐화: 노드 클래스

      함수 노드(데이터){

        this.data = 데이터

        this.prev = null

        this.next = null

      }

      //속성

      this.head = null

      this.tail ==널

      this.길이 = 0

      }

2.1.추가(요소)

암호:

//append 방법

      DoubleLinklist.prototype.append = 데이터 => {

        //1. 데이터를 기반으로 새 노드 생성

        let newNode = 새 노드(데이터)

        //2. 노드 추가

        //Case 1: 첫 번째 노드 추가

        if (이 길이 == 0) {

          this.tail = newNode

          this.head = newNode 

        //Case 2: 추가가 첫 번째 노드가 아님

        }또 다른 {

          newNode.prev = this.tail

          this.tail.next = newNode

          this.tail = newNode

        }

        //3.길이+1

        this.길이 += 1

      }

2.2.toString() 요약

암호:
 

      //연결된 목록을 문자열 형식으로 변환
      //one.toString 메서드
      DoubleLinklist.prototype.toString = () => {         return this.backwardString()       }

      //2.forwardString method
      DoubleLinklist.prototype.forwardString = () => {         //1. 변수 정의         let current =this.tail         let resultString = ""



        //2. (current) {           resultString += current.data + "--"           current = current.prev          }         return resultString       } 동안         각 노드를 얻기 위해 순차 순회합니다.




      //Three.backwardString 메서드
      DoubleLinklist.prototype.backwardString = () => {         //1. 변수 정의         let current = this.head         let resultString = ""



        //2. (current) {           resultString += current.data + "--"           current = current.next         }         return resultString       } 동안         각 노드를 얻기 위해 하나씩 뒤로 순회합니다.




2.3.insert(위치, 요소)

암호:
 

//insert method
      DoubleLinklist.prototype.insert = (position, data) => {         //1. Cross-border 판단         if (position < 0 || position > this.length) return false

        //2. 데이터를 기반으로 새 노드 생성
        let newNode = new Node(data)

        //3. 새 노드 삽입
        //원래 연결 리스트는 비어 있음
          //Case 1: 삽입된 newNode가 첫 번째 노드임

       if (this.length == 0) {           this.head = newNode           this.tail = newNode         // 원래 연결된 목록은 비어 있지 않습니다.         }else {



8c171c279fb247169fa15c7e86478777.png

  //情况2:position == 0
          if (position == 0) {             this.head.prev = newNode             newNode.next = this.head             this.head = newNode


63be32e3cb7d42bd85b9a48cb59bd32c.png

    //情况3:position == this.length 
          } else if(position == this.length){             this.tail.next = newNode             newNode.prev = this.tail             this.tail = newNode


32a4903b933d4f75a44d1f99dbd17091.png

 //사례 4: 0 < position < this.length
          }else{             let current = this.head             let index = 0             while(index++ < position){               current = current.next             }             // newNode를 가리키는 노드 변수를             전후로 수정 pos position.next = 현재             newNode.prev = current.prev             current.prev.next = newNode             current.prev = newNode           }         }         //4.length+1         this.length += 1         return true // 성공적인 삽입을 나타내려면 true를 반환합니다.       }















c7d4d2009723462f840d20fe134c9678.png

 2.4.get(위치)

암호:

      //get method
      DoubleLinklist.prototype.get = position => {         //1. 범위를 벗어난 판단         if (position < 0 || position >= this.length) {//position은 길이와 같을 수 없으며           요소를         가져올 때 null을 반환합니다.



        //2 요소 가져오기
        let current = null
        let index = 0
        //this.length / 2 > position: 처음부터 탐색
        if ((this.length / 2) > position) {           current = this.head           while(index++ < position ){           current = current.next         }         //this.length / 2 =< position: 꼬리에서 트래버스         }else{           current = this.tail           index = this.length - 1           while(index-- > position){           current = 현재 .prev         }         }         현재 데이터 반환       }













자세한 과정:
 
두 개의 변수 current와 index를 정의하고 이전 아이디어에 따라 while 루프 순회를 통해 현재 노드와 해당 인덱스 값 index를 구하고, 구해야 할 위치 이후에 노드를 찾을 때까지, 이때 index = pos =x, 그런 다음 현재 Just .data를 반환합니다.
 
연결 리스트의 노드 수가 많은 경우 이 검색 방법은 효율적이지 않으며 개선 방법은 다음과 같습니다.
 
연결된 목록의 노드 수를 얻으려면 this.length를 사용해야 합니다. 그렇지 않으면 오류가 보고됩니다.
  • this.length / 2 > position인 경우: 헤드(head)에서 이송을 시작합니다.
  • this.length / 2 < position인 경우: 테일(tail)에서 트래버스를 시작합니다.

2e3ecee0e4d547b0ac4340f9b6c7adcd.png

2.5.indexOf(요소)

암호:
 
//indexOf 메서드
      DoubleLinklist.prototype.indexOf = 데이터 => {
        //1. 변수 정의
        let current = this.head
        인덱스 = 0
 
        //2. 연결 리스트를 순회하여 데이터와 동일한 노드를 찾습니다.
        동안(현재){
          if (current.data == 데이터) {
            리턴 인덱스
          }
          현재 = 현재.다음
          지수 += 1
        }
        -1 반환
      }

2.7.update(위치, 요소)

암호:
 
//업데이트 방법
      DoubleLinklist.prototype.update = (위치, newData) => {
        //1. 아웃 오브 바운드 판정
        if (위치 < 0 || 위치 >= this.length) {
          거짓 반환
        }
 
        //2. 올바른 노드 찾기
        let current = this.head
        인덱스 = 0
        //this.length / 2 > 위치: 처음부터 트래버스
        if (this.length / 2 > 위치) {
          동안(인덱스++ < 위치){
          현재 = 현재.다음
        }
        //this.length / 2 =< position: 끝에서 트래버스
        }또 다른{
          현재 = this.tail
          색인 = this.length - 1
          동안 (색인 --> 위치) {
            현재 = 현재.이전
          }
        }
 
        //3. 찾은 노드의 데이터 수정
        current.data = newData
        true 반환 // 성공적인 수정을 나타냅니다.
      }

2.8.removeAt(위치)

암호:

//removeAt 메소드

  DoubleLinklist.prototype.removeAt = 위치 => {

        //1. 아웃 오브 바운드 판정

        if (위치 < 0 || 위치 >= this.length) {

          null 반환

        }

        //2. 노드 삭제

        //linked list에서 length == 1인 경우

        //사례 1: 연결 리스트에 노드가 하나만 있는 경우

        let current = this.head//다음 상황에서 current.data 반환을 용이하게 하기 위해 상단에 정의

        if (이 길이 == 1) {

          this.head = null

          this.꼬리 = null

        //링크드 리스트에서 length > 1일 때

        } 또 다른{

          //case 2: 첫 번째 노드 삭제

          경우 (위치 == 0) {

            this.head.next.prev = null

            this.head = this.head.next

          //케이스 3: 마지막 노드 삭제

          }else if(위치 == this.length - 1){

            current = this.tail//이 경우 마지막으로 삭제된 노드를 반환합니다.

            this.tail.prev.next = null

            this.tail = this.tail.prev

          }또 다른{

          //Case 4: 연결 리스트 중간 노드 삭제

            인덱스 = 0

            동안(인덱스++ < 위치){

              현재 = 현재.다음

            }

            현재.다음.이전 = 현재.이전

            현재.이전.다음 = 현재.다음

          }

        }

        //3.길이 -= 1

        this.길이 -= 1

        return current.data //삭제된 노드의 데이터를 반환

      }

자세한 과정:

노드가 삭제되는 몇 가지 상황이 있습니다.

연결 리스트의 길이 = 1인 경우:

  • 사례 1: 연결된 목록의 모든 노드를 삭제합니다. 연결된 목록의 헤드와 테일이 null을 가리키도록 만듭니다.

265e28d4e76e4429852c2e06186ad0f9.png

 연결 리스트의 길이 > 1:

  • 사례 2: 연결된 목록에서 첫 번째 노드를 삭제합니다.

      패스: this.head.next.prev = null, 1로 변경;

     패스: this.head = this.head.next, 2를 가리키도록 변경합니다.

    Node1에는 다른 노드에 대한 참조가 있지만 Node1에 대한 참조는 없지만 Node1은 자동으로 재활용됩니다.

b13915df571a475bafdb0075cdffda48.png

  • 사례 3: 연결 목록에서 마지막 노드를 삭제합니다.

      Pass: this.tail.prev.next = null, 1을 가리키도록 수정;

      패스: this.tail = this.tail.prev, 2를 가리키도록 수정합니다.

19b81316e7ab45c48dccb9851bf8e90a.png

  •  사례 4: 연결 리스트 중간에 있는 노드 삭제:
    position = x와 같이 while 루프를 통해 삭제할 노드를 찾으면 삭제할 노드는 다음과 같이 Node(x+1)입니다. 수치:

d8341875ece043018d792e60c9cd8353.png

Pass: current.next.prev = current.prev, 1을 가리키도록 수정;

Pass: current.prev.next = current.next, 2를 가리키도록 수정;

이렇게 하면 Node(x+1)을 가리키는 참조가 없고(current는 Node(x+1)을 가리키지만 current는 임시 변수로 메서드 실행 후 소멸됨) Node( x+1) 자동으로 삭제됩니다.

d8ed82ed720e49a3a810b7fa5137092c.png

 2.9 기타 방법

다른 메서드는 다음과 같습니다. remove(element), isEmpty(), size(), getHead(), getTail()
 
암호:

  //8.제거 방법

  DoubleLinklist.prototype.remove = 데이터 => {

    //1. 데이터에 따라 첨자 값 가져오기

    let index = this.indexOf(데이터)

    //2. 인덱스에 따라 해당 위치의 노드 삭제

    return this.removeAt(인덱스)

  }

  //nine.isEmpty 메서드

  DoubleLinklist.prototype.isEmpty = () => {

    반환 this.length == 0

  }

  //10.크기 메소드

  DoubleLinklist.prototype.size = () => {

    반환 this.length

  }

  //11.getHead 메소드: 연결된 목록의 첫 번째 요소 가져오기

  DoubleLinklist.prototype.getHead = () => {

    this.head.data 반환

  }

  //12.getTail 메서드: 연결된 목록의 마지막 요소 가져오기

  DoubleLinklist.prototype.getTail = () => {

    this.tail.data 반환

  }

3. 연결 리스트 구조 요약

단일 연결 목록에는 head 및 next 두 가지 속성이 있고 이중 연결 목록에는 head, tail, next 및 prev 네 가지 속성이 있습니다 . 포인터를 처리하는 것은 포인터를 올바르게 연결하여 간단한 연결 목록을 구현하는 체인을 형성하는 것과 같습니다.

3.1. 주의점

  • 연결 리스트에서 current = current.next는 current --> current.next, 즉 current가 current의 다음 노드를 가리키는 것처럼 왼쪽에서 오른쪽으로 볼 수 있습니다.
  • 노드 삭제 원칙: 개체에 대한 참조가 없는 한 개체에 다른 개체에 대한 참조가 있는지 여부에 관계없이 개체는 재활용(삭제)됩니다.
  • 매개변수의 모든 위치는 범위를 벗어난 것으로 판단되어야 합니다.

3.2 링크드 리스트의 추가, 삭제, 수정 및 조회

이중 연결 리스트를 예로 들어 보겠습니다. 연결 리스트를 추가, 삭제, 수정 및 확인하는 것은 연결 리스트에서 해당 노드를 얻고 두 변수 prev 및 next의 방향을 변경하는 것 이상입니다 .
  • 상황 1: 조작해야 할 변수를 얻기 위해서는 head와 tail 두 개의 변수만 있으면 된다. .prev.prev... 원하는 노드도 괜찮습니다.) 이 경우 연결 목록의 길이는 length: 0 <= length <= 2입니다.
  • 상황 2: 작동해야 하는 변수를 얻기 위해 tail과 head에 의존할 수 없는 경우 while 루프를 사용하여 작동해야 하는 노드를 찾을 수 있습니다.

3.3 연결 리스트 참조 포인트 수정하기

newNode 참조의 포인팅을 먼저 수정한 후 다른 참조를 수정해야 합니다.
  • Case 1: 헤드와 테일 참조를 통해 동작해야 할 노드를 얻을 수 있을 때 최종적으로 헤드 또는 테일 변수의 포인터를 변경한다.
  • Case 2: current를 사용하여 동작해야 하는 node를 획득할 때 마지막으로 curren.next 또는 current.prev의 포인팅을 변경한다.

9adb78c1350f416evacde6dc55f93698.png

 3.4 연결 리스트 순회

두 가지 순회 아이디어 축적
 
  • 지정된 위치 = x 위치에서 다음 노드 및 인덱스 값을 가져옵니다.

루프가 종료된 후 index = position = x, 변수 current는 Node(x+1)을 가리키고 변수 index의 값은 Node(x+1)의 인덱스 값 x입니다.

  • 연결된 목록의 모든 노드를 순회합니다.
루프는 current.next = null이고 current가 연결된 목록의 마지막 노드를 가리킬 때 중지됩니다.
 

 

 

추천

출처blog.csdn.net/m0_65835778/article/details/126460034