1. 코드

1-1) 메인



1-2) 함수



1-3) 출력




2. 해설

 이번 선형 자료구조인 링크드 리스트 뒤집기 알고리즘은 먼저 O(n)에 풀리는 알고리즘이다. 아주 간단한 알고리즘이지만 

포인터를 활용하는데 가장기초적인 부분이기 때문에 연습삼아 작성했다. 핵심은 3가지다. 기존 주소의 보존, 알고리즘 처리, 

다음 단계로의 이동. 이 코드는 무척 단순하지만 모든 반복문 코드가 이 세가지에서 이루어진다. 그렇기때문에 이를 명확히 

기술할 수 있는 지에 대한 연습이었다.



1. 개요

 문제는 이렇다. 어떤 리스트가 있다. 그 리스트의 구조는 모른다. 이 때, 이 리스트의 구조가 순환 형태인지 아닌지 어떻게

알 수 있을까? 

 이 문제를 O(n)에 풀어내는 알고리즘이 플로이드의 순환 찾기 알고리즘이다.

  

플로이드의 순환 찾기 알고리즘을 단순히 표현하면 이렇다. 만약

 

 (출처) https://nol2soft.wordpress.com/2013/07/13/linked-list-cycle-detection-explanation


 이런 형태의 순환 리스트가 있을 때, 토끼와 거북이가 시작점에서 출발한다. 토끼는 거북의 속도보다 2배 더 빠르다. 

토끼가 먼저 출발한다. 당연히 토끼보다 느린 거북이는 평생 토끼를 잡을 수 없다. 그렇지만 만약 둘이 만나게 되면

이 리스트는 순환 구조를 가졌다고 판명된다. 


 여기까진 굳이 플로이드의 순환 찾기 알고리즘이라는 거창한 이름없이 그냥 당연한 알고리즘이다. 하지만 이제부터 

엄청난 사실이 등장한다. 바로 이 순환의 시작점을 어떻게 찾을까? 라는 문제가 나온다면 어떻게 풀어야할까? 


2. 풀이

 즉, 위의 그림에서 m의 길이를 어떻게 찾아낼까? m의 값을 모른다. n의 값도 모른다. 

 이 문제는 정수 이론의 핵심이다. ( 나는 잘모른다. ) 하지만 이론은 일단 제쳐두고 아주 간단한 사실이있다.


 답은 이렇다. 

 토끼는 토끼와 거북이가 만난 자리인 k에서 출발하고 거북이는 시작점에서 출발한다. 

 이 때, 둘이 만나는 최초의 자리가 바로 순환의 시작점이다.


 너무 놀랍다. 그리고 한동안 이게 왜 이런지 알 수가 없었다. 하지만 우리가 색깔을 판단할 때 파랑은 파랑이지

파랑색을 이루는 가시광선이 어떤지 알 필요는 없다. 마찬가지로 나도 이해없이 이 사실만을 알아두고 훗날 필요한

일이 있을 때 써먹어 보고 싶었지만 너무 궁금해서 증명을 찾아냈다.


 m = 순환까지의 거리 (찾아야 할 값)

 n = 순환 루프의 길이

 k = 순환 시작점에서 토끼와 거북이가 만난 자리까지의 거리


 자, 여기서 위의 이론을 증명하려면 k와 시작점에서 한 칸씩 이동했을 때 둘이 만나는 장소가 순환의 시작점이다.

=> m + k (시작점에서 만난 자리까지의 거리) + n - k (루프 길이에서 k값을 뺀 나머지) 이 m이라면 성립된다.

=> ( m + k ) + ( n - k ) = m 에서 n - k가 m과 같냐는 것을 보이고 싶기 때문에 n - k = m을 대입한다.

=> ( m + k ) + m = m

=> m + k = 0

=> m = -k

하지만 거리를 구하는 것이기 때문에 절대값을 붙이면 m == k는 동등하다.


3. 결론

 일단 역시 스승님은 위대하다. 이런 사실을 알게 해주셔서. 하지만 솔직히 이 이론을 언제 써먹어서 코드로 작성

할 날이 올지 모른다. 아마 평생 안올지도 모른다. 하지만 굳이 이 글을 쓴 이유는 이 알고리즘을 이해하려고 노력

하면서 처음으로 수학적 공식을 대입하며 증명을 해보려 노력하였다. 한 번 해봤으니 앞으로도 수학적 이론이 겹친

문제에서 이렇게 풀이할 수 있다는 접근법을 알게되었다. 이와같은 방법으로 계속 연습하여 수학 코딩에서 역량을

발전시키자!! 할 수있다! 쫄지말자!


  




1. 개요

 메모리 최적화 이중 연결 리스트의 아이디어는 충격적이다. 이를 작성하는 이유는 내게 너무나 생각의 폭을 한번 더

넓히게 만들어준 알고리즘이기 때문이다. 그리고 그 아이디어는 무척 간단하다.


 일반적인 이중 연결 리스트의 자료구조다.

 struct Node{

int data;

struct Node* prev; //이전 포인터

struct Node* next; //다음 포인터

 }


 위 자료구조는 당연하다.

 하지만 메모리 최적화 이중 연결 리스트는 위의 자료구조에서 2개의 포인터를 하나로 압축했다.


 메모리 최적화 이중 연결 리스트 자료구조다.

 struct Node{

int data;

struct Node* ptrdiff; //pointer difference의 약자

 }


 어떻게 이게 가능할까? 해답은 XOR 연산에 있다.


2. 풀이

 먼저 이 알고리즘을 위해 알아야 할 XOR 연산의 속성이다.

 (1) X XOR X = 0 (같으면 0이다.)

 (2) X XOR 0 = X (다르면 자기 자신이다.)

 (3) X XOR Y = Y XOR X (대칭이 성립한다.)

 (4) ( X XOR Y ) XOR Z = X XOR ( Y XOR Z ) (교환 법칙이 성립한다.)


 ptrdiff 포인터 변수에는 prev 포인터와 next 포인터의 XOR 연산의 결과이다.

 

 만약 A -> B -> C -> D -> NULL 이라는 리스트가 있고, 현재 위치가 B라면 

 B가진 ptrdiff의 값은 A의 주소와 C의 주소의 XOR 연산 값이다.


 ptrdiff = A XOR C 일 때, 

A 노드로 가기 위해서는 

( A XOR C ) XOR C => A XOR ( C XOR C ) => A XOR 0 => A 노드의 주소가 나타난다.

C 노드로 가기 위해서는 

( A XOR C ) XOR A => ( A XOR A ) XOR C => 0 XOR C => C 노드의 주소가 나타난다.


3. 결론

 정말 대단한 아이디어다. 그런데 ... 그런데 !! 이 포스트를 작성하다보니 하나 이해 안되는 부분이 있다. 이 자료구조에서 

알고있는 ptrdiff는 A XOR C 인데 이것과 연산하는 A의 주소와 C의 주소는 어떻게 알지????


 스승님!!???????


 이해가 안된다. 찾아봐도 안된다. 하지만 이 부분에 대한 이해는 다시 한 번 찾아보기로 하고 일단 넘긴다. 그러나 

중요한 것은 스승님께 쓰여있는 말이다.


 '최근에 삽입, 탐색, 그리고 삭제 동작을 가진 이중 연결 리스트 추상 자료형 대체 구현이 학술지에 제시됐다.'

라는 구절이다. 


 이 말은 스승님께서는 항상 학술지를 쳐다보며 새로운 알고리즘이 등장했는지 꾸준히 찾아보신다는 것이다.

이런 습관이 세계적인 개발자로 거듭나게 되는 방법인가보다. 그리고 영어가 왜 중요한지 알려주는 일이기도

하다. 


 이 글을 작성한 이유는 아직 명확하진 않더라도 이런 기법도 있다는 것과 스승님과 같은 습관을 그리고 

프로그래머로서의 자세를 배워서 이를 쓰기 위해서다. 계속 사고의 한계를 늘려가자!!


  



1. 개요

 프로그래밍 공부를 하다보면 리커젼 기법을 반드시 만나게 된다. 그리고 이 부분에서 수 많은 탈락자가 발생한다.

마치 C언어의 배열, 포인터, 그리고 파일 입출력, 자료구조의 뒤를 잇는 허들이다. 나 역시 리커젼을 아직도 완벽히

이해를 못한다. 단순히 기초만을 알 뿐이다. 하지만 이번에 내 스승님이 된 책을 읽으면서 리커젼의 기본적인 틀을

배웠다. 바로 이렇다.


 스승님이 말하셨다. 모든 리커젼을 이 틀에서 벗어나질 못한다.


 if ( 종료 조건 )

return 종료 값

 else if ( 또 다른 종료 조건 )

return 또 다른 종료 값

 else

return ( 몇 가지 작업 ) and then ( 재귀 호출 )


 정말이지 위대한 틀이다. 이 틀에 가장 적합한 부분은 합, 팩토리얼, 피보나치 수열이다. 이 세 가지 기법은 모두

기업 면접과 시험에 다 만나봤다. 그렇기 때문에 반드시 숙지해야한다. 나한테 하는 말이다. 특히 우리나라에서

가장 큰 게임 기업의 면접 코딩 문제 중 하나로 리커젼을 통한 1부터 n까지의 합을 구하는 문제가 있었고 또 다른

가장 큰 게임 기업의 필기 시험 문제에서는 피보나치 수열을 리커젼으로 풀 때 문제점과 반복문으로 바꾸는 문제

가 있었다. 물론 나는 잘 풀었다. 그래서 합격했다.


 1부터 N까지 합을 구하는 코드를 리커젼으로 작성하면 이렇다. 


 int Function ( int n ){

if ( n == 0 )

return 0;

else 

          return n+Function(n-1);

 }


 정확히 위의 표현식과 일치한다. 


 만약 내가 스승님을 좀 더 빨리 만났다면 그 면접 시간에 이 코드를 작성하지 못해 허둥대고 당황하고 식은 땀을

흘리면서 세상에서 가장 길면서도 짧았던 그 면접 시간을 안느끼고 순식간에 풀어냈겠지.


 반드시 위의 표현식을 기억하자 !!



 그리고 또 하나 반드시 익혀야 할 것! 


 모든 리커젼 기법은 반복문으로 작성할 수 있다. 이것도 연습해야 한다. 이를 해낼 수 있을 때 실력이 한층 발전한다.




2. 재귀 vs 반복

 (1) 일반적으로 반복 방식이 재귀보다 성능면에서 효율적이다. ( 재귀 방식은 함수 호출의 오버헤드가 크다. )

 (2) 그럼에도 재귀를 쓰는 이유는 몇몇 문제에서 명확한 반복 알고리즘이 존재하지 않을 수 있다. 또한

     몇몇 문제에서 반복적인 방법들 보다 재귀적인 해결책이 가장 적합할 수 있다.

 (3) 재귀 코드는 반복 코드보다 짧고 쉽다. 그러므로 메모리 공간을 써서 가독성을 높이는 결과를 만든다.

 (4) 재귀 코드는 자신을 호출할 때마다 원래 문제의 범위보다 조금씩 줄어들어야 한다. (반드시!)

 (5) 재귀 코드의 비효율적인 문제를 해결하기 위해서 동적 프로그래밍(Dynamic Programming)이 등장했다.

     이는 재귀 코드의 시간을 획기적으로 줄일 수 있다. ex) 2^n을 n^2이나 또는 n^2을 nlogn으로 풀어낸다.


 3. 결론 

  (1) 재귀와 반복이 싸우면 반복이 우세하다. 하지만 나중에 나오는 동적 프로그래밍을 위해서 재귀는 필수다.

  (2) 하지만 동적 프로그래밍은 나중에 어마어마하게 사용된다. 기업 코딩 시험에서도 안나오는 경우가 거의 없다.

  (3) 즉, 재귀 공부를 미치도록 하자! (어지간한 문제는 재귀로 다 풀린다.)






+ Recent posts