자료구조 with Python : Stack(스택)

스택(Stack)은 마지막에 들어온 데이터가 가장 먼저 나가게 되는 후입선출(LIFO)의 자료구조이다. 스택도 결국에는 리스트의 특별한 케이스일 뿐이다. 입력과 출력이 한 쪽 끝단에서만 이루어진다.


스택의 구조

스택은 가장 위의 데이터를 알리는 top, 스택에 데이터를 삽입하는 push, top의 데이터를 내보내는 pop함수가 기본적인 구성이다. 이 외에도 peak나 is_empty를 구현하기도 하지만 이건 부수적인 것일 뿐이다.

Push(item)

push는 아이템을 스택에 삽입하는 함수이다. top은 1 증가시키고 top이 가리키는 위치에 아이템을 저장한다.

Pop()

pop은 아이템을 스택에서 꺼내는 함수이다. 단순 참조가 아니라 스택에서 아예 나오게 되는 것이다. top이 가리키는 위치의 아이템을 꺼낸 후 top을 1 감소시킨다.

두 함수 모두 시간 복잡도는 O(1)이고 스택은 원하는 아이템을 찾을 때는 top에서 부터 차례대로 찾아야해서 random 접근이 불가능하다.


스택의 예시

스택을 쉽게 설명하기 위해서 스택은 상자, 데이터는 상자와 너비가 같은 책이라고 예시를 든다 (책은 항상 눕혀서 상자에 넣는다). Top은 상자의 위에서 확인하는 것이라 생각한다.

스택이라는 상자에 처음 위에서 바라보면 상자의 밑바닥을 확인할 수 있다. 스택에 처음에는 자료구조라는 책을 넣는다. 상자를 위에서 봤을 때 우리는 자료구조가 있다는 것을 확인할 수 있다. 그 다음 운영체제 책을 넣고 컴퓨터 구조 책을 넣는다. 이 때 상자의 위에서 볼 수 있는 것은 컴퓨터 구조이다. Stack의 Top이 가리키는 것이 컴퓨터 구조라는 의미이다.

상자에서 우리가 꺼낼 수 있는 책은 마지막에 넣은 컴퓨터 구조이고 다른 책을 넣을 수 있는 공간 역시 컴퓨터 구조 위이다. 우리는 상자에서 컴퓨터구조, 운영체제, 자료구조 순으로 꺼낼 수 있으며 위에서 봤을 때 상자의 밑바닥이 보이면 책을 꺼내는 것이 불가능해진다.

빈 상자와 넣을 책이 3권일 때
자료구조 책을 상자에 넣었을 때
모든 책을 다 넣은 상태

상자에 자료구조가 있는 지 확인하려면 컴퓨터 구조 책부터 순서대로 꺼내가면서 확인을 해야한다.


Stack의 구현

이론적인 Stack (list자료형의 append(), pop() 사용하지 않음)

주소에 의한 참조가 가능하다는 조건에서 만들기 위해 dictionary 자료형을 사용했습니다. del 함수는 사전 자료형에서 시간복잡도가 1입니다.

class Stack:
    def __init__(self):     # 스택의 정의
        self.stack = dict()
        self.top = -1

    def push(self, data):   
        self.top += 1 
        self.stack[self.top] = data

    def pop(self):          
        if self.is_empty(): # 스택이 비었으면 pop이 불가능
            return "Stack is Empty"
        tmp = self.stack[self.top]
        del self.stack[self.top]
        self.top -= 1       

        return tmp

    def size(self):
        return self.top+1

    def is_empty(self):     # stack이 비어있는지 확인
        if self.top == -1:
            return True
        else:
            return False

    def peak(self):         # top이 가리키는 값
        if not self.is_empty():
            return self.stack[self.top]
        else:
            return "Stack is Empty"

    def __repr__(self):
        return repr(self.stack)

확인 코드

stack = Stack()
stack.push("자료구조")
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
stack.push("운영체제")
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
stack.push("컴퓨터구조")
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
print("pop : {}".format(stack.pop()))
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
print("pop : {}".format(stack.pop()))
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
stack.push("알고리즘")
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
print("pop : {}".format(stack.pop()))
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))
print("pop : {}".format(stack.pop()))
print("top : {}, peak : {}, stack : {}".format(stack.top, stack.peak(), stack))

결과


위의 코드는 이론적인 부분을 확인하기 위한 코드로 실제 파이썬에서는 list 자료형의 popappend를 이용하여 구현하는 것이 쉽습니다.

Stack 객체 구현 (list자료형 pop(), append() 사용 )

class Stack:
    def __init__(self):
        self.stack = []
        self.top = -1
    def push(self, data):
        self.stack.append(data)
        self.top += 1
    def pop(self):
        if self.is_empty():
            return "Stack is Empty"
        self.top -= 1
        return self.stack.pop()

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

    def is_empty(self):
        if not self.stack:
            return True
        else:
            return False

    def peak(self):
        if not self.is_empty():
            return self.stack[-1]
        else:
            return "Stack is Empty"

    def __repr__(self):
        return repr(self.stack)

확인 코드 결과


파이썬에서 스택을 굳이 직접 구현해야하는가?

파이썬의 list자료형은 위의 예제에서 봤듯이 pop()함수가 사용가능하고 push대신 append() 함수를 사용하면 스택처럼 리스트의 사용이 가능하다. 두 함수 모두 시간 복잡도는 O(1)이기 때문에 스택에서의 구현과 동일하다. top 또한 len - 1로, peak 또한 list[-1] 로 구현이 가능한 만큼 제공되는 함수를 사용하는 것이 유리하다고 생각한다.

찾는 item이 있는지 확인하는 부분이 리스트는 앞에서 확인하지만 스택은 뒤에서부터 확인한다는 점이 조금 다르긴 할 것이다.

문자열이란

문자열(String)이란 문자를 나열해 놓은 것을 의미한다. 문자는 a, b, c 같은 알파벳과, 가나다 같은 한글, 1,2,3 같은 숫자,!@#같은 특수문자 등 유니코드 내에 문자를 의미한다.

"Hello World"
"123456789"
"010-1234-5678"
"!@#$"

문자열의 표현

파이썬에서는 문자열을 쌍따옴표"로도 나타낼 수 있지만 작은 따옴표'로 나타낼 수도 있다.

str1 = "abc"
str2 = 'abc'
print(True if str1 == str2 else False)

위 예제의 print함수는 두 문자열이 같으면 True를 다르면 False를 출력하는 함수입니다. 두 문자열은 그 내부적으로는 서로 같습니다. 파이썬 내부에서는 기본적으로 문자열을 작은 따옴표로 묶여 있는 것으로 표시된다.

위의 방법 외에도 여러 줄의 문자열을 위해서 여러 줄의 문자열을 한 번에 나타내는 방법이 있다.
큰 따옴표나 작은 따옴표를 3개를 사용해서 나타낸다.
첫번째 줄

str3 = """첫 번째 줄
두 번째 줄
세 번째 줄
"""
print(str3)


이스케이프 문자

이스케이프 문자란 줄바꿈, 띄워쓰기 등 여러 기능을 위해 미리 정해놓은 문자 집합이다..
가장 자주 볼 수 있는 문자는 \n, \t, \\이 있다.

str4 = "첫번째\n두번째\n세번째"
str5 = "1\t2\t3\t4"
str6 = "8\t9\t10\t11"
print(str4)
print(str5)
print(str6)

코드 설명 코드 설명
\n 문자열 안에서 줄을 바꿀 때 사용 \r 캐리지 리턴(줄 바꿈 문자, 현재 커서를 가장 앞으로 이동)
\t 문자열 사이에 탭 간격을 줄 때 사용 \f 폼 피드(줄 바꿈 문자, 현재 커서를 다음 줄로 이동)
\\ 문자 \를 그대로 표현할 때 사용 \a 벨 소리(출력할 때 소리가 나게함)
\' 작은 따옴표를 표현할 때 사용 \b 백 스페이스
\" 큰 따옴표를 표현할 때 사용 \0 널문자

문자열의 연산

문자열 더하기(Concatenation)

문자열은 문자열끼리 더하기가 가능하다. 문자열의 덧셈은 각 요소를 더하는 것이 아니라 문자열 뒤에 문자열을 추가한다고 생각하면 된다.

str1 = "Hello"
str2 = " World"
str3 = str1 + str2
print(str3)

문자열 곱셈(반복)

문자열의 곱셈은 곱하는 수만큼의 반복을 의미한다.

str1 = 'Hello '
str2 = str1 * 3
print(str2)

 

2 * 3 = 2 + 2 + 2 인 것을 생각해보면 문자열의 곱셈이 의미하는 것을 알 수 있다. str2 = str1 + str1 + str1이므로 "Hello "라는 문자열을 3번 이어붙인 것이다.

 

 

문자열의 기본 함수에 대해서는 다음에 포스팅하겠습니다.

'프로그래밍 > Python' 카테고리의 다른 글

[Python] 소수 판별 알고리즘  (0) 2021.10.11
[Python] 리스트 함수 정리  (0) 2021.09.19
[Python] 자료형 - (3) 리스트  (0) 2021.09.14
[Python] 자료형 - (1) 숫자형  (0) 2021.09.14
[Python] 순열과 조합 itertools  (0) 2021.07.18

 

[파이썬] 자료형 - (1) 숫자형

숫자형은 우리가 사용하는 숫자(number)를 나타내는 자료형입니다.
예를 들어 1234나 -1234 같은 정수형과 12.34 같은 실수형이 대표적인 표현 방법이며 2진수, 8진수, 16진수로 나타낸 수도 수를 표현하는 방법입니다.


정수형

정수형은 우리가 가장 잘 아는 양의 정수, 음의 정수, 0을 뜻하는 자료형입니다.

a = 123
b = -178
c = 0

위에서 a, b, c 모두 정수를 의미합니다.


진법

파이썬에서 기본 제공되는 진법 표기는 2진수, 8진수, 16진수가 있습니다.

base_2 = 0b1111
base_8 = 0o0017
base_16 = 0x000F
base_10 = 15

print(base_2, type(base_2))
print(base_8, type(base_8))
print(base_16, type(base_16))
print(base_10, type(base_10))

위의 모든 값들은 print()함수에 넣게 되면 정수 값인 15를 출력하고 type() 함수의 출력을 보면 모두 int로 출력됩니다. 눈으로 보기에는 다른 수 같지만 컴퓨터의 입장에서는 모두 같은 수이기 때문입니다.

<출력 결과>


실수형

실수형은 소수점이 포함된 숫자를 의미합니다.

f1 = .1
f2 = 0.1
f3 = -3.14
f4 = 1.23e4

f1처럼 소수점 앞의 0은 생략이 가능합니다. 1.23e4지수 표현 방식이라고 합니다. $$ 1.23 * 10^4 $$을 의미하고 소문자 e, 대문자 E 모두 사용이 가능합니다. 알고리즘 문제를 풀면서 무제한 값을 설정할 때 1e9와 같은 수를 많이 쓰기도 합니다.

 

 

 

'프로그래밍 > Python' 카테고리의 다른 글

[Python] 소수 판별 알고리즘  (0) 2021.10.11
[Python] 리스트 함수 정리  (0) 2021.09.19
[Python] 자료형 - (3) 리스트  (0) 2021.09.14
[Python] 자료형 - (2) 문자열  (0) 2021.09.14
[Python] 순열과 조합 itertools  (0) 2021.07.18

[백준][파이썬] 2210번 - 숫자판 점프

문제 번호 : 2210
문제 출처 : https://www.acmicpc.net/problem/2210

 

2210번: 숫자판 점프

111111, 111112, 111121, 111211, 111212, 112111, 112121, 121111, 121112, 121211, 121212, 211111, 211121, 212111, 212121 이 가능한 경우들이다.

www.acmicpc.net



Code

import sys


def recursive(x, y, count):
    if count == 5:
        number.append(numbers[x][y])
        answer_set.add(tuple(number))
    else:
        number.append(numbers[x][y])
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if -1 < nx < 5 and -1 < ny < 5:
                recursive(nx, ny, count + 1)
    number.pop()

numbers = [sys.stdin.readline().split() for _ in range(5)]

dx = [1, -1, 0, 0]
dy = [0, 0, 1, -1]

answer_set = set()
number = []
for r in range(5):
    for c in range(5):
        recursive(r, c, 0)

print(len(answer_set))

Idea

  1. 각각의 시작점에서 가능한 방향으로 모두 방문하며 list에 숫자를 추가
  2. 숫자 리스트를 집합 자료형에 입력함으로써 중복을 제거
  3. 재귀함수가 호출 되고나서 입력받은 리스트와 같은 새로운 리스트를 만들지 않고 기존의 리스트를 append와 pop을 사용해서 스택으로 사용

집합의 요소로는 리스트가 들어갈 수 없다. 그래서 집합에 add를 할 때 리스트를 tuple이나 str로 변환하여 입력해주어야 했다. 집합은 직접 수정이 불가능한 자료형인데 리스트는 주소에 의한 수정이 가능해져서가 아닐까 추측해본다.

[백준][파이썬] 15686번 치킨 배달

문제 번호 : 15686
문제 출처 : https://www.acmicpc.net/problem/15686

 

15686번: 치킨 배달

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸

www.acmicpc.net



Code

import sys
from itertools import combinations

## 입력
n, m = map(int, input().split())
city = [list(map(int, sys.stdin.readline().split())) for _ in range(n)]

distance = []   # 집 별로 치킨 집과의 거리 저장
chickens = []   # 치킨 집의 위치와 치킨 집의 번호 저장
houses = []     # 집의 위치 저장
n_chicken = 0
for i in range(n):
    for j in range(n):
        if city[i][j] == 1:
            houses.append([i, j])
        elif city[i][j] == 2:
            chickens.append([i, j, n_chicken])
            n_chicken += 1

for i in range(len(chickens)):
    distance.append([])
    for house in houses:
        distance[i].append(abs(chickens[i][0] - house[0]) + abs(chickens[i][1] - house[1]))

cases = combinations(chickens, m)

answer = 1e9

for case in cases:
    d = []
    for i in range(m):
        d.append(distance[case[i][2]])

    d = list(map(min, zip(*d)))
    answer = min(sum(d), answer)

print(answer)

Idea

  1. N x N 크기의 배열을 모두 탐색하면서 집의 위치와 치킨 집의 위치를 모두 저장
  2. 치킨 집의 위치를 저장할 때에는 치킨 집의 number를 차례로 부여
  3. 집마다 각 치킨집과의 거리를 저장
  4. 치킨 집의 갯수에서 M개의 조합을 생성
  5. M개의 조합을 모두 탐색하며 치킨 거리가 최소인 답을 구함

이번 문제에서 zip 함수와 map 함수, 그리고 * 을 사용해서 각 거리들 중 최단 거리를 구했다.
d = list(map(min, zip(*d))) 를 해석해보겠다.
iterable앞에 *을 붙이게 되면 iterable형의 요소들을 모두 반환한다.
예시를 들자면 다음과 같다.

def print_all(a, b, c):
    print(a)
    print(b)
    print(c)

li = [1, 2, 3]
print_all(*li)

### 실행결과
# 1
# 2
# 3

코드에서 d 는 2차원 리스트이므로 여러 개의 1차원 리스트들을 반환한다.
zip함수는 여러개의 iterable이 입력으로 들어오면 index가 같은 요소들끼리 튜플로 합쳐서 반환한다. d의 각 1차원 리스트들은 각 집에서 치킨집들까지의 거리이고 zip함수를 사용하고 나면 각 치킨 집마다의 집들까지의 거리가 된다.
여기에 map함수로 각 리스트별로 min함수를 사용해서 리스트로 반환한다.
처음에 2차원 배열을 모두 돌면서 각 치킨집 별로 최소 거리를 구하는 방법을 사용했고 시간이 400ms가 나왔다. 그런 과정을 저 한 줄의 코드로 변환해서 확인한 결과 112ms로 시간이 많이 줄어들었다.

+ Recent posts