Programming/Data Structure2013. 5. 30. 13:03

 지금까지 만든 스택 연결 리스트가 올바르게 동작하는지 확인하기 위한 출력 함수를 구현하였다.

스택 연결 리스트의 데이터의 입력, 출력이 일어날 Top 의 위치, 데이터를 출력한다.

 

 스택 연결 리스트의 선언부 이다.(Stack.h)

 스택 연결 리스트의 생성과 관련된 함수(ifCreateStackLinkedList), 소멸과 관련된 함수(ifDestroyStackLinkedList), 기본 기능과 관련된 함수(vfPushStackLinkedList, unfPopStackLinkedList, unfTopStackLinkedList, ifEmptyStackLinkedList) 로 이루어져 있다. 필요에 따라 함수를 수정, 추가, 구현하거나, 저장되는 자료형을 추가한 후, 생성함수를 수정하여 사용할 수 있다.

 

 스택 연결 리스트의 구현부 이다.(Stack.c)

 

 마지막으로 스택 연결 리스트를 사용하는 예제이다.

 vfPrintStackLinkedList 함수가 호출되는 시점을 기준으로 다음과 같이 3 단계로 나눌 수 있다.

1 단계 : 스택을 생성한 후, "1, 2, 3, 4, 5" 를 스택에 Push(데이터 저장)

2 단계 : 스택에서 2 번 Pop(데이터 제거)

3 단계 : 스택의 가장 마지막 데이터 출력

Posted by 개발자테오
Programming/Data Structure2013. 5. 30. 12:57

 연결 리스트 (Linked List) 로 스택을 구현하려 한다. 연결 리스트로 스택을 구현하면, 배열 스택의 단점인 고정된 데이터 저장 공간이라는 점을 극복할 수 있다. 아래의 그림과 같이 스택을 연결 리스트로 구현할 것이다. 단순 연결 리스트 (Single Linked List) 로 구현하되, 마지막 노드를 가리키는 포인터 변수를 저장하여 Push 와 Pop 연산시, 마지막 노드를 탐색하는 시간을 줄이도록 한다.

 

  다음은 스택 연결 리스트 구조체의 선언이다.

 데이터가 저장되는 리스트의 첫번째 노드, 마지막 노드를 가리키는 포인터 변수를 저장한다.

 

 다음은 스택 연결 리스트의 생성 함수, 스택 연결 리스트의 사용이 끝났을 때, 해당 메모리를 반환하는 함수이다.

 

 다음은 스택의 기능인 Push(데이터 저장), Pop(데이터 제거), Top, Empty 함수이다.

 가장 위의 그림에서 보여준 것과 같이 기능을 구현하였다. 단순 연결 리스트는 이전에 구현하였으므로, 구현해놓은 헤더파일(SingleLinkedList.h) 을 포함시켜서 이용하였다. Push(데이터 저장), Pop(데이터 제거) 함수는 기능상 동일하나, 단순 연결 리스트이므로, Pop 시에 이전 노드를 가리키기 위한 연산이 추가되었다. 이 연산을 더 효율적으로 하기 위해서는 이중 연결 리스트 (Double Linked List) 로 구현하면 된다.

 연결 리스트 스택은 배열 스택과 달리 스택이 Full 인지를 확인할 필요가 없다. 메모리를 데이터에 맞게 할당받아 저장하기 때문에 필요에 따라 계속해서 데이터를 저장할 수 있다.

Posted by 개발자테오
Programming/Data Structure2013. 5. 29. 12:30

 지금까지 만든 스택 배열이 올바르게 동작하는지 확인하기 위한 출력 함수를 구현하였다.

 스택 배열의 크기, 데이터의 입력, 출력이 일어날 Top 의 위치, 데이터를 출력한다.

 

 스택 배열의 선언부 이다.(Stack.h)

 스택 배열의 생성과 관련된 함수(ifCreateStackArray), 소멸과 관련된 함수(ifDestroyStackArray), 기본 기능과 관련된 함수(ifPushStackArray, unfPopStackArray, unfTopStackArray, ifEmptyFullStackArray) 로 이루어져 있다. 필요에 따라 함수를 수정, 추가, 구현하거나, 저장되는 자료형을 추가한 후, 생성함수를 수정하여 사용할 수 있다.

 

 스택 배열의 구현부 이다.(Stack.c)

 

 마지막으로 그 동안 구현한 스택 배열을 사용하는 예제이다.

 vfPrintStackArray 함수가 호출되는 시점을 기준으로 다음과 같이 3 단계로 나눌 수 있다.

1 단계 : 배열의 크기를 10 으로 스택을 생성한 후, "1, 2, 3, 4, 5" 를 스택에 Push(데이터 저장)

2 단계 : 스택에서 2 번 Pop(데이터 제거)

3 단계 : 스택의 가장 마지막 데이터 출력

Posted by 개발자테오
Programming/Data Structure2013. 5. 29. 08:37

 이해하기 쉬운 배열로 스택을 구현하려 한다. 배열로 스택을 구현하면, 데이터에 접근하기가 쉽고, 구현이 간단하다는 장점이 있다. 하지만, 메모리를 고정하여 사용하기 때문에, 처음에 구성된 스택의 크기를 벗어나는 데이터를 저장할 수 없다는 단점이 있다. 아래의 그림과 같이 스택을 배열로 구현할 것이다. 고정된 크기의 배열을 생성하고, Top 위치를 저장하고 있다가 Push, Pop 등의 연산을 통해 데이터를 저장하고, 제거할 것이다.

 

 다음은 스택 배열 구조체의 선언이다.

 스택 배열의 크기, 그리고 데이터의 입력, 출력이 일어날 Top 의 위치, 데이터 메모리 주소를 저장한다.

 

 다음은 스택 배열의 생성 함수, 스택 배열의 사용이 끝났을 때, 해당 메모리를 반환하는 함수이다.

 처음에 요청된 스택 배열의 크기만큼의 데이터 공간을 가지고 있게 된다.

 

 다음은 스택의 기능인 Push(데이터 저장), Pop(데이터 제거), Top, Empty 함수이다.

 가장 위의 그림에서 보여준 것과 같이 기능을 구현하였다. Push(데이터 저장) 함수는 스택의 데이터 공간의 가장 위에 데이터를 저장하고, 가장 위를 저장하는 변수를 증가시킨다. 반대로 Pop(데이터 제거) 함수는 스택의 데이터 공간의 가장 위의 데이터를 반환하고, 가장 위를 저장하는 변수를 감소시킨다.

 일반적으로 사용되는 함수와 다르게 구현한 부분은 Empty 함수이다. 일반적으로 Empty 인지를 반환하는 함수인데, 여기서는 Empty 와 Full 여부를 반환하도록 구현하였다. 이를 통하여, Push 전에 Full 인지를 확인하여, 스택의 데이터 공간이 모두 사용 중이면, Push 를 하지 않거나, Pop 전에 Empty 인지를 확인하여, 스택의 데이터 공간이 하나도 사용하지 않고 있다면, Pop 을 하지 않는 용도로 사용할 수 있다.

Posted by 개발자테오
Programming/Data Structure2013. 5. 28. 12:44

 스택(stack)제한적으로 접근할 수 있는 나열 구조이다. 그 접근 방법은 언제나 목록의 끝에서만 일어난다. 끝 먼저 내기 목록(Pushdown list)이라고도 한다. 스택은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형 구조(FILO - First In Last Out)으로 되어 있다. 자료를 넣는 것을 '밀어넣는다' 하여 푸시(push) 라고 하고 반대로 넣어둔 자료를 꺼내는 것을 팝(pop) 이라고 하는데, 이 때 꺼내지는 자료는 가장 최근에 보관한 자료부터 나오게 된다. 이처럼 나중에 넣은 값이 먼저 나오는 것을 LIFO 구조라고 한다.

(참고 : http://ko.wikipedia.org/wiki/%EC%8A%A4%ED%83%9D)

 

 데이터를 저장하는 방법인 스택은 FILO(First In Last Out), LIFO(Last In First Out) 으로 불리는데, 먼저 들어간 자료가 나중에 나오고, 나중에 들어간 자료가 먼저 나온다는 의미이다. 스택의 중요 기능으로는, 데이터를 저장하는 Push, 데이터를 빼내는 Pop 이 있다. 아래의 그림과 같이 Push 를 통해 스택에 데이터를 쌓고(저장), Pop 을 통해 스택에서 데이터를 빼내게(삭제) 된다.

 그 밖의 기능으로는 스택이 비어있는지 확인하는 Empty, 스택의 가장 마지막 데이터를 확인하는 Top 이 있다. 기능만 보더라도 스택은 데이터의 입력, 출력이 자유롭지 않다는 것을 알 수 있다. 단지, 가장 마지막에 저장된 데이터만을 사용할 수 있다. 그럼에도 스택은 범용적으로 쓰이는 자료구조이다. 예를 들어, 정적으로 사용되는 자동 메모리도 스택으로 구현되어 있으며, 네트워크 프로토콜들도 대부분 스택으로 구현되어 있다.

Posted by 개발자테오
Programming/Algorithm2013. 5. 17. 17:06

 칵테일 정렬을 한마디로 표현하면, 거품 정렬의 최종 개선판이라고 할 수 있다.

 앞서 개선한 거품 정렬은 한번 비교가 필요없는 데이터 구간이 결정되면 단계마다 그만큼의 연산이 줄어들기 때문에 효율이 좋아지지만, 한번 비교 연산이 줄어들고 나면, 추가적인 효율이 크게 발생하기 어렵다.

 

 추가적으로 큰 효율 증가를 기대하기 위해서 거품 정렬을 이전에 한 것과 반대 방향으로 이용하는 방식을 번갈아 사용하도록 개선한다.

 위의 그림과 같이 데이터 집합을 한번은 순서대로 거품 정렬하고, 한번은 역순으로 거품 정렬하는 방식으로 정렬을 하며, 앞서 개선한 방법을 이용하여 더 이상 정렬이 필요없는 구간을 다시 비교하지 않도록 한다.

 

 다음과 같이 구현하였다.

 기존에 For 구문이 한 단계씩 진행되는 것이었다면, While 구문으로 두 단계씩 묶어서 정렬한다. 거품 정렬과 동일한 방식으로 인접한 두 개의 데이터를 비교하는 것을 반복하여 정렬한다. 정렬할 때에 사용하는 Swap 함수, 이 정렬 사용하는 예제는 이전 글(링크 : [C] 거품 정렬 (Bubble Sort) 정의 / 코드 / 개선)에서 확인할 수 있다.

Posted by 개발자테오
Programming/Algorithm2013. 5. 17. 15:13

 거품 정렬 (Bubble sort)인접한 두 개의 데이터를 비교하는 것을 반복하여 정렬하는 방법이다. 데이터가 많아질 수록 많은 시간이 걸리지만, 코드가 단순하여 구현하기 쉽다. 거품이라는 이름은 데이터가 거품이 수면으로 올라가듯 정렬되는 모습에서 지어진 이름이다.

 위의 그림과 같은 단계를 통해서 정렬이 진행된다. 처음부터 시작해서 인접한 데이터를 비교해서 큰 값이 뒤에 위치하도록 한다. 데이터 전체를 한번 반복하게 되면, 가장 큰 값이 수의 집합에서 마지막에 위치하게 된다. 그럼 마지막에 위치한 값을 빼고 다시 처음부터 같은 방식으로 정렬한다. 이 방식은 항상 같은 비교 횟수((n-1) + (n-2) + (n-3) ... + 2 + 1 = n * (n-1)/2 = n(n-1)/2)를 가지게 된다.

 

 거품 정렬을 다음과 같이 구현하였다.

 비교는 항상 다음 위치의 값과 비교되므로, 데이터 집합의 처음부터 마지막에서 한개 전의 데이터까지 반복한다. Temp_Complete 는 Step 에 따라 줄어드는 데이터 집합의 크기를 저장하며, iTemp_Compare 가 증가하며, 값을 비교하며 정렬한다.

 

 아래의 함수는 비교 이후, 두개의 값이 바뀌어야 할 때에 값을 바꾸어 주는 Swap 함수이다.

 

 거품 정렬을 사용하는 예제이다.

 앞으로의 정렬 예제에서는 위의 정수형 배열의 값을 출력하는 출력함수를 이용하여, 정상적으로 정렬이 되었는지 확인한다.

 "4, 2, 1, 5, 3, 8, 5, 3, 1, 4" 의 데이터 집합을 만들고, 거품 정렬을 이용하여 정렬한다.

 위와 같이 올바르게 정렬된 것을 확인할 수 있다.

 

 거품 정렬은 추가적인 메모리 사용이 없는 대신, 효율이 좋지 않다. 이를 개선하였다.

 위의 그림을 보면, Step 1 에서 2 번의 비교 후, 데이터 위치 교체를 하고나면, 더 이상의 교체는 일어나지 않는다. 데이터 위치 교체 이후의 값들은 이미 데이터 집합에서 최대값이 순서대로 정렬되어 있음을 의미한다. 3 번째에서부터 7 번째까지의 비교 연산은 다음 Step 부터는 필요가 없다. 이를 개선했을 경우에는 데이터 집합의 후반부에 정렬되어 있는 값들은 다시 비교하지 않아 효율을 올릴 수가 있다.

 

 다음과 같이 거품 정렬을 개선하여 구현하였다.

 iTemp_LastSwap 변수를 추가하여, 데이터 교체가 일어났을 때의 위치를 저장하였다가 다음 단계로 넘어갈 때에, 사이즈를 증가시켜서 다시 비교하여 정렬할 필요가 없는 구간을 다시 비교하지 않도록 개선하였다.

Posted by 개발자테오
Programming/Algorithm2013. 5. 17. 09:52

 전산학과 수학에서 정렬 알고리즘이란 원소들을 번호순이나 사전 순서와 같이 일정한 순서대로 열거하는 알고리즘이다. 효율적인 정렬은 탐색이나 병합 알고리즘처럼 (정렬된 리스트에서 바르게 동작하는) 다른 알고리즘을 최적화하는 데 중요하다. 또 정렬 알고리즘은 데이터의 정규화나 의미있는 결과물을 생성하는 데 흔히 유용하게 쓰인다. 이 알고리즘의 결과는 반드시 다음 두 조건을 만족해야 한다.

 1. 출력은 비 내림차순(각각의 원소가 완전 순서에 의해 이전의 원소보다 작지 않은 순서)이다.
 2. 출력은 입력을 재배열하여 만든 순열이다.

 컴퓨터의 초창기에, 정렬 알고리즘은 연구하기에 대단히 매력적인 주제였다. 간단하고 익숙하지만, 그것을 효율적으로 풀어내기는 복잡하기 때문일지도 모른다. 예를 들어, 거품 정렬은 1956년에 분석되었다. 수없이 많은 논의를 거쳐왔지만, 쓸만한 새로운 정렬 알고리즘들은 현재도 계속 발명되고 있다. 정렬 알고리즘은 다양한 핵심 알고리즘 개념 (점근 표기법, 분할 정복 알고리즘, 자료 구조, 최악의 경우, 평균적인 경우, 최선의 경우 등) 을 소개하는 데 적당하기 때문에, 컴퓨터 과학 강의에서 입문 과정으로 유행하고 있다.

(참고 : http://ko.wikipedia.org/wiki/%EC%A0%95%EB%A0%AC)

 

 위키백과의 설명에도 나와 있듯이 정렬 알고리즘은 핵심 알고리즘 개념을 이해하기 좋다. 이에, 정렬 알고리즘을 하나씩 정리하고, 그 이해를 바탕으로 더욱 복잡하고 효율이 좋은 알고리즘을 만들어나가려 한다. 정렬의 이해와 구현을 넘어서서, 각각의 원리와 그 효율을 비교, 분석하려 한다.

Posted by 개발자테오
Programming/Algorithm2013. 5. 17. 09:22

 알고리즘이란 어떠한 문제를 해결하기 위한 여러 동작들의 유한한 모임이다.

 정의 : 입력(외부에서 제공되는 자료가 0개 이상 존재한다.), 출력(적어도 1개 이상의 결과를 내어야 한다.), 명확성(수행 과정은 명확하고 모호하지 않은 명령어로 구성될 수 있다.), 유한성(종결성)(유한 번의 명령어를 수행 후(유한 시간 내)에 종료한다.), 효율성(모든 과정은 명백하게 실행 가능(검증 가능)한 것이어야 한다)

 분석 기준 : 정확성, 작업량, 기억 장소 사용량, 최적성, 복잡도

(참고 : http://ko.wikipedia.org/wiki/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98)

 

 알고리즘은 문제를 해결하기 위한 방법이라고 볼 수 있다. "매일 아빠와 엄마에게 용돈을 받으면, 하루에 내가 받는 용돈은 얼마인가?" 와 같이 간단한 문제에 대한 해답을 찾는 방법부터 "임의의 수의 집합에 특정 수가 존재하는가?" 와 같은 문제에 대한 해답을 찾는 방법, 모두 알고리즘이라고 할 수 있다. 조금의 자원을 사용하여 정확한 답을 찾는 것이 알고리즘의 목표라고 볼 수 있다. 여기에서는 많이 알려진 알고리즘인 정렬, 탐색에서부터 온라인 저지(Online judge) 사이트들에 있는 문제들을 해결한 방법을 하나씩 정리해보려고 한다.

 

 알고리즘는 내가 블로그를 열게된 이유와도 같다. 알고리즘은 회사 업무에 염증을 느끼던 나에게 다시 한번 프로그래밍의 재미를 일깨워주고, 기초 부분을 하나하나 다시 보면서 몰랐던 내용을 이해하게 해주었다. 여유시간에 자료구조, 알고리즘 관련 책들을 보면서 for, while 등의 반복문을 한번이라도 덜 수행하게 하는 방법이 없는지 고민하면서, 한가지에 몰두하며 시간을 보낼 수 있었다. 이런 재미를 나만이 아닌 다른 누군가도 느끼길 바랬고, 또 내가 알게된 내용을 정리하고자 블로그를 열게 되었다.

Posted by 개발자테오
Programming/Data Structure2013. 5. 14. 09:13

 연결 리스트는 배열과 같이 크기가 정해져 있는 자료구조와 달리 메모리를 동적으로 사용할 수 있다는 장점이 있다. 반면, 다음 노드를 가리키는 포인터를 저장할 메모리를 사용해야 하기 때문에, 정적인 메모리 사용에 비해 메모리가 추가적으로 필요하다. 이와 같이 장점, 단점을 중심으로 연결 리스트를 비교 분석하려 한다.

 

 1. 배열 vs 연결 리스트

 아래와 같이 배열은 처음에 할당받은 메모리만큼 사용 할 수 있다. 실제 메모리 주소는 예로 든 그림과 다르지만 이해를 돕기 위해서 그려보았다. 아래와 같이 0x1000 부터 0x1006 까지 할당을 받게 되면, 7개의 영역을 사용할 수 있게 되며, 7개의 영역을 모두 사용하게 되면, 더 이상 메모리를 사용할 수 없게 된다.

 하지만 아래와 같이 메모리를 필요할 때 마다 할당을 받으면, 컴퓨터의 전체 메모리가 허락하는 한 계속해서 자료를 저장할 수 있게 된다. 사용하는 방법은, 0x1000 에 자료 "1" 을 저장하고, 0x1001 에 다음 자료가 저장되는 메모리 주소인 0x1002 를 저장한다. 이와 같은 방식으로 반복해서 메모리를 할당받아 자료를 저장한다.

 위와 같이 연결 리스트는 동적으로 메모리를 사용할 수 있다는 장점이 있지만, 자료를 저장하는 공간 이외에 다음 자료가 저장되는 메모리 주소를 저장함으로써 메모리를 추가적으로 사용한다는 단점이 있다.

 

 2. 단순 연결 리스트 vs 이중 연결 리스트

 단순 연결 리스트와 이중 연결 리스트는 메모리 주소를 하나 가지고 있느냐, 두개 가지고 있느냐의 차이가 있다. 이로 인해서 나타나는 차이는 노드를 리스트에서 삭제할 때 나타난다.

 위와 같이 단순 연결 리스트에서는 pstSLL_Temp_1 이 가리키는 노드를 삭제하기 위해서는, 리스트의 첫번째 노드에서 부터 탐색하여 pstSLL_Temp_1 의 앞 노드, 그림에서의 pstDLL_Temp_2 를 찾아야한다. 이는 그림에서의 하늘색 포인터를 다시 연결해주어야 하기 때문이다. 이 탐색은 최대 O(n-1) 의 시간을 소비하게 된다.(리스트의 마지막 노드를 삭제할 경우)

 

 반면, 아래 그림과 같이 이중 연결 리스트는 pstSLL_Temp_1 이 가리키는 노드를 삭제하기 위해 찾아야 하는 pstSLL_Temp_2 를 단 한번만에 찾을 수 있다. 이는 각각의 노드가 리스트에서 자신의 앞 노드를 가리키는 포인터를 가지고 있기 때문이다.

 위와 같이 이중 연결 리스트는 단순 연결 리스트에 비해 리스트에서 노드를 삭제할 때 효율적이라는 장점이 있지만, 하나의 포인터 메모리 공간이 더 사용하기 때문에, 메모리를 추가적으로 사용한다는 단점이 있다.

 

 3. 이중 연결 리스트 vs 원형 연결 리스트

 이중 연결 리스트와 원형 연결 리스트는 메모리의 사용에서 약간의 차이가 있을 뿐, 기본적인 구조는 차이가 없다. 하지만, 사용의 차이로 인해 리스트에 노드를 추가할 때에 효율이 차이가 나게 된다.

 위와 같이 이중 연결 리스트에서 리스트에 노드를 추가하기 위해서는 리스트의 마지막 노드를 찾아야하고, 이 탐색은 O(n) 의 시간을 소비하게 된다

 

 반면, 원형 연결 리스트는 아래 그림과 같이, 리스트의 첫번째 노드의 앞 노드를 가리키는 포인터가 이미 리스트의 마지막 노드를 가리키고 있으므로, 이를 찾는 연산이 필요없다.  

 위와 같이 원형 연결 리스트는 구현하는 데에 있어서 조금 어려워진다는 단점 이외에는 리스트에 노드를 추가할 때에 효율적이다.

Posted by 개발자테오