자료구조와 알고리즘

자료구조 (Data Structure)

자료구조란, 데이터가 어떤 방식으로 저장되고 그 저장된 데이터를 어떻게 효율적으로 관리・사용할지를 정의하는 방법을 의미한다. 프로그래밍을 배우면서 의식하지 못했더라도 이미 다양한 자료구조들을 사용해왔을 것이다
가장 단순한 자료구조는 변수(Variable)이다. 숫자나 문자열과 같은 단일 데이터를 저장하기 위해 변수를 사용한다. 그리고 저장한 데이터에 접근하고 싶다면 변수의 이름을 참조하면 된다

변수
String str = "Hello World"; // "Hello World"라는 문자열을 str이라는 변수에 저장
System.out.println(str);    // 변수 str을 참조하여 저장된 값을 출력

변수는 단일 데이터를 다루는 데 매우 단순하고 직관적인 자료구조이다.

사용한 다른 자료구조 중 하나는 배열(Array)이 있을 것이다. 배열은 숫자, 문자열 또는 객체 등 동일한 타입의 여러 데이터를 연속적인 메모리 공간에 저장하는 자료구조이다. 그리고 해당 데이터에 접근하고 싶다면 인덱스(Index)를 통해 접근하여 데이터를 참조한다

배열
int[] arr = {10, 20, 30}; // 3개의 정수를 저장하는 배열
System.out.println(arr[0]); // 첫 번째 데이터(10)에 접근
System.out.println(arr[1]); // 두 번째 데이터(20)에 접근

일반 변수는 변수 이름으로 직접 데이터에 접근하지만, 배열은 배열이름[인덱스] 형태로 데이터에 접근한다는 점에서 차이가 있다

자료구조에 따른 처리 방식의 차이

성적 평균을 구하는 코드

데이터를 변수에 저장하는 경우
int score1 = 90;
int score2 = 72;
int score3 = 81;

// 이 세 데이터의 평균을 구하는 방법은 아주 간단하다
// score1, score2, score3을 모두 더하고 3으로 나눈다
double average = (score1 + score2 + score3) / 3.0; // 정확한 평균을 위해 3.0으로 나눔
System.out.println("변수를 이용한 평균: " + average); // 출력: 변수를 이용한 평균: 81.0
데이터를 배열에 저장하는 경우
int[] scores = {90, 72, 81};

// 평균을 저장할 변수를 선언하고 초기화한다
double sum = 0; // 합계를 저장할 변수 (정확한 계산을 위해 double 타입 사용)

// for 루프를 이용하여 배열을 순회하며 배열의 모든 값을 더해준다
for (int i = 0; i < scores.length; i++) {
    sum += scores[i]; // sum = sum + scores[i];
}
// for 루프를 나오면 sum에는 배열의 모든 원소의 합이 담긴다

// 이제 배열의 원소 개수만큼 나눠주면 평균이 구해진다
double average = sum / scores.length;
System.out.println("배열을 이용한 평균: " + average); // 출력: 배열을 이용한 평균: 81.0

위 두 예시에서 보듯이, 데이터를 변수에 저장하는지, 배열에 저장하는지에 따라서 데이터를 처리하는 방법이 달라졌다. 즉, 자료구조에 따라 데이터를 다루는 방식(알고리즘)이 달라지는 것은 당연한 결과이다. 변수와 배열이 데이터를 저장하는 모양도 다르고 사용하는 방법도 다르기 때문이다

데이터 개수 변화에 따른 유지보수성 비교

3개의 데이터가 아니라 4개의 데이터의 평균을 구해보자

변수에 저장하는 경우

데이터 하나를 더 추가하고, 평균을 구하는 코드를 직접 수정해야 한다

int score1 = 90;
int score2 = 72;
int score3 = 81;
int score4 = 57; // 데이터 하나 추가

// 평균을 구하는 코드를 직접 수정해야 한다.
double averageWithVars = (score1 + score2 + score3 + score4) / 4.0;
System.out.println("변수를 이용한 4개 평균: " + averageWithVars); // 출력: 변수를 이용한 4개 평균: 75.0
배열에 저장하는 경우

훨씬 더 간단하다. 배열에 데이터만 추가해주면 된다.
자바에서 배열은 선언 시 크기가 고정되므로, 실제로는 새 배열을 만들어 복사하는 과정이 필요하지만, 개념적으로 데이터만 추가하는 것처럼 보인다)

int[] scoresWithArray = {90, 72, 81, 57}; // 배열에 데이터만 추가

// for 루프를 사용하는 평균 계산 코드는 수정할 필요가 없다.
double sumWithArray = 0;
for (int i = 0; i < scoresWithArray.length; i++) {
    sumWithArray += scoresWithArray[i];
}
double averageWithArray = sumWithArray / scoresWithArray.length;
System.out.println("배열을 이용한 4개 평균: " + averageWithArray); // 출력: 배열을 이용한 4개 평균: 75.0

두 코드 중 배열을 이용한 코드가 유지보수에 더 좋다. 데이터의 개수가 늘어나더라도 평균을 구하는 핵심 로직(for 루프)을 수정할 필요가 없기 때문이다. 구조에 따라서 데이터를 처리하는 방법이 달라질 뿐만 아니라, 코드가 더 단순해지고 유연해질 수도 있다

알고리즘 (Algorithm)

알고리즘은 어떤 문제를 해결하기 위한 구체적이고 유한한 단계들의 집합을 말한다. 즉, 문제 해결을 위한 ‘확실한 방법’이다. 평균을 구하는 문제를 예로 들어보자

Q. 주어진 수의 평균을 구하시오.

평균을 구하기 위해서는 구체적이고 확실한 방법, 즉 알고리즘이 필요하다

  • 변수 3개가 주어졌을 때: “세 변수를 모두 더하고, 그 합을 3으로 나누라”는 방법을 따르면 평균이 구해진다. 이 단계들으 따르면 답이 얻어진다
  • 배열이 주어졌을 때: “배열의 모든 숫자를 더하고, 그 합을 배열 원소의 개수만큼 나누라”는 방법을 따르면 평균이 구해진다. 이 방법으로도 답이 얻어진다

만약 “대충 더해서 알아서 구해라”라고 한다면, 이는 알고리즘이라고 볼 수 없다. 이렇게 모호한 방식으로는 평균을 확실하게 구할 수 없기 때문이다

위에서 변수와 배열을 이용해서 평균을 구해보았다. 데이터가 어떤 자료구조를 하고 있는지에 따라서 평균을 구하는 방식은 조금 달랐다. 즉, 자료구조에 따라서 알고리즘이 달라진다. 이처럼 알고리즘은 자료구조의 영향을 많이 받는다. 따라서 이 둘은 떼려야 뗄 수 없는 관계이다. 자료구조가 바뀌면 알고리즘도 그에 맞춰 달라지거나 최적화될 필요가 있다

그렇다면 특정한 자료구조에 대해서 “문제를 푸는 알고리즘은 하나만 존재할까?”라는 궁금증이 생길 수 있다. 꼭 그렇지는 않다. 3개의 데이터가 담긴 배열이 있다고 가정해보자.
int[] arr = {90, 72, 81}; 이 배열의 평균을 구하는 알고이름은 어떨까?

  • 알고리즘 1: “배열의 모든 숫자를 더하고 원소의 개수만큼 나누라,” (for 루프 방식)
  • 알고리즘 2: “배열의 첫 번째 원소(arr[0]), 두 번째 원소(arr[1]), 세 번째 원소(arr[2])를 더하고 3으로 나눠라”

이렇게 배열이라는 한 가지 자료구조에 대해서도 여러 가지 알고리즘이 있을 수 있다. 하지만 이 알고리즘들을 동시에 사용할 수는 없으며, 보통은 더 효율적이거나 유연한 것을 골라서 사용하고 싶을 것이다

데이터가 늘어나는 상황을 생각한다면 첫 번째 알고리즘(for 루프 방식)이 더 적합하다. 그 이유는 두 번째 알고리즘은 데이터가 네 개로 늘어나면 코드를 수정하지 않고는 평균을 구하지 못하기 때문이다 (즉 arr[3]을 더하고 4로 나누는 식으로 코드를 직접 수정해야 한다)

이처럼 알고리즘은 자료구조에 따라서 많이 달라지기도 하고, 같은 자료구조에 대해서도 여러 가지 알고리즘이 있을 수 있다

개발을 할 때 먼저 자료구조를 선택하여 데이터를 어떻게 저장하고, 사용할지 결정하고, 이에 맞는 적절한 알고리즘을 통해 데이터를 가공하고 원하는 결과를 얻는 과정을 거치게 된다. 실력이 좋은 개발자라면 상황에 맞는 적절한 자료구조를 택하고, 이에 맞는 가장 효율적인 알고리즘을 적용할 수 있어야 한다

인프런 강의 중 감자님의 강의 ‘그림으로 쉽게 배우는 자료구조와 알고리즘 (기본편)’ 강의 참고