정보시스템 구축 관리 과목에서는 소프트웨어 개발 방법론 활용, IT 프로젝트 정보 시스템 구축 관리, 소프트웨어 개발 보안 구축, 시스템 보안 구축에 관한 내용을 학습하였다. 소프트웨어를 개발하고 운영하는 과정에서 필요한 관리 절차와 보안 기술을 단순히 암기하는 것이 아니라, 실제 프로젝트에서 각 기술이 어떤 역할을 하는지를 중심으로 정리하였다.
소프트웨어 개발 방법론 활용에서는 소프트웨어 생명주기와 주요 개발 방법론의 특징을 학습하였다. 폭포수 모형은 각 개발 단계를 순차적으로 진행하기 때문에 요구사항이 명확한 프로젝트에 적합하며, 프로토타이핑 모형은 시제품을 먼저 제작하여 사용자의 요구사항을 구체화하는 데 활용된다는 점을 이해하였다. 나선형 모형은 개발 과정을 반복하면서 위험을 분석하고, 애자일 방법론은 짧은 개발 주기를 반복하며 변화하는 요구사항에 유연하게 대응하는 방식이라는 점을 배웠다.
개발 방법론 테일러링에서는 조직의 표준 개발 절차를 모든 프로젝트에 동일하게 적용하는 것이 아니라, 프로젝트의 규모, 복잡도, 기술 환경, 참여 인력에 맞게 조정해야 한다는 점을 학습하였다. 불필요한 절차는 줄이되 품질과 관리에 필요한 핵심 활동은 유지해야 하며, 테일러링 기준과 결과를 문서화해야 한다는 점을 이해하였다.
소프트웨어 비용과 일정 산정에서는 LOC, 기능점수, COCOMO 등의 기법을 활용하여 개발 규모와 비용을 예측하는 방법을 학습하였다. 또한 WBS를 통해 전체 작업을 세부 단위로 분해하고, PERT와 CPM을 활용하여 작업 간 선후 관계와 임계 경로를 파악하는 방법을 정리하였다. 프로젝트 일정이 지연되지 않도록 하려면 임계 경로에 포함된 작업을 우선적으로 관리해야 한다는 점을 알 수 있었다.
IT 프로젝트 정보 시스템 구축 관리에서는 프로젝트의 범위, 일정, 비용, 품질, 인력, 의사소통, 위험 요소를 종합적으로 관리하는 방법을 학습하였다. 프로젝트 목표를 명확하게 설정한 뒤 세부 작업과 담당자, 완료 기한을 정하고 실제 진행 상황을 계획과 비교해야 한다는 점을 이해하였다.
프로젝트 범위 관리에서는 요구사항을 기준으로 수행해야 할 작업과 제외할 작업을 구분하는 방법을 학습하였다. 프로젝트 도중 요구사항이 무분별하게 추가되면 일정과 비용이 증가할 수 있으므로, 변경 요청의 필요성과 영향도를 검토한 뒤 승인 절차에 따라 반영해야 한다는 점을 배웠다.
프로젝트 위험 관리에서는 발생할 가능성이 있는 기술적·일정적·비용적 위험을 사전에 식별하고, 발생 가능성과 영향도를 기준으로 우선순위를 정하는 방법을 학습하였다. 위험을 회피하거나 줄이는 대응 방안뿐만 아니라, 위험이 실제로 발생했을 때 적용할 비상 계획도 준비해야 한다는 점을 이해하였다.
소프트웨어 개발 보안 구축에서는 소프트웨어의 설계와 구현 단계부터 보안 취약점을 예방하는 방법을 학습하였다. 개발이 완료된 뒤 보안 문제를 수정하는 것보다 초기 단계부터 보안 요구사항을 반영하는 것이 비용과 위험을 줄이는 데 효과적이라는 점을 이해하였다.
시큐어 코딩에서는 SQL 삽입, 크로스사이트 스크립팅, 경로 조작, 버퍼 오버플로 등 주요 취약점의 발생 원인을 학습하였다. 외부에서 입력되는 값은 신뢰하지 않고 형식과 범위를 검사해야 하며, 데이터베이스 질의에는 문자열을 직접 연결하기보다 매개변수화된 질의를 사용해야 한다는 점을 배웠다.
인증과 접근 통제에서는 사용자가 누구인지 확인하는 인증과, 인증된 사용자에게 허용된 기능만 제공하는 인가의 차이를 학습하였다. 사용자에게 필요 이상의 권한을 부여하지 않는 최소 권한 원칙과 역할에 따라 권한을 부여하는 역할 기반 접근 통제의 중요성을 이해하였다.
시스템 보안 구축에서는 운영체제, 서버, 네트워크, 데이터베이스를 외부 공격과 내부 위협으로부터 보호하는 방법을 학습하였다. 방화벽은 허용된 통신과 차단할 통신을 구분하고, 침입 탐지 시스템은 공격 징후를 탐지하며, 침입 방지 시스템은 탐지한 공격을 능동적으로 차단한다는 차이를 정리하였다.
악성코드와 네트워크 공격 유형에서는 바이러스, 웜, 트로이 목마, 랜섬웨어의 특징과 서비스 거부 공격, 스니핑, 스푸핑 등의 공격 방식을 학습하였다. 각 공격의 명칭만 암기하기보다 공격자가 어떤 방식으로 시스템이나 통신을 방해하고 정보를 탈취하는지를 중심으로 이해하였다.
보안 취약점 점검과 사고 대응에서는 불필요한 계정과 서비스를 제거하고, 보안 패치를 적용하며, 시스템 로그를 정기적으로 분석하는 방법을 학습하였다. 보안 사고가 발생하면 탐지, 피해 범위 분석, 격리, 복구, 원인 분석, 재발 방지의 순서로 대응해야 한다는 점을 배웠다.
이번 학습을 통해 정보 시스템 구축 관리는 개발 기술만을 다루는 과목이 아니라 프로젝트의 계획, 비용, 일정, 품질, 보안을 함께 관리하는 과목이라는 점을 이해하였다. 또한 개발 방법론과 프로젝트 관리, 소프트웨어 보안, 시스템 보안은 각각 독립된 내용이 아니라 안전하고 안정적인 시스템을 구축하기 위해 서로 연결되어 있다는 점을 알 수 있었다.
# =========================================================
# 1. 0/1 배낭 문제
# 각 물건을 한 번만 선택할 수 있을 때,
# 제한된 무게 안에서 얻을 수 있는 최대 가치를 계산
# =========================================================
def knapsack(capacity, items):
# dp[w] = 무게 한도가 w일 때 얻을 수 있는 최대 가치
dp = [0] * (capacity + 1)
for weight, value in items:
# 같은 물건을 중복 선택하지 않도록 뒤에서부터 갱신
for current_weight in range(capacity, weight - 1, -1):
dp[current_weight] = max(
dp[current_weight],
dp[current_weight - weight] + value
)
return dp[capacity]
items = [
(3, 6),
(4, 8),
(5, 12),
(6, 13)
]
print("배낭에 담을 수 있는 최대 가치:", knapsack(10, items))
# =========================================================
# 2. 최장 증가 부분 수열
# 수열의 순서를 유지하면서 값이 계속 증가하는
# 가장 긴 부분 수열의 길이를 계산
# =========================================================
def longest_increasing_subsequence(numbers):
# dp[i] = i번째 숫자를 마지막으로 하는 LIS의 최대 길이
dp = [1] * len(numbers)
for current in range(len(numbers)):
for previous in range(current):
if numbers[previous] < numbers[current]:
dp[current] = max(
dp[current],
dp[previous] + 1
)
return max(dp)
numbers = [10, 20, 10, 30, 20, 50]
print(
"최장 증가 부분 수열의 길이:",
longest_increasing_subsequence(numbers)
)
# =========================================================
# 3. 최장 공통 부분 수열
# 두 문자열의 순서를 유지하면서 공통으로 나타나는
# 가장 긴 부분 수열의 길이를 계산
# =========================================================
def longest_common_subsequence(first, second):
rows = len(first) + 1
cols = len(second) + 1
# dp[i][j] = first의 i개 문자와 second의 j개 문자를
# 비교했을 때의 최장 공통 부분 수열 길이
dp = [[0] * cols for _ in range(rows)]
for i in range(1, rows):
for j in range(1, cols):
if first[i - 1] == second[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(
dp[i - 1][j],
dp[i][j - 1]
)
return dp[-1][-1]
first = "ACAYKP"
second = "CAPCAK"
print(
"최장 공통 부분 수열의 길이:",
longest_common_subsequence(first, second)
)
# =========================================================
# 4. 동전 교환
# 주어진 동전을 반복해서 사용할 수 있을 때,
# 목표 금액을 만드는 데 필요한 최소 동전 수를 계산
# =========================================================
def minimum_coins(coins, target):
infinity = target + 1
# dp[amount] = amount를 만드는 최소 동전 수
dp = [infinity] * (target + 1)
dp[0] = 0
for amount in range(1, target + 1):
for coin in coins:
if coin <= amount:
dp[amount] = min(
dp[amount],
dp[amount - coin] + 1
)
if dp[target] == infinity:
return -1
return dp[target]
coins = [1, 3, 4]
target = 6
print(
"목표 금액을 만드는 최소 동전 수:",
minimum_coins(coins, target)
)
# =========================================================
# 5. 격자 최소 비용 경로
# 왼쪽 위에서 오른쪽 아래까지 이동하면서
# 방문한 칸의 비용 합이 최소가 되는 경로 계산
# 이동 방향: 오른쪽 또는 아래
# =========================================================
def minimum_grid_cost(grid):
rows = len(grid)
cols = len(grid[0])
# dp[row][col] = 해당 칸까지 이동하는 최소 누적 비용
dp = [[0] * cols for _ in range(rows)]
dp[0][0] = grid[0][0]
for col in range(1, cols):
dp[0][col] = dp[0][col - 1] + grid[0][col]
for row in range(1, rows):
dp[row][0] = dp[row - 1][0] + grid[row][0]
for row in range(1, rows):
for col in range(1, cols):
dp[row][col] = min(
dp[row - 1][col],
dp[row][col - 1]
) + grid[row][col]
return dp[-1][-1]
grid = [
[5, 3, 2, 1],
[1, 9, 1, 3],
[2, 1, 4, 2],
[3, 2, 1, 1]
]
print(
"격자에서의 최소 이동 비용:",
minimum_grid_cost(grid)
)
# =========================================================
# 6. 파일 합치기 - 구간 DP
# 연속된 파일들을 하나로 합칠 때 발생하는
# 전체 비용의 최솟값을 계산
# =========================================================
def minimum_merge_cost(files):
n = len(files)
# prefix[i] = 0번부터 i-1번까지 파일 크기의 누적합
prefix = [0] * (n + 1)
for index in range(n):
prefix[index + 1] = prefix[index] + files[index]
# dp[start][end] = start부터 end까지 파일을 합치는 최소 비용
dp = [[0] * n for _ in range(n)]
for length in range(2, n + 1):
for start in range(n - length + 1):
end = start + length - 1
dp[start][end] = float("inf")
total_size = prefix[end + 1] - prefix[start]
for middle in range(start, end):
merge_cost = (
dp[start][middle]
+ dp[middle + 1][end]
+ total_size
)
dp[start][end] = min(
dp[start][end],
merge_cost
)
return dp[0][n - 1]
files = [40, 30, 30, 50]
print(
"파일을 모두 합치는 최소 비용:",
minimum_merge_cost(files)
)
# =========================================================
# 7. 연속된 수의 최대 합
# 현재 위치에서 새로운 부분 수열을 시작할지,
# 이전 수열을 이어갈지 판단하여 최대 합 계산
# =========================================================
def maximum_subarray_sum(numbers):
# current_sum = 현재 위치에서 끝나는 연속 부분 수열의 최대 합
current_sum = numbers[0]
maximum_sum = numbers[0]
for number in numbers[1:]:
current_sum = max(
number,
current_sum + number
)
maximum_sum = max(
maximum_sum,
current_sum
)
return maximum_sum
numbers = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(
"연속된 부분 수열의 최대 합:",
maximum_subarray_sum(numbers)
)
이번 주에는 코딩테스트 대비를 위해 다이나믹 프로그래밍 유형의 문제 풀이를 진행하였다. 다이나믹 프로그래밍은 하나의 큰 문제를 여러 개의 작은 문제로 나누고, 작은 문제의 계산 결과를 저장하여 같은 계산이 반복되는 것을 줄이는 방식이라는 점을 이해하였다.
다이나믹 프로그래밍 문제를 해결할 때는 먼저 어떤 값을 하나의 상태로 정의할 것인지 결정해야 한다. 그다음 현재 상태의 정답이 이전 상태의 결과와 어떤 관계를 가지는지 점화식으로 표현하고, 가장 작은 문제에 해당하는 초기값을 설정해야 한다는 점을 학습하였다.
0/1 배낭 문제에서는 제한된 무게 안에서 선택한 물건의 가치 합을 최대로 만드는 방법을 학습하였다. 각 물건을 한 번만 선택할 수 있으므로 1차원 DP 배열을 뒤에서부터 갱신해야 같은 물건이 한 번의 반복에서 여러 차례 사용되는 것을 방지할 수 있다는 점을 이해하였다.
최장 증가 부분 수열 유형에서는 각 원소를 마지막 값으로 하는 증가 부분 수열의 최대 길이를 저장하였다. 현재 원소보다 앞에 있으면서 값이 작은 원소들을 확인하고, 해당 원소까지의 최장 길이에 1을 더하는 방식으로 점화식을 구성하였다.
최장 공통 부분 수열 유형에서는 두 문자열의 각 위치까지 비교했을 때 만들 수 있는 공통 부분 수열의 최대 길이를 2차원 배열에 저장하였다. 두 문자가 같으면 대각선 위의 결과에 1을 더하고, 다르면 위쪽과 왼쪽 결과 중 큰 값을 선택하는 방식으로 문제를 해결하였다.
동전 교환 유형에서는 특정 금액을 만드는 데 필요한 최소 동전 수를 상태로 정의하였다. 현재 금액에서 각 동전의 금액을 뺀 이전 상태를 확인하고, 해당 상태에 동전 하나를 추가하는 방식으로 최소값을 갱신하였다. 만들 수 없는 금액을 구분하기 위해 충분히 큰 초기값을 설정해야 한다는 점도 학습하였다.
격자 경로 유형에서는 각 칸까지 이동하는 데 필요한 최소 누적 비용을 저장하였다. 현재 칸에 도달하려면 위쪽 칸이나 왼쪽 칸에서 이동해야 하므로 두 상태 중 비용이 더 작은 값을 선택한 뒤 현재 칸의 비용을 더하는 방식으로 점화식을 구성하였다.
파일 합치기 유형에서는 연속된 구간을 나누어 계산하는 구간 DP를 학습하였다. 하나의 구간을 여러 위치에서 두 부분으로 나누고, 왼쪽 구간의 최소 비용과 오른쪽 구간의 최소 비용에 전체 파일 크기를 더한 값 중 최솟값을 선택하였다. 구간의 길이가 짧은 경우부터 계산해야 더 긴 구간을 구할 때 이전 결과를 활용할 수 있다는 점을 이해하였다.
연속 부분 수열의 최대 합 유형에서는 현재 숫자부터 새로운 수열을 시작하는 경우와 이전 연속 수열에 현재 숫자를 추가하는 경우를 비교하였다. 이전 결과가 오히려 현재 합을 감소시키면 현재 숫자부터 다시 시작해야 한다는 점을 이용하여 별도의 2차원 배열 없이도 문제를 해결하였다.
다이나믹 프로그래밍은 메모이제이션을 사용하는 하향식 방식과 반복문으로 작은 문제부터 계산하는 상향식 방식으로 구현할 수 있다는 점도 학습하였다. 하향식 방식은 점화식을 코드로 옮기기 쉽지만 재귀 호출이 많아질 수 있고, 상향식 방식은 계산 순서를 명확하게 설계해야 하지만 함수 호출에 따른 부담을 줄일 수 있다는 차이가 있었다.
이번 학습을 통해 다이나믹 프로그래밍 문제에서는 단순히 DP 배열을 만드는 것이 중요한 것이 아니라, 배열의 각 칸이 어떤 의미를 가지는지 정확하게 정의하는 것이 가장 중요하다는 점을 알 수 있었다. 상태의 의미가 명확해야 초기값과 점화식, 계산 순서를 올바르게 설정할 수 있었다.
또한 입력값만 보고 바로 점화식을 작성하기보다 완전 탐색으로 모든 경우를 계산한다면 어떤 부분 문제가 반복되는지 먼저 확인하는 과정이 필요하다는 점을 이해하였다. 반복되는 문제의 결과를 저장하고 재사용할 수 있으며, 현재 문제의 최적해가 이전 문제의 최적해로 구성된다면 다이나믹 프로그래밍을 적용할 수 있다는 점을 배웠다.
같은 다이나믹 프로그래밍 유형이라도 문제에 따라 1차원 배열, 2차원 배열, 구간 배열 등 상태를 저장하는 구조가 달라질 수 있다는 점도 알 수 있었다. 따라서 풀이 코드를 암기하기보다 상태 정의, 초기값 설정, 점화식 구성, 계산 순서 결정의 네 단계를 기준으로 문제를 분석하는 연습이 필요하다는 점을 이해하였다.