실습
by Ungbae
Naive Matching

def naive_matching(text, pattern):
n = len(text)
m = len(pattern)
comparison_count = 0
match_count = 0
# i는 텍스트에서 시작 위치 (0-indexed)
for i in range(n - m + 1):
# 현재 위치에서 패턴과 매칭 시도
is_match = True
for j in range(m):
comparison_count += 1 # 비교 횟수 증가
if text[i + j] != pattern[j]:
is_match = False
break # 불일치 발견 시 즉시 중단
if is_match:
match_count += 1
return comparison_count, match_count
# 입력 받기
text = input().strip()
pattern = input().strip()
# Naive 매칭 실행
comparisons, matches = naive_matching(text, pattern)
# 결과 출력
print(comparisons, matches)
Basic Rabin-Karp

import sys
def solve():
# 입력을 읽어옵니다. (공백과 줄바꿈 모두 처리)
input_data = sys.stdin.read().split()
# 입력이 부족한 경우 예외 처리
if len(input_data) < 2:
return
text = input_data[0]
pattern = input_data[1]
n = len(text)
m = len(pattern)
# 예외 처리: 패턴이 본문보다 긴 경우
if m > n:
print(0)
return
# 문자 매핑 함수 (a=0, b=1, ... j=9)
def to_int(char):
return ord(char) - ord('a')
d = 10
p = 0
a = 0
# 가장 높은 자릿수의 가중치 계산 (d^(m-1))
h = d ** (m - 1)
# 1. 초기 해시값 계산 (첫 번째 윈도우 & 패턴)
for i in range(m):
p = d * p + to_int(pattern[i])
a = d * a + to_int(text[i])
match_count = 0
results = []
# 2. 루프 실행 (1 부터 n-m+1 까지)
# 파이썬 range는 0부터 시작하므로 0 ~ n-m 로 매핑됩니다.
for i in range(n - m + 1):
# (1) 위치에서 a 출력
# 주의: 업데이트 전의 값을 먼저 출력합니다.
results.append(str(a))
# if(i != 1) 조건에 해당 (파이썬에서는 i != 0)
# 이전 윈도우의 값을 바탕으로 현재 윈도우의 해시값 업데이트
if i != 0:
# 제거될 문자: text[i-1]
# 추가될 문자: text[i+m-1]
prev_char = to_int(text[i-1])
next_char = to_int(text[i+m-1])
# 롤링 해시 공식: d * (이전값 - 제거문자*h) + 추가문자
a = d * (a - prev_char * h) + next_char
# 매칭 확인
if p == a:
match_count += 1
# 결과 출력 (숫자 사이 공백, 마지막에 개수)
print(" ".join(results) + " " + str(match_count))
if __name__ == "__main__":
solve()
Rabin-Karp

import sys
# 문제에서 정의한 mod 함수 구현
def custom_mod(x, y):
return ((x % y) + y) % y
def to_int(char):
return ord(char) - ord('a')
def solve():
# 입력 받기
input_data = sys.stdin.read().split()
# 입력 유효성 검사
if len(input_data) < 2:
return
text = input_data[0]
pattern = input_data[1]
n = len(text)
m = len(pattern)
# 패턴이 본문보다 긴 경우 처리
if m > n:
print("0") # 혹은 아무것도 출력하지 않거나 상황에 맞춰 처리
return
# 상수 설정
d = 26
q = 113
# 초기 해시값 계산
p = 0 # 패턴의 해시
b = 0 # 본문 첫 윈도우의 해시
for i in range(m):
p = custom_mod(d * p + to_int(pattern[i]), q)
b = custom_mod(d * b + to_int(text[i]), q)
# d^(m-1) mod q 미리 계산
h = 1
for _ in range(m - 1):
h = custom_mod(h * d, q)
results = []
match_count = 0
# 메인 루프 (0 부터 n-m 까지)
for i in range(n - m + 1):
# (1) 위치에서 b 출력 (현재 해시값 기록)
results.append(str(b))
# 첫 번째 루프가 아니면 롤링 해시 업데이트
# 주의: 문제의 pseudo-code 흐름상, '현재 윈도우 검사' 전에 '이전 값을 출력'하고
# '업데이트' 하는 로직이 섞여 있어, i=0일 때는 업데이트를 건너뜁니다.
if i != 0:
prev_char = to_int(text[i-1])
next_char = to_int(text[i+m-1])
# b_i = (d * (b_{i-1} - h * A[i-1]) + A[i+m-1]) mod q
# 식을 단계별로 나누어 mod 적용
temp = custom_mod(b - custom_mod(h * prev_char, q), q)
b = custom_mod(d * temp + next_char, q)
# 해시값 비교 및 문자열 실제 비교
if p == b:
# 해시 충돌 가능성이 있으므로 실제 문자열이 같은지 확인
if text[i : i+m] == pattern:
match_count += 1
# 결과 출력
print(" ".join(results) + " " + str(match_count))
if __name__ == "__main__":
solve()
KMP

import sys
def solve():
# 입력을 읽어옵니다.
input_data = sys.stdin.read().splitlines()
# 입력 데이터 유효성 확인
if len(input_data) < 2:
return
# 첫 줄: 검색할 문장 (Text)
# 둘째 줄: 찾고자 하는 패턴 (Pattern)
text_origin = input_data[0].strip()
pattern_origin = input_data[1].strip()
n = len(text_origin)
m = len(pattern_origin)
# 1-based Indexing 구현을 위해 앞에 더미 문자 추가
A = " " + text_origin
P = " " + pattern_origin
# 1. 전처리 (Preprocessing): pi 테이블 생성
# pi[j]: j번 인덱스에서 매칭 실패 시 이동할 패턴의 인덱스
# 표준 LPS(Longest Prefix Suffix) 배열을 구한 뒤 문제의 pi 형식으로 변환합니다.
# 표준 LPS 계산 (0-based 기준 pattern_origin 사용)
lps = [0] * m
length = 0
i = 1
while i < m:
if pattern_origin[i] == pattern_origin[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
# 문제의 로직(j <- pi[j])에 맞는 pi 배열 생성 (크기 m+2)
# 매칭 실패 시 돌아갈 위치는 (직전까지 일치한 길이 + 1)이 됩니다.
pi = [0] * (m + 2)
pi[1] = 0 # j=1에서 실패하면 0으로 가서 i를 증가시킴
for k in range(2, m + 2):
# k 위치에서의 pi값은 (k-1) 길이까지의 부분문자열의 LPS 길이 + 1
# lps 배열 인덱스는 k-2에 해당
pi[k] = lps[k-2] + 1
# 2. KMP 알고리즘 수행 (유사코드 로직 준수)
i = 1 # 본문 포인터
j = 1 # 패턴 포인터
cnt = 0 # 비교 횟수
found_any = False # 매칭 성공 여부
while i <= n:
cnt += 1 # while문 진입할 때마다 카운트 증가
# j==0 이거나 문자가 일치하면 전진
if j == 0 or A[i] == P[j]:
i += 1
j += 1
else:
# 불일치 시 pi 테이블을 이용해 j 이동
j = pi[j]
# 패턴을 모두 찾은 경우 (j가 m+1에 도달)
if j == m + 1:
print(cnt) # 현재 비교 횟수 출력
found_any = True
j = pi[j] # 다음 매칭을 위해 j 위치 재조정
if not found_any:
print("fail")
if __name__ == "__main__":
solve()
Boyer-Moore

import sys
def solve():
# 입력을 읽어옵니다.
input_data = sys.stdin.read().split()
if len(input_data) < 2:
return
text = input_data[0]
pattern = input_data[1]
n = len(text)
m = len(pattern)
# 예외 처리: 패턴이 본문보다 긴 경우
if m > n:
print("0 0")
return
# 1. Jump 테이블 계산 (computeJump)
# 모든 가능한 문자에 대해 기본 이동 거리는 패턴의 길이(m)로 설정
# 입력이 알파벳 소문자라고 가정하면 딕셔너리를 사용하여 유연하게 처리 가능
jump = {}
# 패턴의 0번째부터 m-2번째 문자까지에 대해 이동 거리 계산
# 마지막 문자(m-1)는 테이블 계산에서 제외 (단, 앞에 같은 문자가 있으면 그 값 유지)
for k in range(m - 1):
jump[pattern[k]] = m - 1 - k
# 2. Boyer-Moore-Horspool 검색 수행
i = 0
cnt = 0 # (1) 위치에서의 카운트
match_count = 0 # 발견된 패턴 개수
while i <= n - m:
j = m - 1
k = i + m - 1
# 뒤에서부터 앞으로 문자 비교
# 유사코드: while(j > 0 and P[j] == A[k])
# 파이썬(0-based): j가 0보다 크거나 같아야 함
while j >= 0 and pattern[j] == text[k]:
# (1) 여기서 카운팅 한다.
# 루프 조건(문자 일치)을 만족해야만 이곳에 도달함
cnt += 1
j -= 1
k -= 1
# j가 -1이 되면 모든 문자가 일치했다는 의미
if j == -1:
match_count += 1
# 이동 (Shift)
# i = i + jump[A[i+m-1]]
# 텍스트 윈도우의 가장 오른쪽 문자(last_char)를 기준으로 점프
last_char = text[i + m - 1]
# jump 테이블에 없는 문자는 m만큼 이동
shift = jump.get(last_char, m)
i += shift
# 결과 출력
print(f"{cnt} {match_count}")
if __name__ == "__main__":
solve()'CS & AI > Algorithm' 카테고리의 다른 글
| 2025.11.13. 실습 (0) | 2025.11.19 |
|---|---|
| 2025.11.06. 실습 (0) | 2025.11.06 |
| 2025.10.30. 실습 (0) | 2025.10.23 |
| 2025.10.16. 실습 (0) | 2025.10.20 |
| 2025.10.02. 실습 (0) | 2025.10.12 |
블로그의 정보
그럼에도 불구하고
Ungbae