1 분 소요

Linked List

정의와 성질

  • 원소들을 저장할 때 그 다음 원소가 있는 위치를 포함
  • k번째 원소를 확인/변경하기 위해 O(k)가 필요함
  • 임의의 위치에 원소를 추가/제거는 O(1)
  • 원소들이 메모리 상에 연속해있지 않아 Cache hit rate가 낮지만 할당이 쉬움

    배열 vs 연결 리스트

    || 배열 | 연결리스트 |
    |—|—|—|
    |k번째 원소의 접근| O(1) | O(k) |
    |임의 위치에 원소 추가/제거| O(N) | O(1) |
    |메모리 상의 배치| 연속 | 불연속 |
    |추가적으로 필요한 공간| - | O(N) |

    연결 리스트(빠르게 구현)

  • 원고를 배열로 관리
  • pre와 nxt에 이전/다음 원소의 포인터 대신 배열 상의 인덱스를 저장하는 방식
  • 메모리 누수로 실무에서는 사용X
  • 코딩테스트용
    #include<iostream>
    using namespace std;
    const int MX=1000005;
    int dat[MX], pre[MX], nxt[MX];//dat[i]는 i번째 원소의 값, pre[i]는 i번지 원소에 대해 이전 원소의 인덱스, nxt[i]는 다음 원소의 인덱스
    int unused = 1; //현재 사용되지 않는 인덱스,새로운 원소가 들어갈 수 있는 인덱스
    void insert(int addr,int num){
      //addr은 연결리스트 상에서 순서가 아니라 해당 원소의 주소
      dat[unused]=num;
      pre[unused]=addr;
      nxt[unused]=nxt[addr];
      if(nxt[addr]!=-1){
          pre[nxt[addr]]=unused;
      }
      nxt[addr]=unused;
      unused++;
    }
    //이부분 때문에 실무에서 사용X
    //제거된 원소가 프로그램이 종료될 때까지 메모리를 점유하고 있음
    void erase(int addr){
      nxt[pre[addr]]=nxt[addr];
      if(nxt[addr]!=-1){
          pre[nxt[addr]]=pre[addr];
      }
    }
    void traverse(){
      int cur = nxt[0];
      while(cur!=-1){
          cout<<dat[cur]<<' ';
          cur=nxt[cur];
      }
      cout<<"\n\n";
    }
    int main(void){
      fill(pre,pre+MX,-1);
      fill(nxt,nxt+MX,-1);
      insert_test();
      erase_test();
      return 0;
    }
    

    STL list

    #include<iostream>
    #include<list>
    using namespace std;
    int main(void){
      list<int> L = {1,2}; // 1 2
      list<int>::iterator t = L.begin(); // t는 1을 가리키는 중
      L.push_front(10); // 10 1 2
      cout<<*t<<'\n'; // t가 가리키는 1 을 출력
      L.push_back(5); // 10 1 2 5
      L.insert(t,6); // 10 6 1 2 5 t가 가리키는 곳 앞에 6을 출력
      t++; // t를 1칸 앞으로 전진 t가 가리키는 값 2
      t=L.erase(t); // t가 가리키는 값을 제거, 그 다음 원소인 5의 위치를 반환 10 6 1 5
      cout<<*t<<'\n'; // 5
      for(auto i : L){
          cout<<i<<' ';
      }
      cout<<'\n';
      for(list<int>::iterator it = L.begin(); it!=L.end();it++){
          cout<<*it<<' ';
      }
      return 0;
    }
    

    Tip

  • 문제: 원형 연결 리스트 내의 임의의 노드 하나가 주어졌을 때 해당 List의 길이를 효율적으로 구하는 방법?
  • 동일한 노드가 나올 때 까지 계속 다음 노드로 가면 됨. 공간복잡도 O(1), 시간복잡도 O(N)
  • 문제: 중간에 만나는 두 연결 리스트의 시작점들이 주어졌을 때 만나는 지점을 구하는 방법?
  • 각각의 길이를 구한 후 더 긴 쪽을 둘의 차이만큼 앞으로 이동시키고 두 시작점이 만날 떄까지 두 시작점을 동시에 한 칸씩 전진. 공간복잡도O(1), 시간복잡도O(A+B)
  • 문제 주어진 연결 리스트 안에 사이클이 있는지 판단
  • Floyd’s cycle-finding algorithm 이용 한 칸씩 가는 커서와 두 칸씩 가는 커서를 동일한 시작점에서 출발시키면 사이클이 있다면 만남