Computer >> 컴퓨터 >  >> 프로그래밍 >> 프로그래밍

최대 합 증가 부분 수열(MSIS) 알고리즘 – 개념부터 C++ 구현까지

최대 합 증가 부분 수열(Maximum Sum Increasing Subsequence)은 주어진 정수 목록에서 만들 수 있는 부분 수열 중, 모든 원소가 오름차순으로 배치되어 있으면서 그 합이 가장 큰 부분 수열을 의미합니다.

이 문제는 동적 계획법(Dynamic Programming)으로 효율적으로 해결할 수 있습니다. 각 인덱스 i에 대해 'arr[i]로 끝나는 최대 합 증가 부분 수열'을 저장하는 배열 L을 사용하며, 여기서 L[i]는 array[i]로 끝나는 최대 합 증가 부분 수열이 됩니다.

입력 및 출력

입력:
정수 수열 {3, 2, 6, 4, 5, 1}
출력:
합이 최대가 되는 증가 부분 수열 {3, 4, 5}

예시에서 {3, 6}의 합은 9이지만, {3, 4, 5}의 합은 12로 더 큽니다. 따라서 이 경우 정답은 {3, 4, 5}가 됩니다.

알고리즘

함수 maxSumSubSeq(array, n)은 다음과 같이 동작합니다.

입력: 숫자로 이루어진 수열, 원소의 개수

출력: 합이 최대인 증가 부분 수열

Begin
    define array of arrays named subSeqLen of size n.
    add arr[0] into the subSeqLen
    for i in range (1 to n-1), do
        for j in range (0 to i-1), do
            if arr[i] > arr[j] and sum of subSeqLen [i] < sum of subSeqLen [j], then
                subSeqLen[i] := subSeqLen[j]
        done
    done

    add arr[i] into subSeqLen[i]
    res := subSeqLen[0]
            
    for all values of subSeqLen, do
        if sum of subSeqLen[i] > sum of subSeqLen[res], then
            res := subSeqLen[i]
    done

    print the values of res.

End

알고리즘 단계별 설명

1. 크기가 n인 2차원 배열(벡터의 벡터) subSeqLen을 선언하고, 첫 번째 원소 arr[0]을 subSeqLen[0]에 추가합니다.

2. i를 1부터 n-1까지 반복하면서, 각 i에 대해 j를 0부터 i-1까지 탐색합니다. 이때 arr[i] > arr[j]이고 subSeqLen[i]의 합이 subSeqLen[j]의 합보다 작다면, subSeqLen[i]에 subSeqLen[j]를 복사합니다.

3. 내부 반복이 종료되면 subSeqLen[i]에 arr[i]를 추가하여, arr[i]로 끝나는 증가 부분 수열을 완성합니다.

4. 마지막으로 모든 subSeqLen 값 중 합이 가장 큰 것을 res로 선택한 뒤 출력합니다.

C++ 구현 예제

#include <iostream>
#include <vector>
using namespace std;
 
int findAllSum(vector<int> arr) {     //find sum of all vector elements
    int sum = 0;

    for(int i = 0; i<arr.size(); i++) {
        sum += arr[i];
    }

    return sum;
}
 
void maxSumSubSeq(int arr[], int n) {
    vector <vector<int> > subSeqLen(n);     //max sum increasing subsequence ending with arr[i]
    subSeqLen[0].push_back(arr[0]);

    for (int i = 1; i < n; i++) {         //from index 1 to all
        for (int j = 0; j < i; j++) {     //for all j, j<i
            
            if ((arr[i] > arr[j]) && (findAllSum(subSeqLen[i]) < findAllSum(subSeqLen[j])))
                subSeqLen[i] = subSeqLen[j];
        }
        
        subSeqLen[i].push_back(arr[i]);     //sub Sequence ends with arr[i]
    }

    vector<int> res = subSeqLen[0];

    for(int i = 0; i<subSeqLen.size(); i++) {
        if (findAllSum(subSeqLen[i]) > findAllSum(res))
            res = subSeqLen[i];
    }

    for(int i = 0; i<res.size(); i++)
        cout << res[i] << " ";
    cout << endl;
}

int main() {
    int arr[] = { 3, 2, 6, 4, 5, 1 };
    int n = 6;
    cout << "The Maximum Sum Subsequence is: ";
    maxSumSubSeq(arr, n);
}

실행 결과

The Maximum Sum Subsequence is: 3 4 5

시간 복잡도

이 알고리즘은 두 개의 중첩 반복문을 사용하므로 시간 복잡도는 O(n²)입니다. 또한 각 인덱스마다 부분 수열 전체를 저장하므로 공간 복잡도 역시 O(n²)입니다. 참고로, 매번 findAllSum 함수로 합을 새로 계산하는 대신 각 부분 수열의 누적 합을 함께 저장하면 불필요한 연산을 줄여 실행 속도를 더욱 개선할 수 있습니다.