Stack A와 B가 있다고 가정하고, 최초에 Stack A에 숫자를 넣으면,
Stack A와 Stack B를 이용하여 Stack A에 "오름차순"으로 정렬을 하면 마무리 되는 과제이다.
횟수를 <가장 적게> 정렬하는 것이 이 과제의 포인트다.
다른 분들도 비슷하게 알고리즘을 만들었지만, 나의 경우는 들어온 숫자들을 크기별로 3개의 덩어리로 나누고 그게 또 크다면 다시 3개의 덩어리로 나누고 계속적으로 나누어 덩어리 내의 갯수가 3개 이하가 나올때까지 반복하는 작업을 한다.
: 3개나 나올때까지 나누는 이유는 3개가 되어야 정렬하기 수월해지기 때문이다.
이렇게 하는 방법은 quick sort와 유사하다.
: 하지만 다른 것은 들어온 수를 미리 정렬을 한 뒤에 거기에서 1/3지점의 값, 2/3지점의 값을 미리 확인하고 3덩어리로 나눠준다.(이 과제는 시간 복잡도를 고려하는 과제가 아니라는 것을 알아야 한다. 물론 시간 복잡도가 좋으면 당연히 좋다, 이 과제는 시간 복잡도 보다 명령어의 횟수를 적게 하는 것이 목표이다.)
1.A에 있는 큰 덩어리를 미리 정렬해서 얻은 피벗(1/3지점의 값, 2/3지점의 값)의 기준으로 나눈다.(1번 그림)
: 그렇게 하면 1/3지점의 값보다 작은 값은 [1], 1/3~2/3지점의 구간은 [2], 2/3보다 큰 구간은 [3]이 된다.
: 예를 들면 1,2,3,4,5,6,7,8,9가 A스택에 들어왔으면
[1]의 구간은 1,2,3, [2]의 구간은 4,5,6 [3]의 구간은 7,8,9가 된다고 생각하면 된다.
: 위처럼 A에 [3] B에 [2][1]순서로 둔 것은 이 순서로 다시 a로 넘기면 오름차순의 형태이기 때문이다.
: b스택을 나눌 때에는 정리된
[3_3]의 위쪽에 [3_2_3](3_2중 가장 큰 영역),
[3_3]의 아래쪽에 [3_2_2](3_2중 중간 영역),
b쪽에 [3_2_1](3_2중 가장 작은 영역)을 둔다.
만약 [3_2_3]영역이 정렬이 다 되면 [3_2_2]를 올려준다!
5.나누다 보면 다양한 케이스들이 발생되는데 (4번 그림)을 보면 [3_2_2]를 올려주려고 하는데
알고보니 b쪽에 아직 정리 되지 않은 [3_2_3]이 남아있는 경우가 발생이 된다.
: 이를 방지 하기 위해 [3_2_2]같은 대상들을 올려주기 전에 b쪽에 [3_2_2]이 있는 수보다 큰 수가 있으면 b쪽부터 정리를 하게끔 알고리즘을 짜두었다. 이는 간단하게 3_2_2의 가장 뒷수와 b스택의 상단을 비교만 하면 끝나는 일이다.
6.추가로 3개가 들어오는 케이스와 5개가 들어오는 케이스는 약간 하드 코딩을 해주었다.
: 3개는 그냥 하드코딩을 하면 된다.(쉽다)
: 5개의 경우 먼저 정렬을 하여 가장 중앙의 수를 찾은 후에 그 수보다 작은 수 두개를 b쪽으로 보내버리면 a쪽에 3개가 남게 되는데, 이때부터는 a에 있는 대상은 3개가 있는 것과 똑같이 생각을 하면 된다.
그 후에 3개 정렬했던 알고리즘을 적용하면 a쪽이 정리가 되고, 나머지 b에 남아있는 수를 처리해주면 간단히 처리가 된다.
7.이렇게 해서 나온 대상들을 나의경우는 하나하나 버퍼에 담아 주었고, 정렬이 다 끝나게 되면 이를 알고리즘 최적화 해주는 코드를 따로 해주었다.
8.알고리즘을 완료하게 되면 각각 수행하는 함수들은 각각 독립적이기 때문에 서로가 어떻게 명령어를 냈는지 알수가 없다. 하지만 나온 명령어들을 보면 우리는 최적화 할 수 있는 포인트들을 찾을수가 있다.
1) 예를들면 sa, sb는 ss로 ra, rb는 rr로 rra,rrb는 rrr로 명령어를 바꿀수 있고 2개 > 1개의 효과를 낳는다.
2) 추가로 ra,rra가 연달아 나온다면 돌리고 반대로 돌리고를 할 필요가 없어서 아예 삭제를 해주면 된다.
3) 1)의 경우 ra rb만 rr로 가능한것이 아니다. 예를 들어 ra rr rr rb가 있다면 이것도 rr rr rr로 바꿀수가 있다.
: 추가로 최적화 할 수 있는 포인트들이 있었지만 이 정도로만 해두었다.
9.이 알고리즘의 경우 최종적으로 아래와 같은 횟수가 나왔다.
case 3개 : 0~2개
case 5개 : 10개 이하
case 100개 : 680개 이하
case 500개 : 4600개 이하
: 과제를 하면서 힘들었던 순간이나 헤맸던 순간을 적어놓았다.
1) 가장 큰 문제는 int malloc을 해주었는데 while문을 통과하기전에 i = size로 해두고 a[i] = 0 초기화를 한 사태가 있었다… 없는 곳에 접근을 한 셈이다.
: 예를 들어 a[4]까지만 초기화를 시켜줘야하는데 a[5]를 초기화 시켜주어서 프로그램이 돌때 다른 메모리를 접근해 버리는 상황이 나타났다. 가장 기초적인 것을 실수하는 바람에 모든 것이 삽질이 되었고 덕분에 1주일 이상은 날렸다.
: 하지만 이 사태로 인해서 기본이 얼마나 중요한 것인지 깨달은 계기가 되었다.
2) 상위의 스코프에서 현재 주소를 사용하는데 free를 해주어 문제가 발생된 상황
: 상위의 스코프에서 free해주는 부분의 주소를 사용하는 부분이 있었다. 이 부분에 대해서 아무것도 처리를 해주지 않아서 상위 스코프는 free된 곳을 계속 접근을 하여 프로그램이 이상하게 되었다. 향후에도 이 부분을 조심해서 접근해야 될듯.
: 상위 스코프에서 현재의 메모리를 사용한다면 꼭 주의하여 free를 해주어야 한다!
3)정렬이 되었는지 확인하는 부분을 적절하게 배치하지 못하여 예외상황이 많이 발생하였다.
4)leak잡기
: split으로 들어온 대상들을 분할 해준다음에 쓴 메모리를 해제해줘야하는데, 2중 포인터 인것을 착각해서 leak가 발생하였다. 이로 인하여 leak를 잡는데 시간을 많이 소모해주었다. 2중포인터이기 때문에 안에 있는 대상들도 해제를 따로 해주고 밖에 있는 대상도 해줘야한다.
: 필요 없는 부분들이 malloc해제되는지 체크할것.
: 한번은 다른곳에 char *을 말록 할당을 한뒤에 다시 또 할당을 해서 메모리 누수가 발생한 적도 있었다.
약 3주간 과제를 하였는데 나름 정말 열심히 하였다. 3주내내 몰입해서 과제를 수행했던 것 같다. 중간에 삽질을 정말 많이 하였지만 그것으로 인해서 기본이 왜 중요한지에 대해서 많이 생각하게 되었고, 예외처리를 정말 많이 한 것 같다.
평가를 진행하려고 그림도 나름 그리고 준비를 하였지만 생각보다 처음 보신 분들에게 설명을 해주는 것이 쉽지는 않았다. 다음 평가때는 더 쉽게 설명을 해보도록 해야겠다.
이론 공부를 많이 해보려고 했으나, 생각보다 많이 하지는 못하였다. 알고리즘 책은 향후에 다시 빌려서 다시 공부를 해야겠다.
오랜만에 피신때의 느낌이 들어서 좋기도 했고, 힘들긴 했지만 재밌었던 과제였었다. 많은 아쉬움과 애정이 남는 과제로 남을 것 같다.