리스트 ADT
맨앞과 맨뒤의 노드만 위치를 찾아낼 수 있는 그런 방법을 제공하기 위해선, 자연스럽게 더블리 링크드 리스트 구현이 내부적으로 구현하는게 좋다.
벡터가 배열의 확장이라면 리스트는 더블리 링크드 리스트를 확장한 개념
postiton 을 저장하는 자료구조로, position 을 통해 access한다.
position이 구현된다면 iterator 가되며, iterator 를 통해 access 한다.
실제 STL list 는 더블리 링크드 리스트로 구현되어 있으며, position 으로 iterator를 사용한다.
반복자(이터레이터)를 통해 포지션을 리턴하며, 이는 begin()과 end()로 초기화됨
begin()은 시작 원소, end()는 마지막 원소의 다음 주소를 리턴함
position을 주소값이라고 생각해도 무방하다. (실제로는 주소를 직접 주는것은 아님!)
Q. 반복자를 이동시키는 과정은 O(n) 아님? ⇒ 메서드 밖에서 구현하므로 상관 x
list 의 모든 메소드 뿐만 아니라 인덱스으로도 access 가능한 메소드도 추가되어 있는것
데이터를 일렬로 정렬하는 자료구조의 끝.판.왕
시퀀스를 이용해서 지금까지 배운 모든 자료구조(stack, queue, vector, list) 를 모든 구현할 수 있음 => 저 자료구조들이 지원해줘야 하는 메소드들을 모두 가지고 있다. ( = 모든 기능을 포함하고 있다)
index 와 position 을 모두 사용하여 데이터 element에 access 가능
=> how 가능? : position 을 사용하는 함수 + index 와 position 을 변환하는 함수
의의 : 일렬로 정렬되는 자료구조의 완성형
당연히, 리스트(list) 기반의 메소드들을 모두 포함
추가로 index 를 통해 access 하는 메소드들로 제공
메서드 : 리스트의 모든 함수(포지션 기반) + 포지션과 인덱스를 서로 변환하는 함수
indexOf(p) - 포지션에 해당하는 인덱스 값 반환
atIndex(i) - 인덱스에 해당하는 포지션 값 반환
요약 : 해당 원소에 자체에 대한 자료구조가 아닌, position에 대한 자료구조이므로, (데이터가 아닌 position을 저장하는 자료구조이므로)
메모리를 할당할 때, 주소에 해당하는 4byte로 모든 원소를 할당할 수 있음
(원래 원소가 몇바이트였는지 상관 없이 모두 4byte로 정렬 가능! ⇒ 메모리 절약)배열의 각 인덱스에 저장된 포인터의 해당 주소값(position)을 따라가서 그 데이터를 확인해보면, 각 데이터는 int형, double형, 구조체 node형, ... 등 다양한 형태의 데이터 객체일 수 있다.
f와 l을 사용 : 맨앞, 맨뒤 postition 을 저장 => 마치 큐의 front, rear 와 동일한 기능
배열의 각 인덱스 방에는 position 이 저장되어있는 형태
=> 즉, 각 인덱스 방에 주소값이 저장되어있다.
각 position 에 대한 object 들은 데이터 값과 index 값을 저장하고 있다.
데이터 필드에는 그냥 데이터 값말고도 또 다른 데이터 객체에 대한 포인터 변수 주소값이 저장되어 있는 형태일 수 있다.
각 인덱스에 저장된 주소를 따라가보면 무슨 타입인지는 몰라도 그 주소에 있는 object는 데이터 필드에 데이터와 인덱스를 나타내는 인덱스 필드에 index를 저장하고 있다.
그 데이터 필드도 단순한 데이터 값이 아닌, position (주소값) 을 또 저장하고 있는 연결고리 형태일 수 있다.
그 데이터 필드에 있는 데이터(구조체 포인터 변수 주소값)가 100바이트인 경우, 인덱스는 정수이므로 4바이트이므로 100+4 = 총 104바이트 크기를 차지한다.
즉, 104바이트 짜리의 구조체 포인터 변수가 환형 배열 각 인덱스 방 안에 들어있게 된다.
(시퀀스 : 말그대로 연속적으로 저장된 데이터들. 배열의 각 인덱스에 저장된 주소를 따라갔을 때 있는 객체들을 의미한다)
배열의 인덱스와 시퀀스의 인덱스가 불일치하는 경우 처리 방법
erase 연산 : front가 1증가하면서 배열의 원소들이 다 한칸씩 뒤로 밀리는 방법으로 구현할것
insert 연산 : rear 가 1증가하면서
즉, 가령 0번 인덱스에 0번 시퀀스 객체를 저장하고 있었는데 이제 1번 인덱스에 0번 시퀀스 객체를 저장하게 된다.
(배열의 인덱스:0->1, 그 인덱스에 대한 시퀀스 객체의
인덱스 필드의 값 : 0 그대로 유지)
구현 방법이 까다롭다!
atIndex(i), indexOf(p) 만 추가적으로 구현하면 끝
배열을 기반으로 구현했기 때문에 list 연산은 다 포함하고 추가적인 연산이 있다. 결국 배열을 이용해서 리스트를 구현해야 하는 것과 마찬가지다.
환형 배열의 저장되있는 position 값들을 뒤로 한칸씩 미루고 확보된 공간에 새로 할당받은 position값인 p를 넣어야한다.
최악의 경우 n개를 다 미뤄야하므로 O(n) 이다.
position을 저장하는 환형 배열의 특정 위치p 에다 데이터 e를 넣는경우
시퀀스 객체를 새로 생성
환형 배열의 특정 셀에다 주소값 p를 저장하기 위해선, 기존에 배열에 저장되어있던 position 값들을 뒤로 한칸씩 다 밀어줘야한다. (공간 확보를 위해) => 최악의 경우 O(n) 이 걸린다.
환형 배열의 position 값들을 한칸씩 이동시켜준 순간, 각 position들에 대한 시퀀스 객체들의 인덱스 필드 값을 1 증가시켜줘야한다.
( ex. 인덱스 필드가 3이 저장되어 있는 시퀀스 객체를 4로 바꿔주면서 이 시퀀스 객체는 4번째 객체가 되었음을 명시해준다 )
insertFront : 인덱스 f의 앞 셀에다 position을 삽입후 f를 한칸 뒤로 미룬다 (맨앞 인덱스인 f의 " 앞 " 셀에다 insert하는 것이므로, 환형배열의 저장되있는 값들을 한칸씩 뒤로 미룰 필요x)
eraseFront : 인덱스 f의 값을 1증가시켜서 f를 한칸 뒤로 이동시킴
그 값을 얼마만큼 실제로 0에서 떨어져있는지를 구현을 잘 해내서 알고있다면,
(insert(0), erase(0), insertFront, eraseFront 등에서 연산을 할때마다 f를 증감하는 카운트를 잘 해서 시작 위치인 인덱스 f를 제대로 저장하도록 구현 되있는 경우)
f와 처음 인덱스 0과의 차이를 계산하면 몇칸만큼 떨어져있는 알 수 있으므로 insertFront, eraseFront 등을 구현 가능하다.
시퀀스의 인덱스와 환형배열의 인덱스가 불일치할 수 있다는 점을 고려해야 한다.
i : 환형 배열에서의 인덱스가 아닌, 시퀀스 객체에서의 인덱스를 의미
환형 배열에서 인덱스로 접근하는 법
ex ) i=3 이고, f=1 인 경우 i=3의 시퀀스 객체에 대한 환형 배열의 position 은 환형배열에서 3+1 = 4번째 인덱스에 저장되어 있다.
따라서 환형배열의 인덱스 4번방에 저장되있는 position을 통해 해당 시퀀스 객체를 찾아내고, 그 시퀀스 객체의 데이터 필드에 저장되있는 값을 리턴받으면 된다.
insertFront, eraseFront 를 계속 진행하다보면 환형배열의 인덱스 값과 시퀀스 객체의 인덱스 값이 불일치할 수 있다.
예를들어 eraseFront가 된 경우 f가 한칸 증가해서, 시퀀스에 들어있던 각 객체들의 인덱스 필드의 인덱스 값이 모두 1감소해야 할까?
이렇게 하면 O(1) 이 아닌 O(n) 이 되므로 비효율적이다. 환형배열의 맨앞 원소에 대한 insert,erase 연산이 O(1) 으로 빠른 연산이 가능하다는 메리트 성질을 잃게 됨.
그냥 중간 원소에 대한 insert, erase 는 어짜피 환형배열에서 주소값들을 한칸씩 옮겨야 해서 O(n) 이 걸리므로, 시퀀스 객체들의 인덱스 값들을 최신화 해줘도 O(n) 이여서 상관없다. 하지만 insertFront, eraseFront 는 그러면 비효율적이라는 말이다.
=> eraseFront 가 되면 f가 1감소하는 횟수를 카운팅해서 따로 저장해 놓는다. 그러고 나중에 언젠가 한밤중에 기회가 될때 카운팅한 정보를 기반으로 불일치하던 인덱스를 일치시키도록 다시 sorting 시킨다.
O(1) 으로 구현한다면 환형배열의 인덱스 값과 그 인덱스에 해당하는 시퀀스 객체의 인덱스 값이 불일치 함을 감안하고 적절하게 코딩(구현) 할것
insertFront, eraseFront 의 횟수를 카운팅한다.
insertFront를 할때마다 f가 1감소, 또 eraseFront를 할때마다 f가 1증가한다.
이런 연산의 횟수가 몇번이냐에 따라서 시퀀스 객체에 저장된 인덱스와 환형배열에 실제로 저장된 인덱스가 일치하지 않을 수 있다.
아예 시퀀스 객체에 인덱스 필드를 없애버려서 해결하는 방법이 있긴함. 근데 이건 쫌..
atIndex, indexOf(p) : O(n)
atIndex : 인덱스에 해당하는 실제 position 을 찾아가서 데이터에 access 하라는 것.
indexOf(p) : 실제 주소값을 준것.
시간 복잡도 정리