일반적인 링크드 리스트는 Node가 다른 Node를 하나만 가리키고 있지만 이중 연결리스트는 앞 뒤로 하나씩 연결을 가지고 있습니다. 

그래서 탐색을 할때 해당 노드에서 앞에 노드나 뒤에 노드로 쉽게 왔다갔다 할 수 있는 장점이 있습니다. 특정 노드를 탐색하다가 뒤로 마음대로 갈 수 있다는 의미이다. 프로그램을 작성하기전에 이러한 상황이 꼭 필요하고 자주 발생할 것 같다면 이중 연결 리스트를 고려해 보아야 합니다. 

  

아래 Node를 보면 next와 prev 라는 2개의 Node 포인터를 볼 수 있습니다. 하나는 자신의 앞에 있는 노드(next)를 가르키며 다른하나는 자신의 뒤에 있는 노드(prev)를 가르키는 역할을 합니다.

생성자를 통해 기본적으로 포인터에 NULL을 대입 합니다. 그리고 파라미터를 받는 생성자는 파마리터로 받은 ptr을 가지고 해당 노드 다음에 자신을 놓도록 합니다. 포인터를 사용하기 때문에 복잡해 보일 수 있다. 종이에 하나하나 그려가며 코드를 보면 이해가 쉽게 갈 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Node { 
    int data;
    Node* next, * prev; 
    Node() {
        next = prev = NULL;
        data = 0;
    }
    Node(int i, Node* ptr)//ptr 뒤에 추가
    {
        data = i;
        prev = ptr;
        next = ptr->next;
        next->prev = prev->next = this
    }  
};
cs

 


그리고 이중 연결리스트의 또 다른 장점으로는 노드 자신을 삭제하는게 쉽습니다. 그 이유는 자신의 앞에노드와 뒤에 노드의 정보를 가지고 있기 때문에 포인터를 가지고 자신을 제외한 앞뒤 포인터를 서로 연결 시켜줄 수 있어서 입니다.

아래 예시는 자기 자신의 노드를 삭제하는 함수 입니다. 함수의 첫번째 줄은 자신의 뒤에 노드에 다음을 가르키는 포인터에 자기 자신의 다음 노드의 주소값을 넘겨줍니다. 그러면 뒤에 노드가 자신의 다음 노드를 가르키게 하는 것입니다. 두번째 줄에는 자신의 다음노드의 이전을 가르키고 있는 포인터에 자신이 가르키고 있는 뒷노드의 주소값을 넘겨주는 것입니다. 그리고 마지막으로 delete로 자기자신을 삭제해서 할당받은 메모리를 해제합니다. 간결한 코드이지만 처음이해하는데는 많은 어려움이 있을 수 있습니다.


1
2
3
4
5
void selvDelete() {
     prev->next = next;
     next->prev = prev;
     delete this;
}
cs


일반적으로 간단하게 데이터를 추가하고 탐색하는 경우에는 일반 연결 리스트를 구현하면 됩니다.

연결리스트를 활용하여 구현한 Stack 예제 : http://hijuworld.tistory.com/54


하지만 데이터를 탐색할 때 뒤로 갔다 앞으로 갔다하는 경우가 많고 데이터 삭제가 빈번한 경우라면 이중 연결 리스트로 구현하는 것이 좋습니다.


아래 예제는 이중연결리스트를 구현한 예제입니다.


C++을 기본으로 작성하였으며 데이터를 삽입이나 삭제 할때 가장 앞이나 가장 뒤에서 모두 가능하게 하였습니다.

필요한 기능의 함수가 있다면 DLinkedList 구조체에 추가하여 구현하면 됩니다. 

해당 예제를 가지고 데이터를 앞뒤로 삽입하고 삭제하기 때문에 왠만한 기능을 다 할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include<iostream>
 
 
struct Node {
    int data;
    Node* next, * prev;
    Node() {
        next = prev = NULL;
        data = 0;
    }
    Node(int i, Node* ptr)//ptr 뒤에 추가한다.
    {
        data = i;
        prev = ptr;
        next = ptr->next;
        next->prev = prev->next = this
    }    
    void selvDelete() {
        prev->next = next;
        next->prev = prev;
        delete this;
    }
};
 
struct DLinkedList {
    Node *head;
    Node *tail;
    int count;
    DLinkedList() { //생성자
        count = 0;
        head = new Node(); //더미를 선언해서 가지고 있게한다.
        tail = new Node(); //더미를 선언해서 가지고 있게한다.
        head->next = tail; //서로연결한다.
        tail->prev = head;
    }
    ~DLinkedList() {
        while (head->next != tail)
            head->next->selvDelete();
        delete head;
        delete tail;
    }
    void firstInsert(int i) { //head 다음에 추가한다.
        new Node(i, head);
    }
    void endInsert(int i) { //tail 앞에 추가한다.
        new Node(i, tail->prev);
    }
    void firstDelete() { //head 다음 노드 삭제한다.
        if (head->next == tail)    return;
        head->next->selvDelete();
    }
    void endDelete() { //tail 앞에 제거한다.
        if (tail->prev == head) return;
        tail->prev->selvDelete();
    }
    void printAll() {
        Node* tmp = head;
        while (tmp->next != tail) {
            printf("%d\n", tmp->next->data);
            tmp = tmp->next;
        }
    }
};
 
int main() {
    DLinkedList *list = new DLinkedList();
    list->firstInsert(1); //1을 삽입한다.(가장앞)
    list->firstInsert(3); //3을 삽입한다.(1앞에)
    list->firstInsert(5); //5을 삽입한다.(3앞에)
    list->firstDelete(); //5를 삭제한다
    list->endInsert(100); //100을 삽입한다.(가장뒤에)
    list->endInsert(200); //200을 삽입한다.(100뒤에)
    list->endInsert(300); //300을 삽입한다.(200뒤에)
    list->endDelete(); //300을 삭제한다.
    list->printAll();
    delete list;
}
cs

 이중 연결리스트를 여러번 직접 종이에 그려가며 작성해 보아야 손에 익어서 언제든 작성 할 수 있습니다. 처음부터 코드를 완벽하게 작성하고 실행을 해야합니다. 대충 작성하고 디버깅을 해야지 하고 생각한다면 쉽게 에러를 찾을 수 없습니다. 포인터를 가지고 계속 왔다갔다 하기 때문에 디버깅에 많은 어려움이 있습니다. 연결 리스트 뿐만 아니라 데이터를 포인터로 연결하는 트리나 링큐 등도 디버깅하는데 많은 어려움이 있습니다. 처음부터 주의깊게 프로그래밍하는 것이 좋습니다. 


Posted by 꿈만은공돌
,

c언어/ C++ 에서 딜레이 함수 사용하기


프로그램을 진행하다보면 프로그램을 멈추어야할 떄가 존재합니다.


api를 사용하지 않고 아래와 같이 for문을 중첩사용하여 시간을 끄는 방식이 존재합니다.


int a=0;

for(int i=0; i < 10000; i++){ //1중첩 for문

for(int j=0; j < 10000; j++){ //2중첩 for문

a++; //무의미한 연산

}

}


하지만 위와 같은 방식은 원하는 시간만큼 정확하게 딜레이 시키기가 불가능 합니다.


그래서 C와 C++에서는 Sleep 함수를 사용합니다.


windows에 api 를이용하는 방식입니다.


#include <windows.h> 로 헤더파일을 추가해야합니다.


그리고 Sleep( ) 함수를 사용 합니다.


파라미터로 int 데이터를 사용하는데 ms단위입니다.


예를들어 Sleep(1000); 는 1000ms로 1초를 의미합니다.


프로그램을 사용하다보면 유용하게 쓸일이 많이 있습니다.


아래 예제를 참고바랍니다.



- 사용예제1 -


1
2
3
4
5
6
7
8
#include <stdio.h>
#include <windows.h>
 
int main() {    
    printf("시작\n");
    Sleep(1000); //1초정지
    printf("끝\n");
}
cs



- 사용예제2 -


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <windows.h>
void main()
{
     int i=0;
     for(i=0; i<100; i++){
        printf("%d\n",i);
        Sleep(200);   //200ms 일시정지
     }
}
 
cs


- 사용예제3 -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <windows.h>
 
void main() {    
    printf("1초 정지\n");
    Sleep(1000);
    printf("2초 정지\n");
    Sleep(2000);
    printf("3초 정지\n");
    Sleep(3000);
    printf("4초 정지\n");
    Sleep(4000);
    printf("5초 정지\n");
    Sleep(5000);
    printf("끝\n");
}
cs


Posted by 꿈만은공돌
,

Microsoft Visual studio 2017 에서 빈 콘솔 프로젝트 생성하여 Hello world 를 화면에 출력 하는 방법 이다.

c와 C++ 모두 방법은 동일하며 5번에서 파일 선택시 파일 확장자를 .c 로 생성하면 c언어 코드를 작성할 수 있다.



1. 파일 -> 새로만들기 -> 프로젝트 클릭





2. Visual C++ -> Windows 데스크톱 선택 -> Windows 데스크톱 마법사 선택 -> 

   프로젝트 이름 입력 -> 확인

(Windows 콘솔 응용 프로그램 선택시 빈프로젝트로 만들 수 없음)







3. 빈프로젝트 체크, 미리컨파일된 헤더, SDL 검사 체크 해제 -> 확인 클릭





4. 새로은 프로젝트가 생성

   오른쪽 에 소스파일에 오른쪽 버튼 클릭 -> 추가 -> 새항목 선택





5. 왼쪽에서 Visual C++ 선택 -> C++ 파일 선택 -> 파일이름 입력 -> 추가 클릭





6. 코드를 입력 -> Ctrl + F5로 컴파일 및 실행 



Visual studio 2017에서 주의해야 할 점은 scanf 함수를 사용할 때 에러가 발생하여 컴파일이 안될수도 있다.


scanf 함수가 보안상 이슈가 있어서 사용을 못하게 하는 것인데 해당 문제는 아래 링크를 잠고하자.


Visual studio 2017에서 Scanf 사용시 에러가 날때 : http://hijuworld.tistory.com/47






추가로 Visual studio 2017 에서 코드를 자동 정렬 시켜서 보기 좋게 만드는 방법을 소개한 링크이다.


Visual studio 2017 자동 서식 : http://hijuworld.tistory.com/11


아래는 Visual studio 2017 에서 C# 프로젝트를 만드는 방법을 소개한 링크이다.


Visual studio 2017 C# 프로젝트 만들기 : http://hijuworld.tistory.com/10

Posted by 꿈만은공돌
,

1부터 n까지 합을 구하는 방법을 for문으로 하는 방식과 단순 재귀함수를 이용하는 방식을 이전 블로그(http://hijuworld.tistory.com/3)에서 다뤘는데 이번에는 분할 정복 방식을 이용해서 구하는 함수에 대해서 알아 보겠습니다.



1
2
3
4
5
6
7
8
int divSum(int n) {
    if (n == 1)
        return 1;
    if (n % == 1)
        return divSum(n - 1+ n;
    else
        return divSum(n / 2)*+ (n / 2)*(n / 2);
}
cs


1부터 n까지 합을 반으로 쪼개면 다음과 같습니다.

뒷부분에 공통적으로  이 개 존재합니다. 이를 하나로 묶으면


 이 공통적으로 존재하기 때문에 하나로 묶으면 아래와 같이 정리가 됩니다.





앞부분  은 처음 구하려고 한 1 부터 N까지에  절반인 1부터  까지의 합니다.


그래서 아래와 같은 점화식을 구할 수 있습니다.





해당 점화식을 코드로 옮긴 것은 위의 코드 입니다.


해당 함수의 빅오는  와 같습니다.

for문을 이용해서 합을 구하는 방식은 O(n) 입니다.

아래 코드를 가지고 테스트를 해보면 해당 분할 정복 방식이 코드는 복잡해 보이지만 속도 면에서는 월등히 압서는 것을 볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <time.h>
using std::cout;
using std::endl;
 
int divSum(int n)  //분할 정복 방식
{
    if (n == 1)
        return 1;
    if (n % == 1)
        return divSum(n - 1+ n;
    else
        return divSum(n / 2)*+ (n / 2)*(n / 2);
}
int forSum(int n) // for 이용 방식
{
    int sum=0;
    for (int i = 1; i <= n; i++)
        sum += i;
    return sum;
}
int main() {
    clock_t start, end;
    double result;
    //분할 정복 방식
    start = clock(); 
    divSum(200000000);
    divSum(200000000);
    divSum(200000000);
    divSum(200000000);
    divSum(200000000);
    divSum(200000000);
    divSum(200000000);
    end = clock(); 
    result = (double)(end - start);
    printf("분할정복 시간 측정 : %f\n", result);
 
    //for문 이용방식
    start = clock();
    forSum(200000000);
    forSum(200000000);
    forSum(200000000);
    forSum(200000000);
    forSum(200000000);
    forSum(200000000);
    forSum(200000000);
    end = clock();
    result = (double)(end - start);
    printf("for 시간 측정 : %f\n", result);
    return 0;
}
cs


-실행 결과 - 

분할정복 시간 측정 : 0.000000

for 시간 측정 : 2746.000000

계속하려면 아무 키나 누르십시오 . . .


측정 결과를 보면 분할정복 방식은 1ms 미만이라 0으로 나오며 for문을 이용한 방식은 2746ms로 시간차이가 많이 나는 것을 알 수 있습니다.


많은 양의 숫자를 더할때는 분할 정복을 이용하는 것을 추천 합니다.



참고 서적 : 프로그래밍 대회에서 배우는 알고리즘 문제해결전략 (저자 구종만 지음, 인사이트 출판)


Posted by 꿈만은공돌
,



C++ 에서 표준 템플릿 라이브러리인 STL(Standard Template Library)의 가장 기본적인 시퀀스 컨테이너인 vector에 대해서 설명하겠습니다.

기본적으로 배열과 비슷한 부분이 많이 있습니다.

#include<vector> 헤더파일을 포함시켜 주시고 사용하면 됩니다.

선언을 할때 vector<int> v와 같은 방식으로 선언을 하며 크기를 vector<int> v(10) 과 같이 임의 지정할 수도 있습니다.

vector에 저장할 수 있는 크기가 있으며 그것을 초과 할 경우 vector에 인자들을 복사하여 다른 메모리에 할당하면서 크기를 늘립니다.



1. 기본 선언과 인자 추가 , 탐색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
#include<vector>
using std::cout;
using std::endl;
using std::vector;
 
int main() {
    //선언
    vector<int> v;

    //가장뒤에 인자추가
    v.push_back(5);        // [5]
    v.push_back(15);    // [5, 15]
    v.push_back(25);    // [5, 15, 25]
    v.push_back(1);        // [5, 15, 25, 1]
    
    //탐색
    for (unsigned int i = 0; i < v.size(); i++) {
        cout << v[i] << endl;
    }
    
    cout << "----------"<<endl;
                     
    //탐색2 iterator 이용하기
    vector<int>::iterator iter;
    for (iter = v.begin(); iter != v.end(); iter++) {
        cout << *iter << endl;
    }
    return 0;
}







2. size() 와 clear()

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
    vector<int> v;
    v.push_back(-11);        
    v.push_back(-22);
    v.push_back(33);
    
    //사이즈 측정 및 초기화 함수
    cout << v.size() << endl;    // 3 출력
    v.clear();                    // vector 내용 초기화
    cout << v.size() << endl;    // 0 출력
    return 0;
}





3. 중간에 인자 넣기( insert())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main() {
    vector<char> v;
    v.push_back('a');
    v.push_back('b');
    v.push_back('d');
    v.push_back('e');
 
    //b 다음에 c 삽입하기
    vector<char>::iterator iter;
    for (iter = v.begin(); iter != v.end(); ++iter) {
        if (*iter == 'b') {    //b찾기
            v.insert(iter+1'c');    //b다음에 c 삽입하기
            break;
        }
    }
 
    //결과 출력
    for (iter = v.begin(); iter != v.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}





4. 인자 삭제하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main() {
    vector<double> v;
    v.push_back(1.1);
    v.push_back(3.3);
    v.push_back(-1.3);
    v.push_back(11);
    v.push_back(-5);
 
    //음수 제거하기
    vector<double>::iterator iter;
    for (iter = v.begin(); iter != v.end(); ) {
        if (*iter < 0) {            // 음수 찾기
            iter = v.erase(iter);    // 인자 삭제, 삭제 후 다음 인자 반환
        } else {
            ++iter;
        }
    }
 
    //결과 출력
    for (iter = v.begin(); iter != v.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}





Posted by 꿈만은공돌
,





프로그래밍을 하다보면 딜레이가 발생하거나해서 어느 부분이 느린지 알아내야 하는 경우가 생길 수 있습니다.

또는 알고리즘을 공부하다보면 내가 구현한 알고리즘의 속도를 알고싶을 때가 있습니다.

그럴때 특정 구간에 코드 실행시간을 알아야내야 하는 경우에 필요한 포스팅 입니다.

C언어, C++에서 프로그램 실행시간을 측정하는 방법은 두 가지가 있습니다.




1. time 함수를 이용한 방법

우선 time 함수를 이용한 방법 입니다.

time(NULL) 함수를 이용하여 받아옵니다.

단점은 ms단위가 아닌 초(s) 단위로 측정이 됩니다.

#include <time.h> 헤더파일을 포함시키고 아래와 같이 사용하시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <time.h>
 
int main() {
    time_t start, end;
    double result;
    int i, j;
    int sum = 0;
 
    start = time(NULL); // 시간 측정 시작
 
    for (i = 0; i < 100000; i++) {
        for (j = 0; j < 10000; j++) {
            sum += i * j;
        }
    }
 
    end = time(NULL); // 시간 측정 끝
    result = (double)(end - start);
    printf("%f", result); //결과 출력
    return 0;
}




2. clock 함수를 이용한 방법

clock 함수를 이용하여 앞에 방법의 단점을 해결하여 ms 단위로 시간을 측정 할 수 있습니다.
time 함수와 마찬가지로 include<time.h> 헤더 파일을 포함 시켜야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <time.h>
 
int main() {    
    clock_t start, end;
    double result;
    int i,j;
    int sum = 0;
 
    start = clock(); //시간 측정 시작
    
    for (i = 0; i < 100000; i++) {
        for (j = 0; j < 10000; j++) {
            sum += i * j;
        }
    }
 
    end = clock(); //시간 측정 끝
    result = (double)(end - start);
    printf("%f", result);
    return 0;
}



clock 함수는 아래와 같이 1초 1000 clock으로 정의되어 있습니다.
따라서 1clock은 1ms 입니다.
Visual studio 에서 clock 함수에서 F12를 누르면 아래와 같은 정의를 볼 수 있습니다.





Posted by 꿈만은공돌
,