'알고리즘'에 해당되는 글 5건

출처 : http://hochulshin.com/permutation-composition-summary/

 

 

순열, 중복 순열, 조합, 중복 조합에 대한 이해를 해보자.

 

1. 개요

순열, 중복 순열, 조합, 중복 조합를 비교해 보자.

1.1 순열

  • 중복없이 n개 중에서 r개를 뽑아 순서를 정해 나열하는 경우를 말한다.
  • 예를 들어, 다섯 개의 문자 a, b, c, d, e를 일렬로 배열해야 하는 것을 생각해보자.
a, b, c, d, e
a, b, c, e, d
a, b, e, c, d
...

1.2 중복 순열:

  • n개 중에서 r개를 뽑아 순서를 정해 나열하는 경우, 같은 것(종류)을 다시 뽑을 수 있는 것을 말한다.
  • 예를 들어, 숫자 1,2,3을 이용해 만들수 있는 3자리 정수를 모두 구해야 한다고 생각해 보자.
111
112
113
121
122
...

1.3 조합

  • 중복없이 n개 중에서 r개를 순서에 상관없이 뽑는 것을 말한다.
  • 예를 들어 시간이 서로 다른 4 과목 A, B, C, D중 2개 만을 선택해서 수강 신청 하는 경우를 생각해보자.
AB
AC
AD
BC
BD
CD

1.4 중복 조합

  • n개 중에서 r개를 같은 것(종류)를 뽑을 수 있으며 뽑히는 순서에 상관없이 선택하는 것을 말한다.
  • 예를 들어 1인당 2개의 표를 가지고 4개의 연예인(A, B, C, D)에게 선호도 투표를 하는 경우를 생각해보자. 이때 2개의 표를 같은 인물에게 2표를 모두 행사할 수 있다면.
AA
AB
AC
AD
BB
BC
BD
..

2. 순열

2.1 개요

세개의 문자 a,b,c 중 2개를 택해 일렬로 배열하는 방법의 수는 첫 번째 자리의 문자를 택하는 가짓수(3)와 남은 문자 중에서 두번째 자리에 놓을 가짓수(2)를 곱한 것과 같다. 이처럼 서로 다른 n가지에서 r가지를 택하는 순열의 모든 경우의 수는 nPr로 표시할 수 있다.

nPr = n!/(n-r)!, nP0는 1, nPn은 1(모두 선택하거나, 아무것도 선택하지 않을 경우의 수는 1)

2.2 구현

nPr을 이루는 각 경우를 뽑아내기 위한 구현의 예제는 다음과 같다.

  • 문제: 1, 2, 3, 4 숫자 4개가 주어졌을때 이 중 3개의 숫자를 뽑아 한번씩만 사용해서 만들 수 있는 모든 숫자를 출력하라.
#include <stdio.h>

int T[10]; //nPr을 이루는 각각의 경우를 저장
int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

void swap(int *i, int *j){
    int temp = *i;
    *i = *j;
    *j = temp;
}
/*T[]에서 q개 출력*/
void process(int q){
    for(int i = q-1; i>= 0; i--){
            printf("%d ", T[i]);
    }
    printf("\n");
}
 /*data[]에서 앞에서부터 n개의 숫자 중 r개를 선택해서 순열을 출력하는 함수. q는 출력 시 출력 갯수 지정*/
void Perm(int n, int r, int q){
    if(r == 0){
        process(q);
        return;
    }
    for(int i = n-1; i>=0; i--){
        swap(&data[i], &data[n-1]); //n-1을 모든 index와 swap해서 다양한 순서를 만든다.
        T[r-1] = data[n-1];		  //T의 뒤에서부터 결과값 저장	
        Perm(n-1, r-1, q);		  //다음  index로 진행 	
        swap(&data[i], &data[n-1]);
    }
}
int main(void){
    Perm(4, 3, 3); 
    return 0;
}

3. 중복 순열

3.1 개요

서로 다른 n개의 중복을 허용하여 r개를 택하여 나열하는 방법을 n개에서 r개를 택하는 중복 순열이라고 한다. 중복 순열의 모든 경우의 수는 아래와 같다.

중복 순열 nㅠr = n의 r제곱승

3.2 구현

중복 순열의 각 경우를 출력하는 코드를 순열 구현 코드와 비교하면서 확인해보자. 1, 2, 3, 4 숫자 4개가 주어지고 이 중 3개를 뽑는데 같은 숫자가 반복되어도 가능하다.

#include <stdio.h>

int T[10]; //nPr을 이루는 각각의 경우를 저장
int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

void swap(int *i, int *j){
    int temp = *i;
    *i = *j;
    *j = temp;
}

void process(int q){
    for(int i = q-1; i>= 0; i--){
            printf("%d ", T[i]);
    }
    printf("\n");
}
 /*data[]에서 앞에서부터 n개의 숫자 중 r개를 (중복) 선택해서 순열을 출력하는 함수. q는 출력 시 출력 갯수 지정*/
void PI(int n, int r, int q){
    if(r == 0){
        process(q);
        return;
    }
    for(int i = n-1; i>=0; i--){
        swap(&data[i], &data[n-1]);
        T[r-1] = data[n-1];
        PI(n, r-1, q);	//유일하게 다른 부분. 1가지를 선택한 후 선택할 수 있는 종류를 줄이지 않음.(n)
        swap(&data[i], &data[n-1]);
    }
}

int main(void){
    PI(4, 3, 3);
    return 0;
}

4.조합

4.1 개요

서로 다른 n개에서 순서를 생각하지 않고 r개(0<=r<=n)를 택하는 것을 조합이라고 한다. n개 중 r개를 선택하는 모든 조합의 경우의 수는 다음과 같다.

nCr = nPr을 r!로 나눈 것 = n!/(r!*(n-r)!), nC0과 nCn은 1이다.

nCr은 다음과 같은 관계를 가진다.

nCr = n-1Cr-1 + n-1Cr

참고로, 서로 다른 n개의 물건을 p개, q개, r개의 3개의 그룹으로 나누는 방법은 다음과 같다.

nCp * n-pCq * rCr

예를 들어, 서로 다른 종류의 꽃 15송이를 다섯 송이씩 세묶음으로 나누는 방법의 수는 15C10 * 10C5 * 5C5이다.

4.2 구현

조합을 한 각 경우를 출력하는 것을 생각해보자. nCr = n-1Cr-1 + n-1Cr, nC0 = 1 관계를 이용한다. 1, 2, 3, 4 숫자 4개가 주어졌을때 순서 상관없이 3개씩 묶어 그룹을 만들 때 그 멤버를 출력한다.

#include <stdio.h>

int T[10]; //nPr을 이루는 각각의 경우를 저장
int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

void process(int q){
    for(int i = q-1; i>= 0; i--){
            printf("%d ", T[i]);
    }
    printf("\n");
}
/*data[]에서 앞에서부터 n개의 숫자 중 r개를 선택해서 조합을 출력하는 함수. q는 출력 시 출력 갯수 지정*/
void Comb(int n, int r, int q){
    if(r == 0){
        process(q);
        return;
    }else if(n<r){
        return;
    }
    else {  //loop이 아님
        T[r-1] = data[n-1];
        Comb(n-1, r-1, q);  //n-1Cr-1: 현재 아이템을 선택한 경우
        Comb(n-1, r, q);    //n-1Cr: 현재 아이템을 선택하지 않은 경우
    }
}

int main(void){
    Comb(4, 3, 3);
    return 0;
}

5. 중복 조합

5.1 개요

서로 다른 n개에서 순서를 생각하지 않고 중복을 허용하여 r개(0<=r<=n)를 택하는 것을 중복 조합이라고 한다. 예를 들어, 숫자 1, 2에서 중복을 허용하여 3개를 선택하는 조합은 (1,1,1)(1,1,2)(1,2,2)(2,2,2)이다. 중복 조합의 모든 경우의 수는 다음과 같이 표현된다.

nHr = n+r-1Cr

nHr은 다음과 같은 관계를 가진다.

nHr = nHr-1 + n-1Hr

5.2 구현

여기서 중복 조합의 구현은 생략한다. 스스로 구현해 보자!

'알고리즘' 카테고리의 다른 글

DFS,BFS  (0) 2015.07.09
XOR  (0) 2015.06.12
KMP 알고리즘 (문자열 패턴 검색)  (0) 2015.05.29
[알고리즘] 퀵 정렬(Quick Sort)  (0) 2014.12.01
블로그 이미지

kuku_dass

,

DFS,BFS

알고리즘 2015. 7. 9. 10:52

DFS : 깊이 우선탐색.

 백트래킹에 사용되며, 이진트리가 있다고 가정 하면,

 

root - L - R 순으로 방문한다.

 

BFS : 너비 우선 탐색.

그래프, 트리 등의 자료구조에 있어서, 현재 위치에서

연결된 노드들. 즉 방문가능한 노드들을 모두 방문한 후에, 다음 Depth로 진행한다.

 

  1

 2 3

45 67

이 있다고 하면,

DFS : 1 2 4 5 3 6 7

BFS : 1234567

 

'알고리즘' 카테고리의 다른 글

Algorithm - 순열과 조합  (0) 2016.08.29
XOR  (0) 2015.06.12
KMP 알고리즘 (문자열 패턴 검색)  (0) 2015.05.29
[알고리즘] 퀵 정렬(Quick Sort)  (0) 2014.12.01
블로그 이미지

kuku_dass

,

XOR

알고리즘 2015. 6. 12. 14:14

XOR

 

: x,y가 있을 때, 각 비트가 서로 다르면 1,  서로 같으면 0

 

x : 101

y : 011

------- XOR

     110

 

NOT(~), OR(|) , AND(&) 로 XOR 표현하려면?

 

- XOR = (x'y + xy')

하나씩 의미를 확인해보자.

 

x'y : x의 반대와 y의 AND. 즉 x'y의 결과 값은, x의 반대의 비트와 y 비트 중 똑같이 1인 곳의 값이 1이 된다.

x' : 010

y : 011

-------- AND

    010

즉 , 의미는 x,y 두 비트를 비교했을 때, 값이 서로 다면서, x의 비트가 0인곳의 위치가 , 결과적으로 1이 나오게 하는 것이다.

 

반대로 xy'은, x,y 원본의 비트가 서로 다른데, y의 비트값이 0인 위치가 1이 되도록 하는것이 xy'이다.

 

결론

 

XOR = (x'y + xy') 은

(x,y의 비트값이 서로 다른데, x쪽이 0인 위치 + x,y의 비트값이 서로 다른데, y쪽 비트가 0인 위치) 를 더해준 값이된다.

(어차피 둘다 1이여도, XOR는 0 이 되야 하기 떄문에,  x,y의 비트가 서로 다르면서 한쪽이 0인 부분만 찾아서 합침으로써, 결과를 얻는다)

'알고리즘' 카테고리의 다른 글

Algorithm - 순열과 조합  (0) 2016.08.29
DFS,BFS  (0) 2015.07.09
KMP 알고리즘 (문자열 패턴 검색)  (0) 2015.05.29
[알고리즘] 퀵 정렬(Quick Sort)  (0) 2014.12.01
블로그 이미지

kuku_dass

,

KMP


- 접두부, 접미부, 경계 세개의 개념이 중요.


Ex)

BAABABAAC 라는 패턴이 존재 할 때.

C라는 마지막글자에서 틀렸다면,

맞은 글자들을 대상으로, 위와같이 확인한다.


BAA  BA  BAA

접두부 경계 접미부


접두부와 접미부가 같으면서, 경계의 길이가 최소가 될때의

접두.미부의 길이를 최대 너비라 한다.



- 알고리즘의 요지

 : 100글자의 문자열 S, 10글자의 P가 있을 때,


S의 처음부터 하나씩 다 확인해볼필요가 없이,

확인할 필요가없는 부분은 점프를 해서, 성능향상을 얻는다.


1) S와 P를 한글자씩 비교해 나간다.

 1.1) 끝까지 모두 같으면 패턴을 찾은 것이다.

 1.2) 틀린 부분의 위치를 k라 할 때.

      1~k-1까지의 패턴값을 가지고, 경계를 찾는다.


2) 경계의 길이가 최소가 되는

   접두부,접미부를 구한 다음. 

   접두부를 접미부 시작위치로 옮긴 후 패턴을 다시 시작해서 비교 한다.(

(출처 : http://carstart.tistory.com/143)


일치한 글자의 수 n 0 1 2 3 4 5 6 7 8
문자 c B A A B A B A A  
경계의 길이가 최대가 되는 접두.미사의 길이 ( 접두 = 접미사의 길이) L -1 0 0 0 1 2 1 2 3
이동해야할 거리 S
(s = n-L)
1 1 2 3 3 3 5 5  

이동거리 테이블을 만들어야 한다. 위와같이..


우선, 맞는글자가 하나도 없는 경우의 L은 -1이다.: 공식 s=n-L에 맞추기 위해서는 정해져있다 값이.


위와 같이 처음에 테이블만 한번 구성 한 후,

패턴이 몇글자맞았는지에 따라 옆으로 S만큼 이동하면서 패턴이 다 맞는경우를 찾으면된다.

 

 

예제 문제)

 

집합 S는 N가지 문자열의 집합이다. Q개의 문자열이 주어질 때, 각 문자열의 부분 문자열이 하나라도 S에 속하는지 아닌지 판별하는 프로그램을 작성하라.

예제를 예로 들면 S= {"www","woo","jun"}일때, “myungwoo”의 부분 문자열 중 하나인 “woo”가에 속하므로 답은 ‘Y’이고, “hongjun”의 부분 문자열 중 하나인 “jun”이 에 속하므로 답은 ‘Y’이다. 하지만 “dooho”는 어떤 부분 문자열도 S에 속하지 않으므로 답은 ‘N’이다. 이 답을 이어 붙여서 “YYN”을 출력하면 된다.

 

입력

첫째 줄에는 테스트케이스의 개수 T가 주어지며 둘째 줄부터 각 테스트케이스가 주어진다.

각 테스트케이스의 첫째 줄에는 N(1 ≤ N ≤ 1,000)이 주어진다.

다음 N개의 줄에는 S에 속하는 길이 100 이하의 문자열들이 주어진다.

다음 줄에는 Q(1 ≤ Q ≤ 1,000)가 주어진다.

다음 Q개의 줄에는 답을 판별해야 하는 길이 10,000 이하의 문자열들이 주어진다.

입력으로주어지는 문자열은 모두 알파벳 소문자로 이루어져 있다.

 

출력

각 테스트케이스 마다 정답을 의미하는 Q자리의 문자열을 출력한다.

 

입력 예제

1

3

www

woo

jun

3

myungwoo

hongjun

dooho

출력 예제

YYN

 

main.cpp

input_test.txt

 

 

'알고리즘' 카테고리의 다른 글

Algorithm - 순열과 조합  (0) 2016.08.29
DFS,BFS  (0) 2015.07.09
XOR  (0) 2015.06.12
[알고리즘] 퀵 정렬(Quick Sort)  (0) 2014.12.01
블로그 이미지

kuku_dass

,

http://proneer.tistory.com/295

 

http://glocalit.skhu.ac.kr/~mckim1/Lecture/DS/dna/class08/class08_05.html

 

 

<개인 코멘트>

 

pivot을 기준으로, 왼쪽에는 pivot보다 작거나 같은, 오른쪽에는
pivot보다 크거나 같은 애들만 올 수 있다.

Left : 왼쪽 구역에 있으면 안되는 애들을 찾는다.
Right: 오른쪽 구역에 있으면 안되는 애들을 찾는다.

quickSort의 구성은

(재귀호출임으로..)
1. 기저사례를 작성
2. 가장 좌측의 값을 pivot으로 임의로 정해놓고
 그 값을 적절한 위치에 놓은 후, 그 index값을 return 해주는
 partition 함수를 호출한다.

3. 2번에서 구한 index 값을 기준으로 ,
  좌측 부분에 대해 또 Quick sort를 호출, 우측 부분도 quick sort 재귀 호출.


<partition()의 핵심>

left와 right가 교차되었을 때,
right의 오른쪽은 Pivot보다 모두 크거나 같고,
left의 왼쪽은 pivot보다 모두 작거나 같을 것이다.


교차가 되면, right가 현재 위치한 index의 값과(pivot보다 작은 값에 멈춰있을 것이다.)
Pivot의 값을 교환 해주고, right의 index를 return 해주면 된다.

 

Partition 함수를 짤 때, 가장 왼쪽을 pivot으로 일단 둘지,

오른쪽 값을 pivot으로 할지에 따라, 코드가 조금 달라진다. 밑에 예제를 보자.

 

(1) pivot을 가장 left 의 값으로 하고 시작하는 경우.

 

27 6 38 2 62

27이 pivot이다.

left : 38

right : 2 가 되고, 교체가 이루어 진다.

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

27 6 2 38 62

left : 38

right : 2

left와 right의 index가 교차 되었다.

이 떄가 중요 하다.

left는 현재 38을 가리키고 있고, right는 2를 가리키고 있다.

이때 보장되는 것들은

현재 left의 위치를 기준으로 왼쪽은 모두 pivot보다 작거나 같음을 보장.

현재 right의 위치를 기준으로 오른쪽은 모두 pivot보다 크거나 같음을 보장 한다.

 

이 때, 우리가pivot을 가장 왼쪽에 놓고 시작했다. 즉, 왼쪽은 pivot값보다 작은 것들이 위치해야 하는 구역이다.

그렇기 떄문에, 현재 right가 위치하고 있는 곳의 값은 pivot보다 작거나 같은 값이므로,

pivot값 27과 right가 위치한 값이 2를 교체해준다.

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

2 6 27 38 62

즉. pivot을 임의로 가장 왼쪽의 값으로 가정하고 진행되는 Quick  sort의 경우, 위와 같은 이유 때문에

left ,right가 교차되면, right가 위치하고 있는 값과 임의로 결정한 가장 좌측의 pivot값을 교체 해준다.

그리고 right가 위치한 index값을 return 해주면 된다.

 

 

반대로 가장 오른쪽의 값을 무조건 초기 pivot값으로 한다고 가정하는 경우에는 반대이다.

 

left와 right가 교차하면,  그때의 left값이 가리키고 있는 값은

pivot값보다 크거나 같은 값을 가리키고 있음을 보장하기 때문에,

left가 가리키고 있는 값과, pivot값을 교체 해준다.

 

[개인코드]

#include <iostream>

using namespace std;

#define SIZE 10

int list[SIZE] = { 24,52,11,94,28,36,13,80, 56, 71 };

void quickSort(int left, int right);
int partition(int left, int right);
void swap(int index1, int index2);

void main(void)
{
 quickSort(0, SIZE-1);
 cout<<"Sort Result : ";
 for(int i = 0 ; i < SIZE; i ++)
  cout<<list[i]<<" ";
}

void quickSort(int left, int right)
{
 int pivotIndex;

 if(left < right){       //left가 right보다 작지 않다는 것은, 즉 left가 right와 같거나, 더 크다는 것을 의미한다.
            //이는 검사해야 할, 조각이 하나 이하라는 뜻으로, 즉 더 이상 쪼개서 정렬할 필요가 없음을 의미하기에,
            //quickSort 함수가 종료되야 한다.
  pivotIndex = partition(left, right); //partition 함수가 실행 되면, 가장 왼쪽에 임의로 정한 Pivot 값이, 위치해야할 곳으로 자료가 이동되고,
            //이동한 위치 값 pivotIndex를 return 한다.

            //이제 중간 값이 결정되어, 구간이 두 곳으로 나뉘었으므로, 왼쪽, 오른쪽 구역을 각각 정렬한다.
  quickSort(left, pivotIndex-1);
  quickSort(pivotIndex+1, right);
 }
}

int partition(int left, int right)
{
 int pivotIndex = left;
 
 right++;         //do~while문으로 구현되어, right-1이 진행되기 때문에, 미리 +1을 해준다.
 
 while(true){
  do{
   right--;
  }while(list[right] > list[pivotIndex]); //right은 가장 오른쪽 끝에서 부터, 한칸신 왼쪽으로 이동하면서, pivot 값보다 작거나 같은 값을 찾는다.
  
  do{
   left++;
  }while(list[left] < list[pivotIndex]); //left는 왼쪽 pivot값 다음 부터 시작해서, 한칸씩 오른쪽으로 이동하며 Pivot값보다 크거나 같은 값을 찾는다.

  if(left < right)      //left와 right가 정지 한 후, left, right가 교차 하지 않은 경우.
   swap(left,right);     //left와 right가 찾은 값을 교환 해준다.
  else{         //left와 right가 교차한 경우.
   swap(right,pivotIndex);    //right가 가리키고 있는 값은, pivot보다 작거나 같은 값일 것이기 때문에, 교체 해준다.(pivot이 위치할 곳)
   return right;
  }
 }
}

void swap(int index1, int index2)
{
 int temp;
 printf("swap\n");
 temp = list[index1];
 list[index1] = list[index2];
 list[index2] = temp;

}

 

 

 

 

'알고리즘' 카테고리의 다른 글

Algorithm - 순열과 조합  (0) 2016.08.29
DFS,BFS  (0) 2015.07.09
XOR  (0) 2015.06.12
KMP 알고리즘 (문자열 패턴 검색)  (0) 2015.05.29
블로그 이미지

kuku_dass

,