알고리즘 질문입니다.

zflute의 이미지

다음과 같은 집합이 있습니다.

Quote:

A = 3, 5, 4, 1
B = 4, 2, 5, 1, 6, 9, 10

B의 원소 중 A에 포함되는 것을 제외한 결과를 A와 합치고자 합니다.
결론적으로,
Quote:

C = 3, 5, 4, 1, 2, 6, 9, 10

으로 만들려고 합니다. 단, 여기서 각 숫자 값은 특정한 값으로 순서가 변경되면 안 됩니다.

가장 쉬운 방법은 A의 각 원소에 대해서 B의 원소를 하나씩 검사해서 중복되는 것일 경우 제거시키고 남은 B의 값을 A에 붙여주면 되는데... 이렇게 할 경우 O(n^2)이 될 것 같은데요.

A의 갯수가 1,000개이고 B의 갯수가 10,000개 이면 최악의 경우 10^7번 루프를 돌아야 하는군요. :shock:

좀 효율적인 알고리즘 없을까요?

espereto의 이미지

아래같은 건...

int A[4] = { 3, 5, 4, 1 };
int B[7] = {4, 2, 5, 1, 6, 9, 10};
int C[11], Cidx = 0;
int i=0;
char flag[11];

memset(flag, 0, 11);

for(i=0; i<4; i++)
{
	if( flag[ A[i] ] == 0 )
	{
		flag[ A[i] ] == 1;
		C[Cidx] = A[i];
		Cidx  ++;
	}
}

for(i=0; i<7; i++)
{
	if( flag[ B[i] ] == 0 )
	{
		flag[ B[i] ] == 1;
		C[Cidx] = B[i];
		Cidx  ++;
	}
}

대강 이런 방법이 생각나네요. (위 코드는 돌려보지 않은, 즉 검증안 된 코드입니다.)

딱, 원소수만큼만 돌지만, 100,000 개의 원소들이 있다면 100,000 바이트의 메모리를 필요로 하고, 원소의 타입에 따른 크기(위 코드에서는 4바이트) * 100,000 바이트의 메모리가 결과 취합용으로 하죠.

고수 분들의 알고리즘이 보고 싶네요. :)

PS) 설마.. 저 위의 간단한 코드에 버그가 있진 않겠죠? :shock:

zflute의 이미지

if( flag[ A[i] ] == 0 ) 

이렇게 사용하기 위해서는 A, B의 원소 중 가장 큰 값을 알아야 flag 변수 크기를 잡을 수 있지 않을까요? 메모리 낭비는 심하지만 좋은 아이디어 감사합니다. 적당한 해쉬 함수를 만들어서 쓰면 메모리 문제를 해결할 수 있을 것 같습니다.

winner의 이미지

Knuth 에게 Literate Programming 을 부탁하는 E-mail 이라도 보낼까요?
-_-

A, 와 B 의 각 set 에 자신의 set 내의 순서와 원래 어느 set 에 존재했는지 기록한 다음 정렬하고 합병한 후 다시 원래대로 정렬하는 것을 생각해볼 수 있겠군요.

A 의 크기가 M 이고 B 의 크기가 N 이면

1. 자신의 set 내의 순서와 어느 set 인지 기록한다.
time complexity : M + N

2. 각 set 을 정렬한다.
time complexity : M log M + N log N

3. 두 set 을 합병한다.
time complexity : M + N

4. 두 set 을 재정렬한다.
time complexity : (M+N) log (M+N)
이 경우 비교우선순위는 우선 어느 set 에 속해 있는지 본 후
같은 set 에 속해 있다면 원래의 set 의 자기 순서를 비교하면 되겠죠.

결국 최대 연산비용은 4번이 되는군요.
그다지 좋아보이지 않군요. -_-

위의 경우는 정렬 algorithm 에서 Bucket Sort 라고 하나요?
http://en.wikipedia.org/wiki/Sort_algorithm
WikiPedia 에서.

생각하는 Programming 의 첫 주제에서 bit 연산을 도입해서
1/8 로 필요한 용량을 줄이는 방법을 보여주었죠.

espereto의 이미지

네, 값의 범위가 정해져 있어야 혹은 값의 범위를 알아 내야 가능한 방법입니다. 제가 잠시 착각을 했네요.

espereto의 이미지

Quote:

생각하는 Programming 의 첫 주제에서 bit 연산을 도입해서
1/8 로 필요한 용량을 줄이는 방법을 보여주었죠.

네, 비트 연산으로 하면 1/8로 줄일 수 있죠. 비트 연산으로 코드를 작성할까 하다 귀찮아져서...... :oops:
winner의 이미지

합병할 때 linked list 를 사용한다면 B 에 속한 원소들을 무조건 뒤로 보낼 수 있으므로 굳이 자신이 어느 set 에 속해 있는지 기록할 필요는 없군요.

그리고 재정렬 비용도 자신의 set 내에서만 하면 되므로 최악 M log M + N log N 이면 되겠습니다.
최대비용은 2번이 되는군요.
재정렬인 4번은 원소가 줄어들 수 있으므로 2번보다 커지지는 않네요. 하지만 linked list 정렬은 까다로운데... -_-. 제가 기억하기로 STL 에서는 정렬범위의 절반에 해당하는 추가용량이 없으면 N * (log N) ^ 2 를 허용하는 것으로 기억...

그런데 역시 hash table 좋죠!!! (^_^)

ez8의 이미지

파이썬으로 무식하게 비교하는 것이긴 하지만 올려봅니다

a = [3, 5, 4, 1]
b = [4, 2, 5, 1, 6, 9, 10]
                                                                                
a.extend([x for x in b if a.count(x) == 0])
bugiii의 이미지

A, B 각 집합의 원소들이 중복되지는 않는지요?

bugiii의 이미지

1. A를 소팅해서 D를 만든다.
2. B원소 개수만큼 루프를 돌면서 B[i] 를 D에서 찾는다. (binary_search같은)
3. 찾을 수 없으면 A에 B[i]를 추가한다.

이렇게 하면 D 복사 소팅 + M * logN 정도가 아닐까요?

winner의 이미지

이 이상 줄어들 수 있을까요?

bugiii의 이미지

8) 감사합니다.

p.s. i가 세개입니다. -_-;

ez8의 이미지

ez8 wrote:
파이썬으로 무식하게 비교하는 것이긴 하지만 올려봅니다

a = [3, 5, 4, 1]
b = [4, 2, 5, 1, 6, 9, 10]
                                                                                
a.extend([x for x in b if a.count(x) == 0])

방금 테스트 해봤는데... 절망이군요.

대신에

a.extend([x for x in b if x not in a])

가 더 괜찮은 듯 싶습니다.

cdpark의 이미지

bugiii wrote:
1. A를 소팅해서 D를 만든다.
2. B원소 개수만큼 루프를 돌면서 B[i] 를 D에서 찾는다. (binary_search같은)
3. 찾을 수 없으면 A에 B[i]를 추가한다.

이렇게 하면 D 복사 소팅 + M * logN 정도가 아닐까요?

복사/정렬 대신에 hash를 쓰세요. 대략 O(M+N) 시간에 끝낼 수 있습니다.

cdpark의 이미지

ez8 wrote:
ez8 wrote:
파이썬으로 무식하게 비교하는 것이긴 하지만 올려봅니다

a = [3, 5, 4, 1]
b = [4, 2, 5, 1, 6, 9, 10]
                                                                                
a.extend([x for x in b if a.count(x) == 0])

방금 테스트 해봤는데... 절망이군요.

대신에

a.extend([x for x in b if x not in a])

가 더 괜찮은 듯 싶습니다.

우선 a를 sets로 바꾸는 게 어떨까요? set 이 membership test에 더 유리할테니깐요. sets는 python 2.3부터 지원됩니다.

chaeso의 이미지

제가 문제를 잘못 이해한건가요? 무지 쉬워 보이는데..
A 는 그냥 더하고.. A 에서 원소가 발생 했는지 안했는지만 체크해서..
B 를 더할때 A 에서 발생 하지 않은것만 추가하면 될거 같은데..

A = [3,5,4,1]
B = [4,2,5,1,6,9,10]
C = A # 즉 C 랑 A 랑 같음..

Table = [0 for i in range(0,100)]
for i in A:
Table[i] = Table[i] + 1
for i in B:
if Table[i] == 0:
C.append(i)
print C

bugiii의 이미지

A의 원소를 얼만큼 적게 비교하냐가 문제니까요...

zflute의 이미지

다들 열심히 고민해 주셔서 감사드립니다.

집에 오면서 전철 안에서 생각해 봤는데, chaeso님과 같은 생각입니다.

주어진 값들이 정수인 만큼 적당히 큰 배열을 잡아서 A의 값을 배열의 각 element 인자로 생각하고 변수에는 플래그를 걸어둡니다. 그런 다음에 B의 값에 대해서 배열의 값이 플래그가 셋팅되어 있는지를 보고 원래 A의 뒤에 붙여줄 것인지 결정하면 되는군요.

지금 다루려는 값이 실제로 몇 억 정도까지도 갈 수 있는 정수 값이기 때문에 비트 연산을 통해서 잘 쓰면 메모리도 그렇게 많이 필요하지 않을 것 같구요.

이렇게 하면, cdpark 님 말씀대로 O(N+M) 이 되는군요.

winner의 이미지

zflute wrote:
지금 다루려는 값이 실제로 몇 억 정도까지도 갈 수 있는 정수 값이기 때문에 비트 연산을 통해서 잘 쓰면 메모리도 그렇게 많이 필요하지 않을 것 같구요.

Bucket Sort 를 활용해서 1억까지의 수를 다룬다면 간다면 bit 연산을 해도 12.5MB 가 필요할텐데... -_-

위의 chaeso 님의 source 대로라면...

8억이면 100MB... -_-

어찌됐든 hash 인가요?

그런데 bugiii 는 Number 3 처럼 Bug Three 라고 읽어야 하나요... -_-
저는 'bug 가 많은' 이라는 형용사처럼 읽어야 하는 줄 알았습니다.
buggy 가 변형됐는줄 알았죠...

bugiii의 이미지

최대값이 몇억이라면 비트저장에만도 엄청난 메모리가 소비될텐데요... 그리고 메모리 초기화 하는데도....

또하나 문제가 B원소값이 이리저리 점프하는 성질을 가졌다면 발생 비트 테이블의 페이지 문제로 속도가 확 떨어질 것입니다.

cdpark님께서 말씀하신 해쉬가 값의 존재 여부를 가장 빠르게 판단할 수 있는 것으로 보입니다. 정수라면 해쉬 함수를 크게 걱정하지 않아도 될테고... 그래도 정렬된 vector 의 binary_search도 왠만큼하니까...

p.s. cdpark님은 C++에서 어떤 해쉬 컨테이너를 사용하고 계십니까?

p.s. 벌레3호입니다.

moonzoo의 이미지

실제 몇억이상의 숫자이고.. 그 간격이 촘촘하지 않다면..

해쉬를 이용한 비트플래그 세팅도 괜찮을 듯 합니다.

chaeso의 이미지

숫자의 범위가 대충 1~10 정도 될거라고 예상했거든요..
답은 해쉬밖에 없군요
A 의 집합이 10000 개가 나온다면 테이블을 5001개 정도 잡고 해슁(+Quadratic probing) 써서 처리하시면 될듯..

bugslife의 이미지

생각하는 프로그래밍의 첫 주제가 생각나는군요.

정렬을 한다는 가정이 들어있었으면 거의 똑같은 알고리즘 일텐데.. 정렬이 되면 안된다는 조건이 들어가서 약간의 시간이 더 소요될것 같군요.

A = 3, 5, 4, 1
=> 10111
B = 4, 2, 5, 1, 6, 9, 10
=> 1101110011

교집합은 1111110011정도..
A의 개수와 B의 개수의 합정도의 시간.

만약, A안에 같은 수가 중복된다는 가정이 있다면 이 알고리즘은 사용불가겠지요.(추가 비용이 더 들어가거나 - 물론 B도 마찬가지겠군요.)

A와 대응되는 bit를 clear시켜주면서 A를 결과로 뿌려주고 A를 전부 뿌려준 후 남아있는 bit map과 대응되는 B를 뿌려주면.. 시간이 얼마나 나올까요.. 음..

어느순간부터인가 하루살이의 하루를 알고싶다.

GivenJazz의 이미지

zflute님이 생각한 것과 거의 같구요.
다른 점이라면 원소가 있는지 없는지 찾을 때 다 찾는게 binary로 찾는다는거죠.
그것만으로도 O(NlogM)정도의 복잡도를 가질 수 있습니다.
다음은 C++ 예제입니다.
이렇게 하면 set c에 저장이 되겠네요.

#include <algorithm>
#include <iostream>
#include <set>

using namespace std;

main()
{
    int a[] = {3, 5, 4, 1}; 
    int b[] = {4, 2, 5, 1, 6, 9, 10};

    set<int> c(a, a+sizeof(a)/sizeof(int));
    c.insert(b, b+sizeof(b)/sizeof(int));
}

또 순서가 필요하다면 int result[]배열을 만들고 c에 있는지 없는지 확인하면 해서 result에 더하면 될 것 같습니다.

catzbi의 이미지

A = 3, 5, 4, 1
B = 4, 2, 5, 1, 6, 9, 10
[A&A=null, B&B=null]

set [dont' sort];(A,4) -> inA
set [dont' sort]:(B,7) -> inB 

 multiset [ sort]: ((A,4),(B,7)) -> inAB 
 find:equal_range(inAB) >= 2:distance(itr_beg,itr_end)  -(true)> store:val -> set [?]:C(A&B,less_equal(4):check)-> inC 

inB - = inC ; 
append:sequential[dont' sort] (inA,inB); 

ㅡ,.ㅡ;;의 이미지

zflute wrote:
다음과 같은 집합이 있습니다.
Quote:

A = 3, 5, 4, 1
B = 4, 2, 5, 1, 6, 9, 10

B의 원소 중 A에 포함되는 것을 제외한 결과를 A와 합치고자 합니다.
결론적으로,
Quote:

C = 3, 5, 4, 1, 2, 6, 9, 10

으로 만들려고 합니다. 단, 여기서 각 숫자 값은 특정한 값으로 순서가 변경되면 안 됩니다.

가장 쉬운 방법은 A의 각 원소에 대해서 B의 원소를 하나씩 검사해서 중복되는 것일 경우 제거시키고 남은 B의 값을 A에 붙여주면 되는데... 이렇게 할 경우 O(n^2)이 될 것 같은데요.

A의 갯수가 1,000개이고 B의 갯수가 10,000개 이면 최악의 경우 10^7번 루프를 돌아야 하는군요. :shock:

좀 효율적인 알고리즘 없을까요?

각원소에 비트를 할당할이유가 없어보이는데 그리고 그비트도 실제 만만치 않을듯.

일단 A를출력한후 A와B를 비교하여 원소가 많은쪽을 정렬하시고
만일 A가 많다면 간단히 B를 A에서찾아 없으면 출력하시면되고
만일 B가 많다면 A를 읽어 B값을 찾아 제거한후 출력하시면 됩니다.


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

nohmad의 이미지

ruby:

C = A << B-A

이렇게 해야 할 것 같다고 생각되면 실제로 그렇게 하면 되는 ...

marzok의 이미지


A = 3, 5, 4, 1 
B = 4, 2, 5, 1, 6, 9, 10 
 

C = 3, 5, 4, 1, 2, 6, 9, 10 

대충 자바로 하면..

Set set = new HashSet();
for(int i=0;i<a.length;i++)
    set.add(a[i]);

for(int i=0;i<b.length;i++)
    if( !set.contains(b[i]) )
        set.add(b[i]);

다시 생각해 보니 contains가 필요하군요. contains로 물어 보지 않으면 순서가 바뀌니까.. 역시 알고리즘은 차분히 생각을 해봐야 하는가 봅니다.

최종호의 이미지

ㅡ,.ㅡ;; wrote:

일단 A를출력한후 A와B를 비교하여 원소가 많은쪽을 정렬하시고
만일 A가 많다면 간단히 B를 A에서찾아 없으면 출력하시면되고
만일 B가 많다면 A를 읽어 B값을 찾아 제거한후 출력하시면 됩니다.

대략 말씀하신 과정이 GivenJazz 님이 말씀하신 O(NlogM) 일듯 싶군요.

두 집합의 원소의 갯수를 각각 M, N 이라 하고, N <= M 이라 하고요,
정렬 알고리즘은 O(n log n) 을 쓰고 검색 알고리즘을 O(log n) 알고리즘을 쓴다면,

N log N (정렬) + M * log N (M개의 원소를 정렬된 집합에서 검색)

= (M + N) log N

이 되겠네요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.