Post

데이터 구조 - 선형

데이터 구조

데이터 구조는 데이터를 구성, 처리, 검색 및 저장하기 위한 특수한 형식이다. 알고리즘을 배우기전에 데이터 구조를 먼저 학습하는 이유는 다양한 데이터 구조와 그 속성을 알면 특정 알고리즘에 가장 적합한 데이터 구조를 선택할 수 있으며, 많은 알고리즘은 특정 데이터 구조를 염두에 두고 설계되었기 때문에 데이터 구조에 대한 지식은 알고리즘을 푸는데 많은 도움이 된다. 대표적인 데이터구조에 대해 함께 알아보자. 😉

선형 데이터 구조

선형 데이터 구조는 요소가 순차적으로 배열되어 있으며, 메모리에 저장되는 방식으로 인해 한 번의 실행으로 쉽게 탐색할 수 있는 장점이 있다.

  • 주요 특징
    • 순차적 액세스: 요소는 순서대로 배열되어 하나씩 액세스된다.
    • 단일 레벨: 첫 번째 요소와 마지막 요소를 제외한 모든 요소에는 고유한 선행 요소와 후속 요소가 있다.
    • 메모리 할당: 배열과 같은 데이터 구조의 경우 메모리는 연속된 블록에 할당된다.

Arrays(배열)

배열은 인접한 메모리 위치에 유사한 데이터 유형 모음을 저장하는 데 사용되는 데이터 구조이다. 이는 일련의 요소를 저장하고 액세스하기 위한 가장 간단하고 효율적인 데이터 구조 중 하나이다.

  • 장점
    • 효율적인 액세스: 연속적인 메모리 할당으로 인해 배열을 사용하면 요소에 빠르게 액세스하고 수정할 수 있다.
    • 성능: 읽기 작업에 대한 빠른 성능을 제공한다.
    • 단순성: 배열은 사용이 간단하고 데이터 저장 및 조작이 매우 쉽다.
    • 호환성: 배열은 거의 모든 프로그래밍 언어에서 지원되며 많은 알고리즘의 기본 구성 요소이다.
  • 단점
    • 비용이 많이 드는 삽입/삭제: 배열에 요소를 삽입하거나 삭제하는 작업은 일반적으로 배열의 순서를 유지하기 위해 요소를 이동해야 하므로 비용이 많이 든다.
1
2
3
4
5
6
7
numbers = [10, 20, 30, 40, 50]

print(numbers[0])  # Output: 10
print(numbers[3])  # Output: 40

numbers[2] = 35
print(numbers)  # Output: [10, 20, 35, 40, 50]

Linked List

Linked List는 일련의 노드로 구성된 기본 데이터 구조이다. 각 노드에는 데이터와 다음 노드에 대한 참조(링크)가 포함되어 있다.

  • 장점
    • 효율적인 삽입/삭제: 노드를 추가하거나 제거하는 작업은 배열에서처럼 요소를 이동하는 것이 아니라 포인터만 변경하는 작업이므로 대체로 효율적이다.
  • 단점
    • 순차적 액세스: 노드는 첫 번째 노드부터 순차적으로 액세스해야 하며, 이는 특히 큰 목록(액세스의 경우 O(n))의 경우 시간이 오래 걸릴 수 있다.
    • 복잡성: 배열을 사용하는 것보다 더 복잡하며 오류율이 더 높다(예: 목록에 대한 참조 손실).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Node:
  def __init__(self, data):
    self.data = data
    self.next = None

class LinkedList:
  def __init__(self):
    self.head = None
    self.tail = None

  def append(self, data):
    new_node = Node(data)
    if self.head is None:
      self.head = new_node
      self.tail = new_node
      return
    self.tail.next = new_node
    self.tail = new_node

  def delete_node(self, key):
    cur_node = self.head

    if not cur_node:
      return

    if cur_node.data == key:
      self.head = cur_node.next
      self.tail = None
      return

    while cur_node and cur_node.data != key:
      prev = cur_node
      cur_node = cur_node.next

    if not cur_node:
      return

    if cur_node.next is None:
      self.tail = prev

    prev.next = cur_node.next

  def print_list(self):
    cur_node = self.head
    while cur_node:
      print(cur_node.data, end=" -> ")
      cur_node = cur_node.next
    print(None)

llist = LinkedList()

# Inserting elements
llist.append(3)
llist.append(4)

# Output the list
print("Linked List:")
llist.print_list() # Output : 3 -> 4 -> None

# Deleting an element
llist.delete_node(3)

# Output the list after deletion
print("\nLinked List after deleting 3:")
llist.print_list() # Output : 4 -> None

Stack(스택)

스택은 마지막으로 추가된 항목이 가장 먼저 제거되는 후입선출(LIFO) 원칙을 따른다. 스택은 새 항목의 추가와 기존 항목의 제거가 항상 스택의 맨 위에서 이루어지는 정렬된 항목 모음이다.

  • 장점
    • 간단하고 직관적: LIFO 원칙은 이해하고 구현하기 쉽다.
    • 효율적인 작업: Push, Pop 같은 기본 연산은 일반적으로 O(1)(상수 시간)이므로 매우 효율적이다.
    • 접근 제어: 연산을 최상위 요소로 제한하면 스택 내부에 대한 무단 액세스를 방지하여 데이터 보안을 강화할 수 있다.
  • 단점
    • 제한된 접근성: 최상위 요소에만 직접 액세스할 수 있으므로 목록이나 대기열과 같은 다른 데이터 구조보다 유연성이 떨어진다.
    • 메모리 오버헤드: 특정 구현에서 스택은 기본 스토리지 구조(예: 연결된 목록의 포인터)로 인해 추가적인 메모리 오버헤드가 발생할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Stack:
  def __init__(self):
    self.items = []

  def is_empty(self):
    return len(self.items) == 0

  def push(self, item):
    self.items.append(item)

  def pop(self):
    if not self.is_empty():
        return self.items.pop()
    else:
        return None

  def size(self):
    return len(self.items)

stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)

print(stack.pop())    # Output: 3
print(stack.size())   # Output: 2

Queue(큐)

Queue는 FIFO(선입선출) 원칙을 따르는 선형 데이터 구조이다. 이는 대기열에 추가된 첫 번째 요소가 제거되는 첫 번째 요소가 됨을 의미한다. 이는 음식점 계산대에 줄을 서서 가장 먼저 줄을 선 사람이 가장 먼저 음식을 받는 것과 유사하다.

  • 장점
    • 순서 유지: 요소가 추가된 순서대로 처리되도록 한다.
    • 효율적인 작업: 대기열에 넣기 및 빼기와 같은 기본 작업은 일반적으로 O(1)(상수 시간)으로 매우 효율적이다.
    • 광범위한 사용: 작업 일정 관리, 데이터 통신의 버퍼링과 같은 다양한 컴퓨팅 시나리오에 유용하다.
  • 단점
    • 제한된 접근성: 맨앞 및 맨뒤 요소와의 상호 작용만 허용하고 중간 요소에 대한 직접 액세스를 제한한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Queue:
  def __init__(self):
    self.items = []

  def is_empty(self):
    return len(self.items) == 0

  def enqueue(self, item):
    self.items.insert(0, item)

  def dequeue(self):
    if not self.is_empty():
      return self.items.pop()
    else:
      return None

  def size(self):
    return len(self.items)

queue = Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)

print(queue.dequeue()) # Output: 1
print(queue.size())    # Output: 2
This post is licensed under CC BY 4.0 by the author.

© zwoong. Some rights reserved.