midas+son의 크리에이티브(creative) 이야기

문제 출처 : 코딩도장(http://codingdojang.com/scode/531)

문제


놀이공원에 가면 인형 맞추기 게임이 있다.

한 놀이공원에는 인형에 숫자를 써놓고 인형 맞추기를 한다.

그런데 맞춘 인형의 숫자의 합이 특정한 값이 되는 경우에만 맞춘 인형을 가져 갈 수 있다.

가령 10개의 인형에 쓰여진 숫자가 각각 25 27 3 12 6 15 9 30 21 19이고 

숫자의 합이 50이 되는 경우만 인형을 가져갈수 있다면 

6 19 25가 쓰여진 인형을 맞춰야 인형을 가져 갈 수 있다.

이때 인형의 갯수와 각각의 숫자와 필요한 합을 입력받으면 

맞춰야 하는 인형의 숫자를 출력하는 프로그램을 작성해 보자.


입력 - 첫줄에는 인형의 갯수와 필요한 합, 둘째 줄에는 인형 각각에 쓰여진 숫자


예)

10 50

25 27 3 12 6 15 9 30 21 19

출력 - 필요한 합이 되는 인형 각각의 숫자를 오름차순으로 출력


예)

6 19 25


코딩 해결 : 내 손과 머리 (Visual Studio 2013)

#include <iostream>

#include <sstream>

#include <vector>

#include <list>


using namespace std;


//표준 숫자 입력 함수

vector<int> getInput(size_t sizeLimit)

{

vector<int> vToken;

vToken.reserve(sizeLimit);


string line;

getline(cin, line);

istringstream iss(line);

string token;

while (getline(iss, token, ' ')) 

{

try 

{

vToken.push_back(stoi(token));

}

catch (exception& e) 

{

continue;

}


if (vToken.size() >= sizeLimit) {

break;

}

}

return vToken;

}


//정렬함수(선택정렬) - 내림차순

void SortR(vector<int>& v)

{

int dollsCount = v.size();

int dollsCountM1 = dollsCount - 1;

for (int i = 0; i < dollsCountM1; ++i)

{

int lastIndex = i;

bool bChange = false;


for (int j = i + 1; j < dollsCount; ++j)

{

if (v[lastIndex] < v[j]) //내림차순

{

bChange = true;

lastIndex = j;

}

}


if (bChange)

{

//이동함수를 이용한 순수 스왑 - c++11

int temp = move(v[i]);

v[i] = move(v[lastIndex]);

v[lastIndex] = move(temp);

}

}

}


//출력 함수

void Print(vector<int>& v)

{

for (auto iter = v.begin(); iter != v.end(); ++iter)

{

cout << *iter << ' ';

}

}


//결과 출력 함수

void PrintResult(list<int>& v)

{

for (auto iter = v.begin(); iter != v.end(); ++iter)

{

*iter == -1 ? cout << endl : cout << *iter << ' '; //삼항연산

}

}


//탐색 함수

void SeachNum(vector<int>& v, list<int>& result, list<int>& tempList, int start, int end, int sNum)

{

if (v[start] == sNum) //첫 숫자와 결과 수가 맞다면

{

result.push_back(-1); //구분자 입력

result.push_back(v[start]); //현재 숫자 입력


if (!tempList.empty()) //임시 리스트가 비어 있지 않다면

{

result.insert(result.end(), tempList.begin(), tempList.end()); //지금까지의 임시 리스트(부모) 입력

}

}

else if (v[start] < sNum)    //결과 값이 더 클 경우

{

int searchNewNum = sNum - v[start];    //구해야 할 값 갱신


for (int i = start + 1; i < end; ++i) //정렬된 현재의 다음 노트 부터

{

tempList.push_front(v[start]); //부모를 나타내기 위해 임시 리스트 앞에 입력

SeachNum(v, result, tempList, i, end, searchNewNum); //재귀

tempList.pop_front(); //재귀가 끝나면 리스트 앞을 다시 비움

}

}

//else if (v[start] > sNum) //무시

}


int main(void)

{

//자연수 2개 받기 - 인형 갯수, 필요 합

vector<int> stdNum = getInput(2);


int dollsCount = stdNum[0]; //인형 갯수

int sumNums = stdNum[1]; //필요 합


//인형 숫자들 입력

vector<int> vNumbers = getInput(dollsCount);


//정렬 - 내림차순

SortR(vNumbers);

//출력

Print(vNumbers);    //정렬된 벡터 출력


//결과 받을 리스트

list<int> vResults;

//임시 리스트

list<int> vTemps;


//탐색

for (int i = 0; i < dollsCount; ++i)

{

SeachNum(vNumbers, vResults, vTemps, i, dollsCount, sumNums);

}


//결과 출력

cout << endl;

PrintResult(vResults);


//getchar();


return 0;

}


처음에 내림 차순으로 큰 숫자부터 정렬 한것은

깊이우선 탐색을 생각하다보니

더 많은 연산을 제한하기 위해서

결과 합에서 작은 수부터 빼나아가 찾는 것보다

큰 수를 빼나아가 찾는 것이 연산이 적을 것이라 예상 했기 때문 입니다.


리스트와 벡터의 복합 사용 이유는

고정된 메모리 상의 데이터를 처리하고자 할 경우 벡터

자주 삽입, 삭제가 이루어 지는 것은 리스트로 처리하여 

연산이나 메모리에 부담을 덜어주고자 하였습니다.

지금까지 c++에서 define을 매크로로 사용하지 않고

특정 값 지정만 해서 사용하니까 몰랐던 사실을 이제야 알게 되었습니다.

저 나름 신박했던 것이어서 기억하고자 적어봅니다.


#include <iostream>

#define ff(x) x*x

using namespace std;

void main()

{

int k = 3;

cout << ff(k + 1);

}

ff라는 매크로를 만들고 출력하고자 합니다.

위의 출력 값을 예측 하신다면 뭐라고 생각하시나요?


저는 처음엔 머리로 16 이라고 예상 했었습니다.

3+1은 4이고 4 * 4 가 될 것이라고 생각을 했던 것이지요.


하지만 아닙니다.

결과는 7이 나옵니다.


왜 그럴까 생각을 해봅시다.

혼자 생각해서 쉽게 감을 잡을 수 없다면

우리의 멋진 선생님 MSDN을 찾아 봅시다.

https://msdn.microsoft.com/ko-kr/library/teas0593.aspx


요약 하면 이렇습니다.

#define으로 만들어 놓은 것은 치환 먼저 하고 계산을 한다는 것이죠.

즉 k+1 한 것이 계산 되어서 ff에 들어가는 것이 아니라

k+1 자체가 들어가서 x와 치환되어

k + 1 * k + 1 이 되고

k는 3이니까 3 + 1 * 3 + 1 이 되며

곱셈 먼저 하니 3 + 3 + 1이 되어  결과가 7이 나온 것입니다.


일반 함수처럼 임시 변수에 4로 계산되어 들어가지 않는다는 것이 센세이션이었습니다.

의도대로 제곱을 하고자 한다면

#include <iostream>

#define ff(x) (x)*(x)

using namespace std;

void main()

{

int k = 3;

cout << ff(k + 1);

}

위와 같이 x * x 가 (x)*(x)이 되어야 합니다.

그래야 k+1을 넣었을 때에도

(k+1)*(k+1) 로 원하는 제곱이 됩니다.


이상 입니다.

define 매크로를 쓰는 것은 치환부터 시작한다는 것을 기억합시다.

cout 으로 출력 할 때 소수점 자릿수를 고정 해야 할 경우가 있다.

게임 만들 때는 보통 쓸일이 없었지만

어떻게 보면 기본적으로 알고 넘어 갔었어야 할 수도 있다.


그래서 알아 보았다.


    float num = 8.8f;    //출력할 숫자

  

    cout << fixed;    //고정 출력

    cout.precision(5);    //소숫점 다섯자리

    cout << num << endl;    //8.80000

  

    cout << scientific;    //과학적 표기법 - 어떤 양을 소수 부분과 10의 멱수로 나타내는 표기법(네이버 지식백과)

    cout << num << endl;    //8.80000e+00

  

    //이 아래부터는 c++11에서 추가된 것

    cout << hexfloat;    //16진수 표기

    cout << num << endl;    //0x1.19999ap+3 

  

    cout << defaultfloat;    //float 기본 출력

    cout << num << endl;    //8.8



참고 자료 : http://www.cplusplus.com/reference/ios/fixed/

자료 구조 정렬 중 하나의 기법이다.


퀵 정렬 방법을 설명해보겠다.

1.정렬할 숫자 중 하나를 pivot으로 지정한다.

2.pivot보다 좌측에 있는 숫자들을 좌측 맨 처음 숫자부터 L이라는 위치가 이동하면서 L값이 pivot보다 크면 정지한다.

3.pivot보다 우측에 있는 숫자들을 우측 맨 마지막 숫자부터 R이라는 위치가 pivot쪽으로 이동하면서 R값이 pivot보다 작으면 정지한다.

(L과R은 pivot을 넘어가지 못한다. 그러므로 교차할 일은 없다.)

4.L과 R이 모두 정지하면 L값과 R값의 값들만 서로 swap해준다.

(L과 R의 위치는 바뀌지 않는다.)

5.L과 R이 모두 pivot에서 정지할 때까지 2~4를 반복한다.

(반복 할 때 L과 R의 위치는 초기화하지 않는다.)

6.L과 R이 모두 pivot에서 정지하면 pivot값은 이제 고정위치로 결정한다.

7.고정위치가된 기존 pivot의 좌측과 우측은 각각 다시 1~6을 반복한다.

(나뉜 측의 숫자들이 없거나 1개이면 바로 고정 한다.) 


위의 설명이 퀵정렬 기본 설명이다.

옛날 대학교 1학년때 배운 책에서는 

pivot을 (첫 index + 끝 index) / 2 의 위치에 있는 값으로 했는데

이것은 무의미 한것이다.(pivot이라는 말 자체는 중심이라는 뜻이긴 하지만...)

L과 R의 정지에 따라 pivot도 위치가 옮겨다니므로

굳이 중간 위치에 있는 것을 가져다 안써도 된다는 것이다.


오히려 요즘 퀵정렬은 L과 R이 pivot과 만나면 멈추는 것을 이용하여

pivot 자체를 맨 첫값이나 맨 끝값으로 하여 처음 부터 L이나 R을 멈추고 시작한다.

맨 첫 값을 pivot으로 하면 L이 pivot이므로 L은 멈추고

R만 맨 우측에서 작은 것을 찾아 바꾸고 

그러면 다시 R이 pivot이 되어

L이 움직여 큰것을 찾아 멈추고 바꾸면 L이 다시 pivot이 된다. 

L과 R 중 하나만 체크하게 되니

하는 방법도 쉽고 설명도 쉬워 

요즘 퀵정렬은 이렇게 잘 사용되는 추세이다.


이 방식으로 한번 소스를 짜보겠다.

//시작=====================================================

#include <iostream>


using namespace std;


int LeftMove(int* _arr, int _pivot, int _left);

int RightMove(int* _arr, int _pivot, int _right);


//퀵정렬

void QuickSort(int* _arr, int _left, int _right)

{

int leftIdx = _left;

int rightIdx = _right;


//L인덱스를 초기 피벗 인덱스로 초기화

int pivotIdx = leftIdx;


//R탐색 시작

pivotIdx = RightMove(_arr, pivotIdx, rightIdx); //최종 피벗의 인덱스 리턴 받는다.


//최종 피벗 인덱스가 정해지고 좌측에 남은 항이 2개 이상일 경우 

//퀵정렬 재귀호출

if (pivotIdx - leftIdx > 1)

{

QuickSort(_arr, leftIdx, pivotIdx - 1); //L인덱스 부터 피벗 인덱스 전 까지

}


//최종 피벗 인덱스가 정해지고 우측에 남은 항이 2개 이상일 경우 

//퀵정렬 재귀호출

if (rightIdx - pivotIdx > 1)

{

QuickSort(_arr, pivotIdx + 1, rightIdx); //피벗 인덱스 다음부터 R인덱스 까지

}


}

//L탐색

int LeftMove(int* _arr, int _pivot, int _left)

{

int pivotIdx = _pivot;

int leftIdx = _left;


//L 인덱스가 피벗의 인덱스보다 작다면 루프

while (leftIdx < pivotIdx)

{

//L 값이 피벗 값보다 크다면

if (_arr[pivotIdx] < _arr[leftIdx])

{

swap(_arr[leftIdx], _arr[pivotIdx]); //서로 값 교환

pivotIdx = RightMove(_arr, leftIdx, pivotIdx - 1); //R탐색 - 최종 피벗 인덱스를 리턴받는다

break;

}

else

{

leftIdx++; //값이 작다면 좌측으로 이동

}

}

return pivotIdx; //최종 피벗 인덱스를 리턴

}


//R탐색

int RightMove(int* _arr, int _pivot, int _right)

{

int pivotIdx = _pivot;

int rightIdx = _right;

//R 인덱스가 피벗의 인덱스보다 크다면 루프

while (rightIdx > pivotIdx)

{

//R 값이 피벗 값보다 작다면

if (_arr[pivotIdx] > _arr[rightIdx])

{

swap(_arr[rightIdx], _arr[pivotIdx]); //서로 값 교환

pivotIdx = LeftMove(_arr, rightIdx, pivotIdx + 1); //L탐색 - 최종 피벗 인덱스를 리턴받는다

break;

}

else

{

rightIdx--; //값이 크다면 좌측으로 이동

}

}


return pivotIdx; //최종 피벗 인덱스를 리턴

}


void main()

{

int arr[] = { 17, 29, 10, 41, 5, 92, 34, 26, 3, 18 };    //숫자 배열

int size = _countof(arr);


QuickSort(arr, 0, size - 1);    //퀵정렬 함수


        //결과 출력 부분

for (int i = 0; i < size; i++)

{

cout << arr[i] << " ";

}

cout << endl;

}

//끝=====================================================


대략 적으로 위와 같이 짜게 됐는데

결과는 잘 나온다.


예전 기억으로는 pivot구하기부터 엄청 복잡하게 짰었는데

L이나 R을 pivot으로 고정 시키니 많이 간략해지고 쉬워진 느낌이다.


이론적이든 결론적이든

실제 퀵정렬을 시작할 때 pivot의 위치는 중요하지 않다.

그 pivot이 고정되게될 위치가 중요하다.

고정되는 위치가 딱 중간이 되면 가장 좋은 것이고

맨 끝으로 치우치게 되면 가장 안좋은 것이다.

퀵정렬은 처리가 숫자가 많아질수록 느려질 가능성이 커지는데

한 루프마다 반씩 줄어든다면 처리량도 반씩 줄어들 것이다.

그리고 나중엔 하나씩 남는다면 

루프를 더 돌리지 않고 고정 값이 하나 더 생기니 더 좋을 것이다.

하지만 pivot의 고정될 결과는 미리 알 수 있는 것이 아니니

최악과 최고의 시간 복잡도가 나뉘는 것임을 알아두자.

소스는 없다.

이전 포스팅을 첨고로 봐보도록 하자.

-------------------------------------------------------

벡터(vector)는 크기(size), 용량(capacity)를 별도로 가지고 있다.

capacity는 현재 벡터가 차지할 수 있는 메모리 전체영역이고

size는 영역내에 채워져서 사용되고 있는 부분이다.


배열이 고정된 메모리를 가지고 처리한다면 

벡터는 유동적인 메모리 공간을 가질 수 있다.

벡터는 유동적인 배열로 불러도 손색이 없다.


그럼 여기서 궁금한 것이 생각났다.

erase와 resize로 벡터의 원소를 빼앗을 경우 어떻게 되겠는가?


예를 들어본다.

용량은 8이고 원소는 아래와 같이

100 200 300 400 500

5개의 원소를 집어 넣었다.

그럼 현재 벡터의 메모리 내부는

100 200 300 400 500  ?  ?  ?

이렇게 채워져 있을 것이다.


벡터를 이용한 접근 가능한 부분을 파란색

접근 불가한 부분을 붉은 색으로 재표시 해보자.

100 200 300 400 500  ?  ?  ?

하지만 at()으로 접근 할 수 있는 것은

벡터 begin() + 4 까지이다.(여기서 begin()은 end() - 5와 같고 begin() + 4는 end() - 1과 같다.)

5이상 부터는 에러가 난다.


여기서 erase(begin() + 4)을 하면 500을 지우라는 명령이 된다.

하지만 실제 500이 지워지는가?

아니다. 지워지지 않는다. 그대로 그자리에 남아있다.

단 벡터를 이용한 접근이 불가할 뿐이다.

색을 주어서 표시해보자.

100 200 300 400 500  ?  ?  ?


위처럼 메모리 상에 값은 남아있다.

하지만 저 주소를 알지 못하는 이상 접근 할 수 없다.


다시 300을 뺀다면 어떻게 될까?

300은 400에 덮여씌워져 지워진다. 하지만 400이 남는다.

100 200 400 400 500  ?  ?  ?


여기서 또 100을 지웠다고 해보자.

그럼 아래와 같이 메모리내에 값 구성이 된다.

200 400 400 400 500  ?  ?  ?

이제 대충 보이는가?
벡터 자체에서 at()함수나 [인덱스]로 접근만 하지 못할 뿐 내부에 값은 남아있다.
남아 있기 때문에 
주소 값을 지우기 전에 미리 받아내거나
begin()주소부터 + 인덱스 로 접근도 가능하다.
조사식으로 확인이 가능하였고 캡쳐 이미지를 아래에 첨부하겠다.

[조사식 분석]


resize()도 마찬가지이다.

5개 원소가 들어있을 때 

resize(3)을 하면

3 만큼만 접근 가능 하게 만들고

나머지 4, 5번째는 접근이 안되게 만들 뿐 내부에 데이터는 남아 있다.


평소에는 생각해보지 못하고 그냥 사용했었지만

이렇게 하나하나 분석을 하고 나니 더욱 

벡터와 친해진 느낌이다.

#include <iostream>

#include <vector>


//벡터 출력 함수

void Print(std::vector<int>& myvector)

{

std::vector<int>::iterator it;

std::cout << "myvector contains:";

for (it = myvector.begin(); it < myvector.end(); it++)

{

std::cout << ' ' << *it;

}

std::cout << '\n';

}


void main()

{

std::vector<int> myvector(3, 100); // size : 3, capacity : 3 , 맨처음 메모리 할당

std::vector<int>::iterator it;


Print(myvector);    //myvector contains: 100 100 100

it = myvector.begin();

it = myvector.insert(it, 200); // size : 4, capacity : 4 , 새로운 메모리 공간에 전부 재 할당

Print(myvector);    //myvector contains: 200 100 100 100


it = myvector.insert(it, 1, 300); // size : 5, capacity : 6 , 새로운 메모리 공간에 전부 재 할당

Print(myvector);    //myvector contains: 300 200 100 100 100


it = myvector.insert(it, 1, 302); // size : 6, capacity : 6

Print(myvector);    //myvector contains: 302 300 200 100 100 100


it = myvector.insert(it, 1, 303); // size : 7, capacity : 9 , 새로운 메모리 공간에 전부 재 할당

Print(myvector);    //myvector contains: 303 302 300 200 100 100 100


it = myvector.insert(it, 1, 304); // size : 8, capacity : 9 

Print(myvector);    //myvector contains: 304 303 302 300 200 100 100 100


it = myvector.insert(it, 1, 305); // size : 9, capacity : 9 

Print(myvector);    //myvector contains: 305 304 303 302 300 200 100 100 100


it = myvector.insert(it, 1, 306); // size : 10, capacity : 13 , 새로운 메모리 공간에 전부 재 할당

Print(myvector);    //myvector contains: 306 305 304 303 302 300 200 100 100 100


it = myvector.begin(); //이터레이터 변수 다시 시작지점으로 초기화


std::vector<int> anothervector(2, 400); // 다른 벡터

myvector.insert(it + 2, anothervector.begin(), anothervector.end()); // size : 12, capacity : 13

Print(myvector);    //myvector contains: 306 305 400 400 304 303 302 300 200 100 100 100


int myarray[] = { 501, 502, 503 }; // size : 15, capacity : 19 , 새로운 메모리 공간에 전부 재 할당

myvector.insert(myvector.begin(), myarray, myarray + 3);

Print(myvector);    //myvector contains: 501 502 503 306 305 400 400 304 303 302 300 200 100 100 100


myvector.pop_back(); // size : 14, capacity : 19 , 동일한 메모리 공간, 뒤에 것만 삭제, 반환값 없음

Print(myvector);    //myvector contains: 501 502 503 306 305 400 400 304 303 302 300 200 100 100


myvector.reserve(5); // size : 14, capacity : 19 , 이전 capacity보다 같거나 작으면 동작하지 않음

Print(myvector);    //myvector contains: 501 502 503 306 305 400 400 304 303 302 300 200 100 100


myvector.reserve(20); // size : 14, capacity : 20 , 새로운 메모리 공간 전부 재 할당

Print(myvector);    //myvector contains: 501 502 503 306 305 400 400 304 303 302 300 200 100 100


myvector.resize(5); // size : 5, capacity : 20 , 동일한 메모리 공간, 사이즈 외에 것은 삭제

Print(myvector);    //myvector contains: 501 502 503 306 305


myvector.shrink_to_fit(); // size : 5, capacity : 5 , 새로운 메모리 공간 전부 재 할당, 벡터 용량(capacity)을 크기(size)에 맞춤. c++11에 추가

Print(myvector);    //myvector contains: 501 502 503 306 305


getchar();

}


c++ 에서 class나 데이터를 한 군데 모아 사용하는데에 

가장 잘 사용했던 것이 vector였는데

이전 까지는 내부 메모리를 무시하고 사용했었다. 

그래서 단편화 현상 유발이나 

여러 다른 문제들을 야기 할 수 있다는 것을 간과했다.

하지만 위와같이 테스트해본 결과 메모리 처리가 

다른 STL들과는 다른 점이 있어서

정리를 해두려고 한다.


벡터는 size와 capacity가 별도로 있다.

capacity는 벡터에 허용된 용량을 나타내고

size는 그 용량 내에 차지하고 있는 크기를 나타낸다.


테스트 결과 벡터에서는 capacity가 바뀌는 상황이 되면

메모리 재할당이 이루어 지는 것을 발견 할 수 있었다.

벡터 내에서는 data들이 일렬로 배열처럼 들어 있는데

일렬로 원하는 용량이 들어 갈 수 있는 공간을 찾아서

메모리와 data들이 재 할당 되는 것이다. 

기존의 공간은 이제 비게 되고 다른 곳에서 사용가능해진다.

현재공간에서 size 외에 남은 용량은 다른 곳에서 사용하지 못한다.


용량을 함수로 늘리거나 줄이는거 외에

insert나 push_back으로 추가 삽입이 이루어 질때 

capacity를 넘기게 되면 자동으로 메모리 재할당이 이루어 진다.

하지만 1씩 증가되는 것이 아니라 현재 capacity의 1.5배로 늘어나는 것을 확인했다.

capacity 변화 순서 : 3 -> 4(4.5지만 소수 버림) -> 6 -> 9 -> 13(13.5지만 소수 버림) -> 19(19.5지만 소수 버림)


벡터는 리스트보다 데이터 검색이 빠르다.

하지만 추가 삭제가 빈번하여

메모리 재할당이 자주 일어나면 성능이 낮아질 수 밖에 없다.

그만큼 단편화도 자주 일어날 가능성이 크다.


벡터(vector)의 좋은 사용법은 

추가 삭제가 자주 일어나지 않아야 하고

미리 reserve로 최대 용량을 잡아주거나

resize로 원하는 만큼만 생성해 그 안에서 컨트롤 하는 것이다.

최대 용량은 단편화를 낮추기 위해 2의 배수가 좋을 것 같다고 생각한다.

#include <iostream>


using namespace std;

/*

피보나치 수열이란,

첫 번째 항의 값이 0이고 두 번째 항의 값이 1일 때,

이후의 항들은 이전의 두 항을 더한 값으로 이루어지는 수열을 말한다.

예) 0, 1, 1, 2, 3, 5, 8, 13


피보나치 수열의 n번째 수를 4,294,967,291으로 나누었을 때의

나머지를 출력하는 프로그램을 작성하시오.


정수 n이 입력으로 주어진다. (1 <= n <= 10^100000)

*/

#define MAXNUM 4294967291 //unsigned int 최대인 4294967295 보다 4 작음


unsigned int first = 0;

unsigned int second = 1;

int fibonacciCount = 0; //숫자 1, 2, 그외 체크용 카운트


//피보나치 계산 함수

unsigned int Fibonacci()

{

fibonacciCount++;

//cout << fibonacciCount << "번째";

if (fibonacciCount == 1) return first;

else if (fibonacciCount == 2) return second;

else if (fibonacciCount > 2)

{

fibonacciCount--; //카운트를 3이상 늘릴 필요는 없다


unsigned int temp = 0;

temp = first;

first = second;


if (MAXNUM - second < temp)

{

second = temp - (MAXNUM - second);

}

else

{

second += temp;

}

return second;

}

}


//0 체크 함수

bool NumIsZero(char* numChar, int size)

{

int count = 0;

for (int i = 0; i < size; i++)

{

if (*(numChar + i) == '0')

{

count++;

}

else

{

break;

}

}


if (count == size)

{

return true;

}

else

{

return false;

}

}


//숫자 -1 시키는 함수

void NumMinus(char* numChar, int size)

{

int idx = size - 1;


while (!NumIsZero(numChar, size)) //숫자가 0이 아니라면

{

if (*(numChar + idx) != '0') //자릿수가 0이 아니라면 

{

*(numChar + idx) = *(numChar + idx) - 1; //1 빼주고 나옴

break;

}

else

{

*(numChar + idx) = '9'; //자릭수가 0이면 9로 바꾸어 주고

idx--; //다음 자릿수를 찾기로 함

}

}

}


void main()

{

char n[256];     //숫자를 넣을 char 배열 - 높은 숫자를 하기 위해선 자리수만큼 더 늘려야 한다

cin >> n; //입력


char* charTemp = n;


while (*charTemp != '\0')

{

charTemp++;

}

int  charSize = charTemp - n;    //들어온 수의 사이즈


while (!NumIsZero(n, charSize))

{

cout << n << endl;        //진행도를 알기 위한 출력

NumMinus(n, charSize);

Fibonacci();

}


cout << endl << second << endl;

getchar();

getchar();

}


어제 했었던 문제와 같은 문제이다.

하지만 어제 무시했었던 10^100000을 처리하기 위해

char배열로 n값을 받고 char배열에 있는 값을 변환하여 처리하도록 하였다.

메모리 공간을 256이 아니라 더 많이 늘리면 자료형의 최대값(맥스값)보다 더 큰 값도 구하는 것이 가능하다.


이 코딩에는 큰 문제가 하나 발생하는데 

엄청 느리다


숫자를 char배열로 하나하나 컨트롤 하니 느리다곤 생각하고 있었지만

느려도 너무 느리다.


1000000000000000(1천조)를 처리하는데 대략 10개월 정도의 시간이 걸릴것 같다.

이런 프로그램을 짜서는 안되겠지만

그래도 짤려면 이렇게라도 짤 수 있단걸 보여주기 위해 해보았다.



문제 출처 : 코딩도장

코딩 출처 : 내 머리, 내 손

#include <iostream>


using namespace std;

/*

피보나치 수열이란,

첫 번째 항의 값이 0이고 두 번째 항의 값이 1일 때,

이후의 항들은 이전의 두 항을 더한 값으로 이루어지는 수열을 말한다.

예) 0, 1, 1, 2, 3, 5, 8, 13


피보나치 수열의 n번째 수를 4,294,967,291으로 나누었을 때의

나머지를 출력하는 프로그램을 작성하시오.


정수 n이 입력으로 주어진다. (1 <= n <= 10^100000)

*/

#define MAXNUM 4294967291 //unsigned int 최대인 4294967295 보다 4 작음


unsigned int Fibonacci(unsigned long long n)

{

if (n > 2)

{

unsigned int first = 0;

unsigned int second = 1;


unsigned int temp = 0;

unsigned long long count = n - 2;


for (unsigned long long i = 0; i < count; i++)

{

temp = first;

first = second;


if (MAXNUM - second < temp)

{

second = temp - (MAXNUM - second);

}

else

{

second += temp;

}

}

return second;

}

else if (n == 2) return 1;

else return 0; //if (n <= 1) 

}


void main()

{

unsigned long long n = 1000000000;

cout << Fibonacci(n) << endl;

getchar();

}


10^100000 까지 입력할 수 있는 방법을 무시하고

기본 자료형으로 최대치를 계산 할 수 있는 unsigned long long 을 사용하여 구하였다.

리턴될 수 있는 최대값는 unsigned int 보다 작기 때문에 unsigned int 을 사용하였다.


MAXNUM  < temp +  second 을 계산하여야 하나

temp +  second을 해버리면 자료형 맥스 값을 넘어가므로

값 하나를 넘겨 MAXNUM - second < temp 로 조건을 주었다.

마차나지로 temp + second - MAXNUM 을 해야 하나

temp + second은 맥스 값을 넘기게 되므로

자료형 맥스 값을 안넘기기 위해

temp - (MAXNUM - second)로 처리하였다.


위처럼 자료형의 최대치(맥스값)를 넘기지 않게 컨트롤 하지 않고

무작정 더해버리면 원하지 않는 결과가 나와버린다.

커다란 숫자를 컨트롤 해야 할 경우에는

자료형의 최대값(맥스값)을 항상 고려하면서 코딩을 해야 한다.



문제 출처 : 코딩도장

코딩 출처 : 내 머리, 내 손

#include <iostream>


using namespace std;


void main()

{

unsigned short a = 0x1;      // 0001

unsigned short b = 0x5;      // 0101


cout << (a^b) << endl;   // 4 = 0100


getchar();

}


컴퓨터 관련 전공이나 전산 쪽이면 XOR는 많이 봤을 것이다.


A

B

결과 

 0

 0

 0

 0

 1

 1

 1

 0

 1

 1

 1

 0


A와 B의 입력이 있을 때 둘 중 하나만 1일 경우가 1이다 라는 것인데

이를 코드로 표현 할 일이 거의 없지만 

두개의 변수의 값을 교환 할때 사용할 수 있단 걸 알게 되었다.


이전에는

temp = a;

a = b;

b = temp;

이런식으로 별도의 동일한 자료형의 더미를 만들어 Swap을 하였으나

아래와 같이 XOR을 3번 해주고 각각 변수 A, B, A 순으로 담아주면

더미 변수 없이도 서로 값이 바뀌는 것을 결과로서 볼 수 있다.

주석의 이진 수를 참고하여 아래의 코드를 봐보자.


#include <iostream>


using namespace std;


void main()

{

int a = 127; //1111 1111

int b = 59; //0111 1011


a = a ^ b; // a = 1000 0100

b = a ^ b; // b = 1111 1111

a = a ^ b;  // a = 0111 1011


cout << a << endl;   

cout << b << endl;  



int c = 60817; //0000 0000 0000 0000 0000 1110 1101 1001 0001

int d = 201606; //0011 1011 0110 0100 0111 0001 0011 1000 0110


c = c ^ d; // c = 0011 1011 0110 0100 0111 1111 1110 0001 0111

d = c ^ d; // d = 0000 0000 0000 0000 0000 1110 1101 1001 0001

c = c ^ d;  // c = 0011 1011 0110 0100 0111 0001 0011 1000 0110


cout << c << endl;

cout << d << endl;


getchar();

}




 #include <iostream>


using namespace std;

/*

계단을 오르는 방법에는 여러가지가 있습니다. 

한 계단씩만 올라가는 방법이 있고, 

두 계단만 올라가는 방법이 있고 혹은 

세 계단만 올라가는 방법이 있습니다.


계단의 수를 n이라 하고

한번에 올라갈 수 있는 계단의 수를 최대 세 계단이라고 했을 때 

계단을 올라갈 수 있는 경우의 수를 구하시오.

*/

int Fibonacci(int n)

{

if (n == 1) return 1;

else if (n == 2) return 2;

else if (n == 3) return 4;

else if (n > 0)

{

int first = 1;

int second = 2;

int third = 4;


int temp = 0; //임시 변수

int count = n - 3; //반복을 돌릴 카운트


for (int i = 0; i < count; i++)

{

temp = first;

first = second;

second = third;

third += temp + first; //third = temp(first) + first(second) + third

}

return third;

}

else

{

return -1;

}

}

//메인함수

void main()

{

cout << Fibonacci(6) << endl;

getchar();

}


해설 : 

이런건 노트에 적어가면서 풀어보면 규칙이 보입니다.

계단이 하나일 경우 (1) 계단 만 오를 수 있으니 경우의 수는 1 입니다.

두개일 경우 (1, 1)로 오르거나 (2) 계단 오를 수 있어서 경우의 수가 2입니다.

계단이 세 개일 경우 (1, 1, 1)로 오르거나 (1, 2) 로 오르거나 (2, 1)로 오르거나 (3) 계단 전부 오를 수 있으므로 경우의 수는 4개 입니다.

계단이 네 개일 경우 (1, 1, 1, 1), (1, 1, 2), (1, 2, 1), (2, 1, 1), (2, 2), (1, 3), (3, 1) 의 경우로 경우의 수는 7 입니다.


감이 좋으신 분들은 여기에서 눈치를 챘을 수 있습니다.

잘보면 보이는 규칙이 있습니다.

저는 계단 다섯 개 까지 경우의 수를 써보고 규칙을 찾아냈습니다.


1로 시작 할경우 바로 전 계단의 경우의 수와 동일한 경우나 나타납니다.

2로 시작 할 경우 바로 전전 계단의 수에서의 경우의 수가 나오고

3로 시작 할 경우 바로 전전전 계단의 수에서의 경우의 수가 나오는 것이 보입니다.

 

네 계단 에서의   (1, 1, 1, 1), (1, 1, 2), (1, 2, 1), (2, 1, 1), (2, 2), (1, 3), (3, 1) 을 보기 좋게 나열해보면

 (1, 1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 3)

 (2, 1, 1), (2, 2)

 (3, 1)

이고 첫 숫자들만 지워보면

 (1, 1, 1), (1, 2), (2, 1), (3)

 (1, 1), (2)

 (1)

이렇게 되고 이는 각각 세 계단일 경우, 두 계단일 경우, 하나의 계단일 경우 와 동일 합니다.


즉 앞의 숫자 3개를 더하면서 진행되는 피보나치 수열을 짜면 문제는 해결 되는 것입니다.

이해가 잘 안되시는 분들은 다섯, 여섯 계단 일 경우도 노트에 적어 보시면 이해가 되실 것 같습니다.



문제 출처 : 코딩도장(http://codingdojang.com/)

코딩 출처 : 내 머리, 내 손