classNode:"""링크드 리스트의 노드 클래스"""def__init__(self, data):
self.data = data # 실제 노드가 저장하는 데이터
self.next = None# 다음 노드에 대한 레퍼런스classLinkedList:"""링크드 리스트 클래스"""def__init__(self):
self.head = None# 링크드 리스트의 가장 앞 노드
self.tail = None# 링크드 리스트의 가장 뒤 노드deffind_node_with_data(self, data):"""링크드 리스트에서 탐색 연산 메소드. 단, 해당 노드가 없으면 None을 리턴한다"""
iterator = self.head
# 링크드 리스트 전체를 돈다while iterator isnotNone:
# iterator 노드의 데이터가 찾는 데이터면 iterator를 리턴한다if iterator.data == data:
return iterator
iterator = iterator.next# 다음 노드로 넘어간다defappend(self, data):"""링크드 리스트 추가 연산 메소드"""
new_node = Node(data)
# 링크드 리스트가 비어 있으면 새로운 노드가 링크드 리스트의 처음이자 마지막 노드다if self.head isNone:
self.head = new_node
self.tail = new_node
# 링크드 리스트가 비어 있지 않으면else:
self.tail.next = new_node # 가장 마지막 노드 뒤에 새로운 노드를 추가하고
self.tail = new_node # 마지막 노드를 추가한 노드로 바꿔준다def__str__(self):"""링크드 리스트를 문자열로 표현해서 리턴하는 메소드"""
res_str = "|"# 링크드 리스트 안에 모든 노드를 돌기 위한 변수. 일단 가장 앞 노드로 정의한다.
iterator = self.head
# 링크드 리스트 끝까지 돈다while iterator isnotNone:
# 각 노드의 데이터를 리턴하는 문자열에 더해준다
res_str += " {} |".format(iterator.data)
iterator = iterator.next# 다음 노드로 넘어간다return res_str
# 새로운 링크드 리스트 생성
linked_list = LinkedList()
# 여러 데이터를 링크드 리스트 마지막에 추가
linked_list.append(2)
linked_list.append(3)
linked_list.append(5)
linked_list.append(7)
linked_list.append(11)
# 데이터 2를 갖는 노드 탐색
node_with_2 = linked_list.find_node_with_data(2)
ifnot node_with_2 isNone:
print(node_with_2.data)
else:
print("2를 갖는 노드는 없습니다")
# 데이터 11을 갖는 노드 탐색
node_with_11 = linked_list.find_node_with_data(11)
ifnot node_with_11 isNone:
print(node_with_11.data)
else:
print("11을 갖는 노드는 없습니다")
# 데이터 6 갖는 노드 탐색
node_with_6 = linked_list.find_node_with_data(6)
ifnot node_with_6 isNone:
print(node_with_6.data)
else:
print("6을 갖는 노드는 없습니다")
중괄호(brace)을 사용하면 f-string 안 에 파이썬의 표현식(expression)을 삽입할 수 있는데요.
문자열 안에 어떤 변수나 표현식을 삽입할 때 f-string의 진가가 발휘되기 시작합니다.
f-string 사용 예시
main.py
from collections import deque
from subway_graph import *
defbfs(graph, start_node):"""최단 경로용 bfs 함수"""
queue = deque() # 빈 큐 생성# 모든 노드를 방문하지 않은 노드로 표시, 모든 predecessor는 None으로 초기화for station_node in graph.values():
station_node.visited = False
station_node.predecessor = None# 시작점 노드를 방문 표시한 후 큐에 넣어준다
start_node.visited = True
queue.append(start_node)
while queue: # 큐에 노드가 있을 때까지
current_station = queue.popleft() # 큐의 가장 앞 데이터를 갖고 온다for neighbor in current_station.adjacent_stations: # 인접한 노드를 돌면서ifnot neighbor.visited: # 방문하지 않은 노드면
neighbor.visited = True# 방문 표시를 하고
neighbor.predecessor = current_station # 이 노드가 어디서 왔는지 표시
queue.append(neighbor) # 큐에 넣는다defback_track(destination_node):"""최단 경로를 찾기 위한 back tracking 함수"""
res_str = ""# 리턴할 결과 문자열
temp = destination_node # 도착 노드에서 시작 노드까지 찾아가는 데 사용할 변수# 시작 노드까지 갈 때까지while temp isnotNone:
res_str = f"{temp.station_name}{res_str}"# 결과 문자열에 역 이름을 더하고
temp = temp.predecessor # temp를 다음 노드로 바꿔준다return res_str
# 테스트 코드
stations = create_station_graph("./new_stations.txt") # stations.txt 파일로 그래프를 만든다
bfs(stations, stations["을지로3가"]) # 지하철 그래프에서 을지로3가역을 시작 노드로 bfs 실행print(back_track(stations["강동구청"])) # 을지로3가에서 강동구청역까지 최단 경로 출력
subway_graph.py
classStationNode:"""지하철 역을 나타내는 역"""def__init__(self, station_name):
self.station_name = station_name
self.adjacent_stations = []
self.visited = Falsedefadd_connection(self, station):"""파라미터로 받은 역과 엣지를 만들어주는 메소드"""
self.adjacent_stations.append(station)
station.adjacent_stations.append(self)
defcreate_station_graph(input_file):
stations = {}
# 일반 텍스트 파일을 여는 코드withopen(input_file) as stations_raw_file:
for line in stations_raw_file: # 파일을 한 줄씩 받아온다
previous_station = None# 엣지를 저장하기 위한 변수
raw_data = line.strip().split("-")
for name in raw_data:
station_name = name.strip()
if station_name notin stations:
current_station = StationNode(station_name)
stations[station_name] = current_station
else:
current_station = stations[station_name]
if previous_station isnotNone:
current_station.add_connection(previous_station)
previous_station = current_station
return stations
이 줄이 너무 길어서 줄바꿈이 있는 거처럼 보이실 수도 있는데요. 이게 다 한 줄입니다. 내용을 살펴보면 서울 지하철 1호선 노선 데이터가 있죠? 소요산역부터 서동탄역까지 순서대로 저장돼 있고, "-"로 구별돼 있습니다.
stations.txt 파일의 각 줄은 하나의 지하철 노선의 데이터를 저장하고 있습니다. 이번 과제에서는 이 텍스트 파일의 바탕으로 서울 지하철역 노드들을 만들어 볼게요.
실습 결과
classStationNode:"""간단한 지하철 역 노드 클래스"""def__init__(self, station_name):
self.station_name = station_name
defcreate_station_nodes(input_file):"""input_file에서 데이터를 읽어 와서 지하철 그래프 노드들을 리턴하는 함수"""
stations = {} # 지하철 역 노드들을 담을 딕셔너리# 파라미터로 받은 input_file 파일을 연다withopen(input_file) as stations_raw_file:
for line in stations_raw_file: # 파일을 한 줄씩 받아온다
subway_line = line.strip().split("-") # 앞 뒤 띄어쓰기를 없애고 "-"를 기준점으로 데이터를 나눈다for name in subway_line:
station_name = name.strip() # 앞 뒤 띄어쓰기 없애기# 여기에 코드를 작성하세요if station_name notin stations:
cur_station = StationNode(station_name)
stations[station_name] = cur_station
return stations
stations = create_station_nodes("./stations.txt") # stations.txt 파일로 그래프 노드들을 만든다# 테스트 코드# stations에 저장한 역들 이름 출력 (채점을 위해 역 이름 순서대로 출력)for station insorted(stations.keys()):
print(stations[station].station_name)
런던에 엄청난 폭우가 쏟아진다고 가정합시다. 정말 재난 영화에서나 나올 법한 양의 비가 내려서, 고층 건물이 비에 잠길 정도입니다.
그렇게 되었을 때, 건물과 건물 사이에 얼마큼의 빗물이 담길 수 있는지 알고 싶은데요. 그것을 계산해 주는 함수 trapping_rain()을 작성해 보려고 합니다.
함수 trapping_rain()은 건물 높이 정보를 보관하는 리스트 buildings를 파라미터로 받고, 담기는 빗물의 총량을 리턴해 줍니다.
예를 들어서 파라미터 buildings로 [3, 0, 0, 2, 0, 4]가 들어왔다고 합시다. 그러면 0번 인덱스에 높이 3의 건물이, 3번 인덱스에 높이 2의 건물이, 5번 인덱스에 높이 4의 건물이 있다는 뜻입니다. 1번, 2번, 4번 인덱스에는 건물이 없습니다.
그러면 아래의 사진에 따라 총 10 만큼의 빗물이 담길 수 있습니다. 따라서 trapping_rain() 함수는 10을 리턴하는 거죠.
3차원 말고, 2차원으로 생각해 주세요! 이번에는 파라미터 buildings로 [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]가 들어왔다고 합시다. 그러면 아래의 사진에 따라 총 6 만큼의 빗물이 담길 수 있습니다. 따라서 trapping_rain() 함수는 6을 리턴하는 거죠.
리스트 [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
이 정보를 기반으로, trapping_rain() 함수를 작성해 보세요!
더 많은 공간을 쓰더라도 조금 더 효율적인 시간 복잡도로 문제를 풀어 볼까요?
실습 결과
deftrapping_rain(buildings):
total_height = 0# 총 갇히는 비의 양을 담을 변수
n = len(buildings)
# 각각 왼쪽 오른쪽 최대값 리스트 정의
left_list = [0] * n
right_list = [0] * n
# buildings 리스트 각 인덱스 별로 왼쪽으로의 최댓값을 저장한다
left_list[0] = buildings[0]
for i inrange(1, n):
left_list[i] = max(left_list[i-1], buildings[i-1])
# buildings 리스트 각 인덱스 별로 오른쪽으로의 최댓값을 저장한다
right_list[-1] = buildings[-1]
for i inrange(n - 2, -1, -1):
right_list[i] = max(right_list[i+1], buildings[i+1])
# 저장한 값들을 이용해서 총 갇히는 비의 양을 계산한다for i inrange(1, n-1):
# 현재 인덱스에 빗물이 담길 수 있는 높이
upper_bound = min(right_list[i], left_list[i])
# 현재 인덱스에 담기는 빗물의 양을 계산# 만약 upper_bound가 현재 인덱스 건물보다 높지 않다면, 현재 인덱스에 담기는 빗물은 0
total_height += max(0, upper_bound - buildings[i])
return total_height
print(trapping_rain([3, 0, 0, 2, 0, 4]))
print(trapping_rain([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]))
(N + 1)의 크기인 리스트에, 1부터 N까지의 임의의 자연수가 요소로 할당되어 있습니다. 그렇다면 어떤 수는 꼭 한 번은 반복되겠지요.
예를 들어 [1, 3, 4, 2, 5, 4]와 같은 리스트 있을 수도 있고, [1, 1, 1, 6, 2, 2, 3]과 같은 리스트가 있을 수도 있습니다. (몇 개의 수가 여러 번 중복되어 있을 수도 있습니다.)
이러한 리스트에서 반복되는 요소를 찾아내려고 합니다.
중복되는 어떠한 수 ‘하나’만 찾아내도 됩니다. 즉 [1, 1, 1, 6, 2, 2, 3]의 예시에서 1, 2를 모두 리턴하지 않고, 1 또는 2 하나만 리턴하게 하면 됩니다.
이번 과제에서는 두 가지의 제약이 있습니다.
O(n)이상의 공간을 사용할 수 없습니다. 즉 사전이나 리스트와 같이 인풋 리스트의 길이에 비례하는 공간 저장 도구를 사용할 수 없습니다!
인풋으로 받는 리스트 some_list의 요소들을 바꾸거나 변형할 수 없습니다.
실습 결과
deffind_same_number(some_list, start = 1, end = None):if end == None:
end = len(some_list)
# 반복 요소를 찾으면 리턴한다if start == end:
return start
# 중간 지점을 구한다
mid = (start + end) // 2# 왼쪽 범위의 숫자를 센다. 오른쪽은 리스트 길이에서 왼쪽 길이를 빼면 되기 때문에 세지 않는다
left_count = 0for element in some_list:
if start <= element and element <= mid:
left_count += 1# 왼쪽과 오른쪽 범위중 과반 수 이상의 숫자가 있는 범위 내에서 탐색을 다시한다if left_count > mid - start + 1:
return find_same_number(some_list, start, mid)
return find_same_number(some_list, mid + 1, end)
print(find_same_number([1, 4, 3, 5, 3, 2]))
print(find_same_number([4, 1, 5, 2, 3, 5]))
print(find_same_number([5, 2, 3, 4, 1, 6, 7, 8, 9, 3]))
영훈이는 출근할 때 계단을 통해 사무실로 가는데요. 급할 때는 두 계단씩 올라가고 여유 있을 때는 한 계단씩 올라갑니다. 결국 계단을 오를 수 있는 모든 방법으로 계단을 올라갔는데요.
이제 다르게 계단을 올라갈 수는 없을까 고민하던 영훈이는 특이한 방법으로 계단을 오르려고 합니다.
가령 계단을 한 번에 1, 2, 4 칸씩 올라가 보는 건데요. 예를 들어서 계단을 4개를 올라가야 되면:
1, 1, 1, 1
2, 1, 1
1, 2, 1
1, 1, 2
2, 2
4
총 6개 방법이 있네요.
함수 설명
함수 staircase()는 파라미터로 총 계단 수 n 그리고 한 번에 올라갈 수 있는 계단 수를 possible_steps로 받고, 올라갈 수 있는 방법의 수를 효율적으로 찾아서 리턴합니다.
그러니까 n이 3, possible_steps 가 [1, 2, 3]이면, 계단 총 3칸을 1, 2, 3칸씩 갈 수 있을 때 오르는 방법의 수를 구하는 거죠. 단, possible_steps에는 항상 1이 포함된다고 가정합니다.
실습 결과
# 높이 n개의 계단을 올라가는 방법을 리턴한다defstaircase(stairs, possible_steps):# 계단 높이가 0 이거나 1 이면 올라가는 방법은 한 가지밖에 없다
number_of_ways = [1, 1]
# 이 변수들을 업데이트해 주며 n 번째 계단을 오르는 방법의 수를 구한다.for height inrange(2, stairs + 1):
number_of_ways.append(0)
for step in possible_steps:
# 음수 계단 수는 존재하지 않기 때문에 무시합니다if height - step >= 0:
number_of_ways[height] += number_of_ways[height - step]
return number_of_ways[stairs]
print(staircase(5, [1, 2, 3]))
print(staircase(6, [1, 2, 3]))
print(staircase(7, [1, 2, 4]))
print(staircase(8, [1, 3, 5]))