13 minute read

배열(Array)

  • 파이썬(Python)에서 다음과 같이 배열을 초기화할 수 있다.
# [0, 0, 0, 0, 0]
n = 5
arr = [0] * n
print(arr)

# [0, 1, 2, 3, 4]
n = 5
arr = [i for i in range(n)]
print(arr)
[0, 0, 0, 0, 0]
[0, 1, 2, 3, 4]
n = 3
m = 5
arr = [[0] * m for i in range(n)]
print(arr)
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
n = 3
m = 5
arr = [[i * m + j for j in range(m)] for i in range(n)]
print(arr)
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]
n = 3
m = 5
arr1 = [[0] * m] * n
arr2 = [[0] * m for i in range(n)]

arr1[1][3] = 7
arr2[1][3] = 7

print(arr1)
print(arr2)
[[0, 0, 0, 7, 0], [0, 0, 0, 7, 0], [0, 0, 0, 7, 0]]
[[0, 0, 0, 0, 0], [0, 0, 0, 7, 0], [0, 0, 0, 0, 0]]
arr = [0, 1, 2, 3, 4, 5, 6, 7, 8]
print(arr)
[0, 1, 2, 3, 4, 5, 6, 7, 8]

연결 리스트(Linked List)

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


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

    # 가장 뒤에 노드 삽입
    def append(self, data):
        # 헤드(head)가 비어있는 경우
        if self.head == None:
            self.head = Node(data)
            return
        # 마지막 위치에 새로운 노드 추가
        cur = self.head
        while cur.next is not None:
            cur = cur.next
        cur.next = Node(data)

    # 모든 노드를 하나씩 출력
    def show(self):
        cur = self.head
        while cur is not None:
            print(cur.data, end=" ")
            cur = cur.next

    # 특정 인덱스(index)의 노드 찾기
    def search(self, index):
        node = self.head
        for _ in range(index):
            node = node.next
        return node

    # 특정 인덱스(index)에 노드 삽입
    def insert(self, index, data):
        new = Node(data)
        # 첫 위치에 추가하는 경우
        if index == 0:
            new.next = self.head
            self.head = new
            return
        # 삽입할 위치의 앞 노드
        node = self.search(index - 1)
        next = node.next
        node.next = new
        new.next = next

    # 특정 인덱스(index)의 노드 삭제
    def remove(self, index):
        # 첫 위치를 삭제하는 경우
        if index == 0:
            self.head = self.head.next
            return
        # 삭제할 위치의 앞 노드
        front = self.search(index - 1)
        front.next = front.next.next


linked_list = LinkedList()
data_list = [3, 5, 9, 8, 5, 6, 1, 7]

for data in data_list:
    linked_list.append(data)

print("전체 노드 출력:", end=" ")
linked_list.show()

linked_list.insert(4, 4)
print("\n전체 노드 출력:", end=" ")
linked_list.show()

linked_list.remove(7)
print("\n전체 노드 출력:", end=" ")
linked_list.show()

linked_list.insert(7, 2)
print("\n전체 노드 출력:", end=" ")
linked_list.show()
전체 노드 출력: 3 5 9 8 5 6 1 7 
전체 노드 출력: 3 5 9 8 4 5 6 1 7 
전체 노드 출력: 3 5 9 8 4 5 6 7 
전체 노드 출력: 3 5 9 8 4 5 6 2 7 

파이썬에서의 리스트(List)

arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(arr[4]) # 인덱싱(indexing)

# 저장(storing)
arr[7] = 10

# 뒤에 붙이기(append)
arr.append(10)
print(arr)

# 뒤에서 꺼내기(pop)
arr.pop()
print(arr)

# 길이(length)
print(len(arr))

# 배열 비우기(clear)
arr.clear()
print(arr)
4
[0, 1, 2, 3, 4, 5, 6, 10, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 10, 8, 9]
10
[]
arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
new_arr = arr[2:7] # 슬라이싱(slicing)
print(new_arr)

arr1 = [0, 1, 2, 3, 4]
arr2 = [5, 6, 7, 8, 9]
arr1.extend(arr2) # 확장(extend)
print(arr1)

arr = [0, 1, 2, 3, 4]
arr.insert(3, 7) # 삽입(insertion)
print(arr)

del arr[3] # 삭제(delete)
print(arr)

data = {7, 8, 9}
arr = list(data) # 다른 자료구조로 리스트 만들기
print(arr)
[2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 7, 3, 4]
[0, 1, 2, 3, 4]
[8, 9, 7]
arr = [0, 1, 2, 3, 4]

print(3 in arr) # 존재 여부(in)
print(7 not in arr) # 비존재 여부(not in)

arr.pop(1) # 인덱스 1에 해당하는 원소 꺼내기(pop)
print(arr)

arr.remove(3) # 리스트의 특정 원소 삭제(remove)
print(arr)

new_arr = arr.copy() # 복제(copy)
print(new_arr)
True
True
[0, 2, 3, 4]
[0, 2, 4]
[0, 2, 4]
arr = [3, 5, 4, 1, 2]

print(min(arr)) # 최소(min)
print(max(arr)) # 최대(max)

for x in arr: # 원소 순회(iteration)
    print(x, end=" ")
print()

print(arr * 2) # 리스트 반복하여 곱하기(multiply)

arr.sort() # 정렬(sorting)
print(arr)
1
5
3 5 4 1 2 
[3, 5, 4, 1, 2, 3, 5, 4, 1, 2]
[1, 2, 3, 4, 5]

스택(Stack) - 리스트 자료형을 이용한 구현

  • 가장 마지막에 들어온 원소가 가장 먼저 추출되는 자료구조다.
class Stack:
    def __init__(self):
        self.stack = []

    def push(self, data):
        # 마지막 위치에 원소 삽입
        self.stack.append(data)

    def pop(self):
        if self.is_empty():
            return None
        # 마지막 원소 추출
        return self.stack.pop()

    def top(self):
        if self.is_empty():
            return None
        # 마지막 원소 반환
        return self.stack[-1]

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


stack = Stack()
arr = [9, 7, 2, 5, 6, 4, 2]
for x in arr:
    stack.push(x)

while not stack.is_empty():
    print(stack.pop())
2
4
6
5
2
7
9

스택(Stack) - 연결 리스트를 이용한 구현

  • 가장 마지막에 들어온 원소가 가장 먼저 추출되는 자료구조다.
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


class Stack:
    def __init__(self):
        self.head = None

    # 원소 삽입
    def push(self, data):
        node = Node(data)
        node.next = self.head
        self.head = node

    # 원소 추출하기
    def pop(self):
        if self.is_empty():
            return None

        # 머리(head) 위치에서 노드 꺼내기
        data = self.head.data
        self.head = self.head.next

        return data

    # 최상위 원소(top)
    def top(self):
        if self.is_empty():
            return None
        return self.head.data

    # 먼저 추출할 원소부터 출력
    def show(self):
        cur = self.head
        while cur:
            print(cur.data, end=" ")
            cur = cur.next

    # 스택이 비어있는지 확인
    def is_empty(self):
        return self.head is None


stack = Stack()
arr = [9, 7, 2, 5, 6, 4, 2]
for x in arr:
    stack.push(x)
stack.show()
print()

while not stack.is_empty():
    print(stack.pop())
2 4 6 5 2 7 9 
2
4
6
5
2
7
9

큐(Queue) - 연결 리스트를 이용한 구현

  • 가장 먼저 삽입된 원소가 먼저 추출되는 자료구조다.
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


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

    def enqueue(self, data):
        node = Node(data)
        if self.head == None:
            self.head = node
            self.tail = node
        # 꼬리(tail) 위치에 새로운 노드 삽입
        else:
            self.tail.next = node
            self.tail = self.tail.next

    def dequeue(self):
        if self.head == None:
            return None

        # 머리(head) 위치에서 노드 꺼내기
        data = self.head.data
        self.head = self.head.next

        return data

    def show(self):
        cur = self.head
        while cur:
            print(cur.data, end=" ")
            cur = cur.next


queue = Queue()
data_list = [3, 5, 9, 8, 5, 6, 1, 7]

for data in data_list:
    queue.enqueue(data)

print("\n전체 노드 출력:", end=" ")
queue.show()

print("\n[원소 삭제]")
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())

print("[원소 삽입]")
queue.enqueue(2)
queue.enqueue(5)
queue.enqueue(3)

print("전체 노드 출력:", end=" ")
queue.show()
전체 노드 출력: 3 5 9 8 5 6 1 7 
[원소 삭제]
3
5
9
[원소 삽입]
전체 노드 출력: 8 5 6 1 7 2 5 3 
  • 큐(queue)의 구현 방식에 따른 연산 속도를 비교할 수 있다.
import time

data_list = [i for i in range(100000)]

start_time = time.time()

queue = []
for data in data_list:
    queue.append(data)
while queue:
    queue.pop(0)

print(f"Elapsed time: {time.time() - start_time} seconds.")
print(queue)

start_time = time.time()

queue = Queue()
for data in data_list:
    queue.enqueue(data)
while queue.head != None:
    queue.dequeue()

print(f"Elapsed time: {time.time() - start_time} seconds.")
queue.show()
Elapsed time: 3.0279040336608887 seconds.
[]
Elapsed time: 0.7380266189575195 seconds.

덱(Deque) - 파이썬 라이브러리를 이용한 구현

  • 왼쪽과 오른쪽 모두에서 삽입과 삭제할 수 있는 자료구조다.
from collections import deque


d = deque()
arr = [5, 6, 7, 8]
for x in arr:
    d.append(x)
arr = [4, 3, 2, 1]
for x in arr:
    d.appendleft(x)
print(d)

while d:
    print(d.popleft())

arr = [1, 2, 3, 4, 5, 6, 7, 8]
for x in arr:
    d.appendleft(x)
print(d)

while True:
    print(d.pop())
    if not d:
        break
    print(d.popleft())
    if not d:
        break
deque([1, 2, 3, 4, 5, 6, 7, 8])
1
2
3
4
5
6
7
8
deque([8, 7, 6, 5, 4, 3, 2, 1])
1
8
2
7
3
6
4
5

덱(Deque) - 연결 리스트를 이용한 구현

  • 왼쪽과 오른쪽 모두에서 삽입과 삭제할 수 있는 자료구조다.
class Node:
    def __init__(self, data):
        self.data = data
        self.prev = None
        self.next = None


class Deque:
    def __init__(self):
        self.front = None
        self.rear = None
        self.size = 0

    def appendleft(self, data):
        node = Node(data)
        if self.front == None:
            self.front = node
            self.rear = node
        else:
            node.next = self.front
            self.front.prev = node
            self.front = node
        self.size += 1

    def append(self, data):
        node = Node(data)
        if self.rear == None:
            self.front = node
            self.rear = node
        else:
            node.prev = self.rear
            self.rear.next = node
            self.rear = node
        self.size += 1

    def popleft(self):
        if self.size == 0:
            return None
        # 앞에서 노드 꺼내기
        data = self.front.data
        self.front = self.front.next
        # 삭제로 인해 노드가 하나도 없는 경우
        if self.front == None:
            self.rear = None
        else:
            self.front.prev = None
        self.size -= 1
        return data

    def pop(self):
        if self.size == 0:
            return None
        # 뒤에서 노드 꺼내기
        data = self.rear.data
        self.rear = self.rear.prev
        # 삭제로 인해 노드가 하나도 없는 경우
        if self.rear == None:
            self.front = None
        else:
            self.rear.next = None
        self.size -= 1
        return data

    def front(self):
        if self.size == 0:
            return None
        return self.front.data

    def rear(self):
        if self.size == 0:
            return None
        return self.rear.data

    # 앞에서부터 원소 출력
    def show(self):
        cur = self.front
        while cur:
            print(cur.data, end=" ")
            cur = cur.next


d = Deque()
arr = [5, 6, 7, 8]
for x in arr:
    d.append(x)
arr = [4, 3, 2, 1]
for x in arr:
    d.appendleft(x)
d.show()

print()
while d.size != 0:
    print(d.popleft())

arr = [1, 2, 3, 4, 5, 6, 7, 8]
for x in arr:
    d.appendleft(x)
d.show()

print()
while True:
    print(d.pop())
    if d.size == 0:
        break
    print(d.popleft())
    if d.size == 0:
        break
1 2 3 4 5 6 7 8 
1
2
3
4
5
6
7
8
8 7 6 5 4 3 2 1 
1
8
2
7
3
6
4
5

이진 탐색 트리(Binary Search Tree)

from collections import deque


class Node:
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None


class BinarySearchTree:
    def __init__(self):
        self.root = None

    def search(self, node, key):
        return self._search(self.root, key)

    def _search(self, node, key):
        if node is None or node.key == key:
            return node

        # 현재 노드의 key보다 작은 경우
        if node.key > key:
            return self._search(node.left, key)
        # 현재 노드의 key보다 큰 경우
        elif node.key < key:
            return self._search(node.right, key)

    def insert(self, key):
        self.root = self._insert(self.root, key)

    def _insert(self, node, key):
        if node is None:
            return Node(key)

        # 현재 노드의 key보다 작은 경우
        if node.key > key:
            node.left = self._insert(node.left, key)
        # 현재 노드의 key보다 큰 경우
        elif node.key < key:
            node.right = self._insert(node.right, key)

        return node

    def delete(self, key):
        self.root = self._delete(self.root, key)

    def _delete(self, node, key):
        if node is None:
            return None

        # 현재 노드의 key보다 작은 경우
        if node.key > key:
            node.left = self._delete(node.left, key)
        # 현재 노드의 key보다 큰 경우
        elif node.key < key:
            node.right = self._delete(node.right, key)
        # 삭제할 노드를 찾은 경우
        else:
            # 왼쪽 자식이 없는 경우
            if node.left is None:
                return node.right
            # 오른쪽 자식이 없는 경우
            elif node.right is None:
                return node.left
            # 왼쪽과 오른쪽 자식 모두 있는 경우
            node.key = self._get_min(node.right)
            node.right = self._delete(node.right, node.key)

        return node

    def _get_min(self, node):
        key = node.key
        while node.left:
            key = node.left.key
            node = node.left
        return key

    def preorder(self):
        self._preorder(self.root)

    def _preorder(self, node):
        if node:
            print(node.key, end=' ')
            self._preorder(node.left)
            self._preorder(node.right)

    def inorder(self):
        self._inorder(self.root)

    def _inorder(self, node):
        if node:
            self._inorder(node.left)
            print(node.key, end=' ')
            self._inorder(node.right)

    def postorder(self):
        self._postorder(self.root)

    def _postorder(self, node):
        if node:
            self._postorder(node.left)
            self._postorder(node.right)
            print(node.key, end=' ')

    def levelorder(self):
        return self._levelorder(self.root)

    def _levelorder(self, node):
        if node is None:
            return

        result = []

        queue = deque()
        queue.append((0, node))  # (level, node)

        while queue:
            level, node = queue.popleft()
            if node:
                result.append((level, node.key))
                queue.append((level + 1, node.left))
                queue.append((level + 1, node.right))

        for level, key in result:
            print(f"level: {level}, key: {key}")

    def to_list(self):
        return self._to_list(self.root)

    def _to_list(self, node):
        if node is None:
            return []
        return self._to_list(node.left) + [node.key] + self._to_list(
            node.right)


arr = [7, 4, 5, 9, 6, 3, 2, 8]
bst = BinarySearchTree()
for x in arr:
    bst.insert(x)
print('전위 순회:', end=' ')
bst.preorder()
print('\n중위 순회:', end=' ')
bst.inorder()
print('\n후위 순회:', end=' ')
bst.postorder()
print('\n[레벨 순회]')
bst.levelorder()

bst.delete(7)
print('\n전위 순회:', end=' ')
bst.preorder()
print('\n중위 순회:', end=' ')
bst.inorder()
print('\n후위 순회:', end=' ')
bst.postorder()
print('\n[레벨 순회]')
bst.levelorder()

bst.delete(4)
print('\n전위 순회:', end=' ')
bst.preorder()
print('\n중위 순회:', end=' ')
bst.inorder()
print('\n후위 순회:', end=' ')
bst.postorder()
print('\n[레벨 순회]')
bst.levelorder()

bst.delete(3)
print('\n전위 순회:', end=' ')
bst.preorder()
print('\n중위 순회:', end=' ')
bst.inorder()
print('\n후위 순회:', end=' ')
bst.postorder()
print('\n[레벨 순회]')
bst.levelorder()

print(bst.to_list())
전위 순회: 7 4 3 2 5 6 9 8 
중위 순회: 2 3 4 5 6 7 8 9 
후위 순회: 2 3 6 5 4 8 9 7 
[레벨 순회]
level: 0, key: 7
level: 1, key: 4
level: 1, key: 9
level: 2, key: 3
level: 2, key: 5
level: 2, key: 8
level: 3, key: 2
level: 3, key: 6

전위 순회: 8 4 3 2 5 6 9 
중위 순회: 2 3 4 5 6 8 9 
후위 순회: 2 3 6 5 4 9 8 
[레벨 순회]
level: 0, key: 8
level: 1, key: 4
level: 1, key: 9
level: 2, key: 3
level: 2, key: 5
level: 3, key: 2
level: 3, key: 6

전위 순회: 8 5 3 2 6 9 
중위 순회: 2 3 5 6 8 9 
후위 순회: 2 3 6 5 9 8 
[레벨 순회]
level: 0, key: 8
level: 1, key: 5
level: 1, key: 9
level: 2, key: 3
level: 2, key: 6
level: 3, key: 2

전위 순회: 8 5 2 6 9 
중위 순회: 2 5 6 8 9 
후위 순회: 2 6 5 9 8 
[레벨 순회]
level: 0, key: 8
level: 1, key: 5
level: 1, key: 9
level: 2, key: 2
level: 2, key: 6
[2, 5, 6, 8, 9]

힙(Heap)

  • 우선순위 큐(priority queue) 구현 목적으로 사용된다.
class Heap(object):
    def __init__(self):
        # 첫번째 원소는 사용하지 않음
        self.arr = [None]

    # 원소 삽입(push)
    def push(self, x):
        # 마지막 위치에 원소를 삽입
        self.arr.append(x)
        # 첫 원소인 경우 종료
        if len(self.arr) == 2:
            return
        # 값의 크기를 비교하며 부모를 타고 올라감
        i = len(self.arr) - 1
        while True:
            parent = i // 2
            # 작은 값을 부모 쪽으로 계속 이동
            if 1 <= parent and self.arr[parent] > self.arr[i]:
                self.arr[parent], self.arr[i] = self.arr[i], self.arr[parent]
                i = parent
            else:
                break

    # 원소 추출(pop)
    def pop(self):
        # 마지막 원소
        i = len(self.arr) - 1
        # 남은 원소가 없다면 종료
        if i < 1:
            return None
        # 루트 원소와 마지막 원소를 교체하여, 마지막 원소 추출
        self.arr[1], self.arr[i] = self.arr[i], self.arr[1]
        result = self.arr.pop()
        # 루트(root)에서부터 원소 정렬
        self.heapify()
        return result

    # 루트(root)에서부터 자식 방향으로 내려가며 재정렬
    def heapify(self):
        # 남은 원소가 1개 이하라면 종료
        if len(self.arr) <= 2:
            return
        # 루트 원소
        i = 1
        while True:
            # 왼쪽 자식
            child = 2 * i
            # 왼쪽 자식과 오른쪽 자식 중 더 작은 것을 선택
            if child + 1 < len(self.arr):
                if self.arr[child] > self.arr[child + 1]:
                    child += 1
            # 더 이상 자식이 없거나, 적절한 위치를 찾은 경우
            if child >= len(self.arr) or self.arr[child] > self.arr[i]:
                break
            # 원소를 교체하며, 자식 방향으로 내려가기
            self.arr[i], self.arr[child] = self.arr[child], self.arr[i]
            i = child
arr = [9, 1, 5, 4, 3, 8, 7]
heap = Heap()

for x in arr:
    heap.push(x)

while True:
    x = heap.pop()
    if x == None:
        break
    print(x, end=" ")
1 3 4 5 7 8 9 
import random
import time


# N개의 무작위 데이터 생성
arr = []
n = 100000
for _ in range(n):
	arr.append(random.randint(0, 1000000))

# 시간 측정 시작
start_time = time.time()

# 힙에 모든 원소 삽입
heap = Heap()
for x in arr:
	heap.push(x)

# 힙에서 모든 원소 추출
result = []
while True:
	x = heap.pop()
	if x == None:
		break
	result.append(x)

# 시간 측정 종료
print(f"Elapsed time: {time.time() - start_time} seconds.")

# 오름차순 정렬 여부 확인
ascending = True
for i in range(n - 1):
	if result[i] > result[i + 1]:
		ascending = False
print("Sorted:", ascending)

# 가장 작은 5개 원소와 가장 큰 5개 원소 출력
print(result[:5])
print(result[-5:])
Elapsed time: 2.883451223373413 seconds.
Sorted: True
[5, 49, 64, 69, 77]
[999955, 999959, 999970, 999971, 999994]