[TIL/14] C#전용문법 및 알고리즘

안건우·2025년 10월 1일

sparta_til

목록 보기
13/26

오늘은 내배캠에서 배포한 C#강의 후반부를 들었다.
솔직히 초반부는 기본적인 개발문법이라 그렇게 주의깊게 듣지않았으나..
후반부부터는 확실히 생소한 내용들이 등장하기 시작했다.

Chpater4 C# 고급 문법

챕터4에서 다룬 내용들이다. Lambda는 뭐 C#만의 개념이 아니기도하고 Linq같은경우는 나름 혼자 개발을 하면서 익숙하게 보아왔던 개념들이었다.

  • Delegate (델리게이트): 메서드를 넘기는 파이프라인 비슷한 것
    하지만 delegate는 웹에서도 본적은 있지만 그 원리가 확 이해가 되지는 않았다. 메서드를 변수처럼 넘길 수 있는 파이프라인 비슷한 것이라고 이해를 하려고 하니 조금 머리속에 들어오는 듯했다.. 특히 Reflection이나 스크립트 언어의 eval이 떠올리니 확연하게 그 개념을 이해할수 있었다. 다만 Delegate는 컴파일 시점에 타입을 엄격하게 검사해서 훨씬 안전하고 효율적인 방식이며 C#이 쌓아올려진 기반, 정체성과도 같은 객체인 듯 싶었다.

  • Lambda (람다): C#에선 LINQ 때문에라도 필수적인 것
    람다는 뭐 자바에서도 그럭저럭 익숙하게 쓰고 있었지만, C#에서는 그 중요성이 다른 듯했다. 자바를 할 때 람다는 사실 있으면 좋고 없어도 그만인 수준이었다고 생각한다. 그런데 C#에서는 그 중요성이 다른 이유는 바로 LINQ 때문인 것 같았다.

  • LINQ (Language Integrated Query): 닷넷의 아이폰
    사실 LINQ는 체이닝된 메서드들의 이름 자체가 Where, Select처럼 직관적이라 처음부터 그 역할을 이해하는 것은 쉬웠다. 하지만 C#에서 LINQ의 중요성은 엄청난듯 했다. 따지고 보면 LINQ애플에 아이폰이 있듯, MS의 닷넷에는 LINQ가 있다 이런 느낌이었다. 이 과정에서 C# 자체가 MS의 언어라는 걸 처음 알았다. 닷넷, MS, VS 등이 전부 MS가 관리하는 통합 개발 환경이라는 듯 싶었다. 그래서 VS도 C#에 최적화되어 있다고는 하지만... 개인적으로 그래도 VS는 뭔가 쓰기가 힘들었다. 물론 VS -> Rider가 Eclipse -> IntelliJ처럼 엄청난 생산성 증대를 가져다주는 느낌은 아니긴 했지만...


알고리즘 수업

챕터 5는 알고리즘에 대한 내용이었다. 역시 챕터4와는 비교도 할수 없이 난이도가 높았다..

1. ⏱️ Big-O 표기법과 복잡도 분석

알고리즘의 성능을 객관적으로 분석하는 Big-O 표기법은 단순히 코드가 얼마나 빠른지가 아니라, 입력 크기(N)에 따라 시간과 공간 사용량이 어떻게 변화하는지를 예측하는 데 필수적이라고 한다.

  • Big-O 계산의 핵심:
    • 최악의 경우를 기준으로 함
    • 상수항과 낮은 차수는 무시하고, 가장 높은 차수만 남겨서 표기
  • 시간 복잡도: 코드가 실행하는 연산의 횟수
    • O(N) (선형), O(N²) (중첩 반복문)
  • 공간 복잡도: 입력에 따라 필요한 추가 메모리 양
    • 대부분의 탐색/정렬 알고리즘에서 추가 배열 없이 동작하면 O(1) (상수 공간)을 가짐.

2. ⚙️ 정렬 알고리즘

  • 선택 정렬 & 삽입 정렬: O(N²) (시간), O(1) (공간). 기본적이지만 대규모 데이터에는 비효율적이라고 한다. 삽입 정렬은 이미 정렬된 데이터에는 O(N)으로 효율적이라는 특징이 있다고 한다.
  • **퀵 정렬 (Quick Sort)
    • 원리: Pivot(기준점)을 정해 데이터를 두 부분으로 나누고, 각 부분을 재귀적으로 정렬하는 방식
    • 복잡도: 평균 O(N log N) (시간), O(log N) (공간 - 재귀 스택). 최악의 경우 O(N²)
    • 특징: 가장 빠른 정렬 알고리즘 중 하나로 꼽히며, 실제 시스템에서도 많이 활용됨.
  • 병합 정렬 (Merge Sort)
    • 원리: 배열을 계속 절반으로 나누고, 정렬된 상태로 다시 병합하는 방식
    • 복잡도: 항상 O(N log N) (시간), O(N) (공간 - 임시 배열).
    • 특징: 퀵 정렬과 달리 항상 O(N log N)을 보장하며, 안정 정렬(stable sort)
  • 내장 정렬 함수 활용: 실무에서는 C#의 Array.Sort()List.Sort()처럼 언어에서 제공하는 최적화된 정렬 함수를 활용하는 게 일반적이라고 한다. 람다 표현식을 사용해서 정렬 기준을 커스터마이징하는것 또한 가능하다고 한다.

3. 🔍 탐색 알고리즘

  • 이진 탐색 (Binary Search):
    • 원리: 반드시 정렬된 배열에서만 사용 가능하며, 중간값을 기준으로 탐색 범위를 절반씩 줄여나가는 방식
    • 복잡도: O(log N) (시간).
  • 그래프/트리 탐색: 비선형 구조의 탐험
    • 그래프: 정점(vertex)과 간선(edge)으로 구성됨. 방향성, 가중치에 따라 다양한 형태가 존재.
    • DFS (깊이 우선 탐색):
      • 원리: 한 방향으로 최대한 깊이 파고들어가는 방식. 스택(Stack) 또는 재귀(Recursion) 방식으로 구현됨
      • 활용: 모든 정점 방문, 사이클 감지, 특정 경로 탐색에 사용
    • BFS (너비 우선 탐색):
      • 원리: 시작점에서 가까운 노드부터 순차적으로 탐색하는 방식 큐(Queue)를 사용.
      • 활용: 최단 거리(가중치 없는 그래프), 모든 정점 방문, 미로 찾기 등.
  • 최단 경로 알고리즘: 최적의 길 찾기
    • Dijkstra (다익스트라): 음수 가중치가 없는 그래프에서 특정 출발점으로부터 모든 다른 정점까지의 최단 경로를 찾는 알고리즘
    • Bellman-Ford (벨만-포드): 음수 가중치 간선이 있는 그래프에서 최단 경로를 찾으며, 음수 사이클도 감지가능
    • A-Star (A*): 길 찾기에 주로 사용되며, 휴리스틱(Heuristic) 함수를 이용해 목표 지점까지의 예상 비용을 고려하여 효율적으로 탐색

4. 🧠 고급 알고리즘

  • Dynamic Programming (동적 계획법):
    • 원리: 큰 문제를 작은 단위로 나누고, 중복되는 작은 문제의 해답을 저장(Memoization)해서 재활용하는 방식
    • 핵심: Overlapping Subproblems (겹치는 부분 문제)Optimal Substructure (최적 부분 구조) 특성을 가질 때 유용함.
    • 예시: 비효율적인 O(2^N) 피보나치 수열을 O(N)으로 최적화할 때 사용됨.
  • Greedy Algorithm (그리디 알고리즘):
    • 원리: 매 순간 가장 최적이라고 생각되는 선택을 해서 최종 해답에 도달하는 방식.
    • 특징: 지역 최적해가 항상 전역 최적해를 보장하지는 않음. 하지만 대부분 효과적
    • 예시: 동전 거스름돈 문제 (가장 큰 동전부터), 작업 스케줄링 (가장 빨리 끝나는 작업부터) 같은 것들.
  • Divide and Conquer (분할 정복):
    • 원리: 큰 문제를 독립적인 작은 문제로 분할하고, 각각을 해결한 후 그 결과들을 다시 합쳐 큰 문제의 해를 구하는 방식.
    • 특징: 분할된 작은 문제들이 서로에게 영향을 주지 않아 병렬 처리가 용이.
    • 예시: 퀵 정렬, 병합 정렬 같은 것들.
    • DP와의 차이: DP는 겹치는 부분 문제의 해를 재사용하지만, 분할 정복은 독립적인 부분 문제를 해결함.

마치며

알고리즘에 대해선 항상 그런 이야기만 들어오긴했다.
'너 대기업 기술직갈거야? 그거 아니면 굳이 CS랑 알고리즘은 굳이...'
사실 알고리즘 자체의 난이도도 있고, 가볍게 공부해선 해결할수 있는 부분은 아니기 때문에 나 또한 책한권 읽고 알고리즘에 대한 공부는 소홀히 한지 오래였다... 그래서일지 오늘 챕터5의 실습과제로 나온
Largest Rectangle in Histogram, Flood Fill, Longest Increasing Subsequence 3문제는 단 하나도 풀지 못했다. 3문제 모두 알고리즘쪽에서는 기초적인 난이도로 상당히 유명한 문제라고 한다. 특히 Largest Rectangle in Histogram같은 경우는 구현까지는 성공을 했지만 시간복잡도를 도저히 해결하지 못했다. 결국 AI에게 도움을 받아 정답을 받고 그걸 디버깅까지하면서 분석해봤지만... 역시나 이해가 쉽지않았다. 문제의 핵심 풀이사고는 Stack을 사용하는것과 넓이 계산의 주체를 전환하는것이었다. 소홀히 했으니 당연하지... 라며 자기 위안을 해봤지만 스크래치는 확실히 가는 느낌이었다.

물론 지금은 공부보다는 개발에 집중하고 싶어서 알고리즘 자체에 큰 시간 투자를 하지는 못하겠지만, 조금 상황이 안정되면 CS 지식과 함께 알고리즘 자체에 대해 좀 더 익숙해지려는 노력이 필요할 것 같다고 느꼈다. CS와 알고리즘을 버리는 개발자들이 많아지는 이유가 정말 필요가 없기때문일까 그럴리가 없다. 결국 알고리즘도 CS도 개발을 잘하기 위한 도구일뿐 특정한 무언가를 해내기 위해 필요한 기술이 아니기 때문이다. 우리가 쓰는 모든 if문, for문, 그리고 함수 호출 하나하나가 결국 어떤 문제 해결을 위한 알고리즘이다.
알고리즘이 뭐 특별한것일까. 다만 특정 문제 해결을 위해 정형화되고, 효율적이고, 검증된 로직모델을 사용하는게 우리가 따로 배우는 알고리즘일뿐인것이다. 문제는 알고리즘을 '안 쓰는 것'이 아니라 '효율적인 알고리즘을 쓰지 못하는 것'이거나, 혹은 자신이 쓰고 있는 것이 알고리즘임을 인지하지 못하는 것에 있다. 단순히 어렵고 힘들고, 직접적으로 지식을 활용할 일이 없으니 필요가 없다고 느낄뿐. 필요없는 지식이라는게 과연 세상에 어디있을까. 물론 과거에 책 한 권 읽고, 오늘 1시간 강의 들은 걸로 내가 알고리즘에 대한 이해를 끝냈을 리는 없다. 실습은 물론이고, 개념에 대한 공부 또한 지속적으로 이루어져야 하는 부분인 것 같다. 단순히 문제 풀이 자체를 넘어, 코드의 효율성을 높이고 문제 해결의 본질적인 사고력을 기르는 과정으로 접근해야 할 것이다. 이러한 꾸준함이야말로 과거의 아쉬움을 채우고 더 나은 개발자로 나아가는 길이라고 생각한다.

0개의 댓글