CS공부/자료구조
[자료구조] Array와 Linked List
이숭간
2021. 7. 11. 18:11
728x90
Array
어레이는 어떤 언어에나 존재하는 가장 기본적인 자료구조로 고정된 크기의 연속된 메모리를 말한다.
순서대로 번호(인덱스)가 붙은 원소들이 연속적인 형태로 구성된 구조를 뜻하며 인덱스를 알면 해당원소에 접근할 수 있다.
원소들이 연속적으로 배치되어 있기때문에 인덱스로 원소에 접근할때 O(1)의 시간복잡도를 가진다.
배열은 참조형 타입으로 변수명 자체가 레퍼런스를 저장하고 있기때문에 변수자체를 포인터로 활용할수 있다.
이때 배열은 연속적인 위치를 보장하므로 (포인터값 + 데이터형의 크기)로 다음요소에 접근할수 있다.
한계점:
- 고정된 크기
- 배열 자료구조는 선언시에 할당된 메모리크기가 고정되어있기 때문에 배열의 크기를 늘려야 할때는 크기가 큰 새로운 배열을 생성하고, 기존 내용을 복사하거나 배열일부를 연결리스트 등으로 다른곳과 연결하는 등의 방법이 쓰인다.
- 처음에 할당한 배열 크기만큼 배열공간을 사용하지 않을경우 사용되지 않은 메모리공간이 낭비되게 된다.
- 연속된 메모리공간만을 할당받아야 하기때문에 메모리사용이 비효율적이다.( 흩어져있는 메모리를 모아서 사용할수 없기 때문 )
- 삽입과 삭제
- 배열의 중간에서 삽입과 삭제시 삽입한원소 혹은 삭제한 원소보다 큰 값을 가지는 원소들은 모두 자리를 shift해줘야하는 비용이 발생하고 이경우 시간복잡도는 O(n)이 된다.
- 다만 가장 끝에 원소 추가 삭제의 경우 이동시킬 원소가 존재하지 않으므로 시간복잡도는 O(1)이다.
따라서 삽입과 삭제가 빈번한 상황에서는 배열을 쓰는것을 지양한다.
장점 :
- 인덱스만 알면 조회에 시간이 O(1)밖에 걸리지 않기 때문에 조회가 빈번한 상황에서는 배열을 사용하도록 한다.
배열은 언제 사용하면 좋을까?
- 데이터 개수가 확실하게 정해져 있을때 데이터저장을 위한 자료구조로 선택하면 좋다.
- 삽입/삭제가 적고 조회(검색)이 빈번할때
LinkedList
배열의 한계점을 극복하기 위해 만들어진 자료구조로 각 노드가 데이터와 포인터를 가지고 한줄로 연결된 방식으로 데이터를 저장하는 자료구조이다.
장점 :
- 배열의 단점인 고정된 크기를 해결한점으로, 리스트의 길이가 가변적이다.
- 중간에서의 삽입/삭제도 O(1)시간에 가능하다. 위치만 알면 삽입/삭제시 앞뒤 포인터만 바꿔주면 되기때문에 그렇다.
단점 :
- 임의의 노드에 바로접근이 불가능 : 순차접근만 가능하기 때문에 임의의 위치에 있는 원소를 찾는데 최악의 경우 O(n)시간이 걸린다.
- 다음노드 위치를 저장하기위한 추가공간이 필요
한계점:
- 아이러니한것이 삽입/삭제가 O(1)에 가능하지만, 결국 어떤노드를 삽입/삭제 할것인지에 대한 탐색과정에서 O(n)이 걸린다는 것이다. Array와 달리 논리적저장순서와 물리적 저장순서가 일치하지 않기 때문이다.
- 즉 결국 검색에도 O(n), 삽입과 삭제에도 O(n)의 시간복잡도를 갖는다.
언제 연결리스트를 사용하는가?
- 연결리스트는 Tree자료구조의 근간이 되는 자료구조로, tree에서 진가를 발휘한다.
연결리스트의 구현 by Python
# LinkedList 구현 210711
class Node(object):
def __init__(self, data, next=None):
self.data = data
self.next = next
class SingleList(object):
def __init__(self):
self.head = Node(None)
self.size = 0
def listSize(self):
return self.size
def is_empty(self):
if self.size != 0:
return False
else:
return True
def selectNode(self, idx):
if idx >= self.size:
print("Index Error")
return None
if idx == 0:
return self.head
else:
target = self.head
for cnt in range(idx):
target = target.next
return target
# 맨 왼쪽에 추가 head를 변경
def appendLeft(self, value):
# 연결리스트의 원소가 하나도없는경우 헤드만 추가되는 노드를 가리키도록
if self.is_empty():
self.head = Node(value)
else:
# 헤드가 가리키던 화살표를 추가되는 노드가 가지는 화살표로 바꿔준다.
self.head = Node(value, self.head)
self.size += 1
# 맨 오른쪽에 추가
def append(self, value):
if self.is_empty():
self.head = Node(value)
self.size += 1
else:
# 현재의 헤드다음 노드 -> 맨마지막 노드를 향해 간다.
target = self.head
while target.next != None:
target = target.next
# 맨 마지막에 추가될 노드이므로 두번째인자는 주지않는다. -> None으로 초기화
newTail = Node(value)
target.next = newTail
self.size += 1
#idx에 새로운노드 추가 ->
# idx이전노드의 next가 가리키던 값을 자신의 next로
# idx이전노드의 next는 자신으로
def insert(self, value, idx):
if self.is_empty():
self.head = Node(value)
self.size += 1
else:
target = self.selectNode(idx-1)
newNode = Node(value)
newNode.next = target.next
target.next = newNode
self.size += 1
def delete(self, idx):
if self.is_empty():
print("삭제할 노드가 없다.")
return
elif idx>= self.size:
print("인덱스에러")
return
elif idx == 0:
target = self.head
self.head = target.next
del(target)
self.size -=1
else:
target = self.selectNode(idx-1)
deltarget = target.next
target.next = deltarget.next
del(deltarget)
self.size -=1
def printlist(self):
target = self.head
while target:
if target.next != None:
print(target.data, '->', end=" ")
target = target.next
else:
print(target.data)
target = target.next # None
mylist = SingleList()
mylist.append('A')
mylist.printlist()
mylist.append('B')
mylist.printlist()
mylist.append('C')
mylist.printlist()
mylist.insert('D', 1)
mylist.printlist()
mylist.appendLeft('E')
mylist.printlist()
print(mylist.listSize())
mylist.delete(0)
mylist.printlist()
mylist.delete(3)
mylist.printlist()
mylist.delete(0)
mylist.printlist()
mylist.appendLeft('A')
mylist.printlist()
# A
# A -> B
# A -> B -> C
# A -> D -> B -> C
# E -> A -> D -> B -> C
# 5
# A -> D -> B -> C
# A -> D -> B
# D -> B
# A -> D -> B