반응형

 

Hash Motivation

 

Direct Access Array

Hashing

 

Chaining

chaining 은 해쉬 테이블이 아닌 다른 자료 구조에 아이템을 저장해서 충돌을 해결하는 방법입니다. 해쉬 테이블의 각 인덱스에는 아이템이 아닌 또 다른 자료 구조를 가르키는 포인터가 저장됩니다. 이 자료 구조는 insert, search, 그리고 delete(k) 를 상수 비용으로 지원하는 Set Interface 이고 chain 이라고 합니다. linked list 나 array 를 생각하면 됩니다.

충돌이 발생해도 모든 해쉬 테이블의 chain 의 크기가 상수 이면 insert, serach, 그리고 delete(k) 를 O(1) 으로 수행할 수 있게 되고 이것이 hashing 을 통해 달성하고자 하는 목표인데요. 이를 위해서는 해쉬 함수가 중요합니다. 만약에 아주 안좋은 해쉬 함수를 선택해서 모든 키의 해쉬 값이 동일한 인덱스를 가진다면, 해당 인덱스 chain 의 크기는 O(n) 이 되고 insert, serach, 그리고 delete(k) 의 비용도 O(n) 이 되버립니다. 최악이죠.

 

이상적으로 어떠한 키 유니버스가 주어져도 충돌의 개수를 줄여서 테이블의 가장 큰 chain 의 크기가 상수인 해쉬 함수를 선택해야 합니다.

 

Universal Hashing

좋은 해쉬 함수의 기준을 다시 한번 정리하겠습니다.

 

어떠한 키 유니버스가 주어져도 충돌의 개수를 줄여서 가장 큰 chain 의 크기를 상수로 만드는 해쉬 함수

Dot Product Hash Family

 

참조

https://www.youtube.com/watch?v=Nu8YGneFCWE&list=PLUl4u3cNGP63EdVPNLG3ToM6LaEUuStEY&index=5

https://www.youtube.com/watch?v=z0lJ2k0sl1g&list=PLTV7NgF818VXpV2P-Px2H74hvEBL2o-G2&index=11 

 

 

ref : https://klioop.tistory.com/43

반응형
반응형


Time complexity, Big O notation




O(1) < O(log n) <  sqrt(n) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n!) 







ref : https://en.wikipedia.org/wiki/Time_complexity


반응형
반응형

진즉부터 올릴려고 하던걸 이제서야.... OTL....

컬러스크립트 사용방법이 아직 익숙하지 않은듯.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
char itoc(int a) 
       return a + 0x30
       // 0x30 is usable for number of ascii code  
}
 
char* itoc(int a, char* array_) 
       int temp = a; int cnt = 0;   // 자릿수 세기 
       while (temp > 0)  
       {  
              temp = temp / 10;  
              ++cnt; 
       } 
       temp = a;
 
       for (int i = 0; i != cnt; ++i) 
       {  
              *(array_ + cnt - 1 - i) = itoc(temp % 10);  
              temp = temp / 10
       } 
       return array_;
}
 
cs

 

1. 한자리 숫자 변환하기 (1~5)

우선 int 숫자가 한자리 인것을 변환해준다.

0x30 만큰의 아스키 코드 값의 차이가 있어서 단순히 더해주는 것으로 하였다.

assert 같은걸로 한자리 이상이면 예외처리를 해주는 것은 왜인지 하지 않았다. -_ -a

 

2. 한자리 이상의 숫가 변환하기 (7~23)

함수 오버로딩을 통하여 char 배열형태포인터를 받기로 하였다.

자릿수 세는 것을 하고싶지는 않았지만, 현재 알고리즘상으로는 자릿수 만큼 돌려야 하기에, 일단 사용을 하는걸로..

19번째 줄이 핵심. 마지막 자리숫자부터 _array의 앞부분에 차근차근 집어 넣었다.

그리고 return array_는 사실 하던 안하던 관계는 엄슴.

 

 

음... 뭔가 100점짜리는 아니지만 짧은 시간에 그냥저냥 클리어 한 느낌이랄까.

 

그럼 안녕~

 

 

Feedback : smile.k06@gmail.com

반응형
반응형

BLOG main image





해쉬 함수를 찾으려 구글링하다가 FNV라는 해쉬를 만들었다


저장소가 2의 승수가 아닐경우 이 함수를 만든 사람이 FNV를 추천한다고함 FNV  는 저자가 만든것임;


아래 그래프는 결과값으로 32비트를 구할 경우에 대한 균일 분포를 나타낸 그래프라고 함


분포는 대략 괜찮은 분포인듯.. 문자열이 비슷하고 하위 부분만(완전 하위는 아니였지만) 좀 바꿔서 해보니 Figure1 코드에선


키값은 그닦 잘 뽑지는 못한다(Figure4코드는?)




Figure1 소스를 32bit라 가정하고 c++로 코드를 바꿔 시간을 측정해보니 대략  100000 번 정도 돌리니




for(int i=0;i<100;++i){

FNV("Fieldstone_DM.tga");

}





 경과시간 : 22.018000 (ms)  ,   220180 (us)


 

측정 횟수가 늘 수 록 이 이상에선 여기서대략 해보니 100000 이 횟수에 10을 곱한 횟수가 경과시간에서 수치상 10을 곱한시간과 


대략 비슷하닥 보면 됨



그리고 100번 돌렸을때는 


경과시간 : 0.043000 (ms)  ,   430 (us)


정도의 시간을 보임




Figure4 에 나오는 코드로 테스트해보니


10000000 번 수행에서 다음처럼


name : fnvs , 경과시간 : 2665.978000 (ms)  ,   26659780 (us)

name : fnvs1 , 경과시간 : 2126.676000 (ms)  ,   21266760 (us)


속도가 Figrue1보다도 빠르고 키 값또한 잘 뽑는다 


아래는 사이트..










http://home.comcast.net/~bretm/hash/6.html


Hash Functions (continued)

Fowler/Noll/Vo Hash


Hash Functions (continued)

Fowler/Noll/Vo Hash

This hash function is described here. It is a simple multiplicative hash with the addition of a post-processing step called xor-folding to remove some linearity in the lower bits. The FNV authors recommend using xor-folding if the desired hash size is not a power of 2, but they actually mean to use xor-folding when the hash size is not a power of 2 or is less than 32.

There are different initialization and multiplication constants for use with 32-bit, 64-bit, 128-bit hashes, etc., but we'll only examine the 32-bit version of FNV here. In our uniform distribution test we'll use xor-folding for the lower bits since that is the intended use of this hash, but we'll examine the upper bits as-is. Since our avalanche test is a full 32-bit test, we can't use xor-folding there. This will allow us to identify classes of keys for which using xor-folding is a necessity.

The primary appeals of this hash are its simplicity and its speed, but its speed is dependent on whether or not the CPU architecture supports fast integer multiplication. The Jenkins shift-add-xor hashes are faster on CPU architectures without fast multiplication.

Figure 1 shows the listing for the FNV 32-bit hash.


Figure 1

Fowler/Noll/Vo (FNV) 32-bit hash function.

public class FNV : HashFunction

{

    int shift;

    uint mask;


    // hash without xor-folding

    public FNV()

    {

        shift = 0;

        mask = 0xFFFFFFFF;

    }


    // hash with xor-folding

    public FNV(int bits)

    {

        shift = 32 - bits;

        mask = (1U << shift) - 1U;

    }


    public override uint ComputeHash(byte[] data)

    {

        uint hash = 2166136261;

        foreach (byte b in data)

            hash = (hash * 16777619) ^ b;

        if (shift == 0)

            return hash;

        return (hash ^ (hash >> shift)) & mask;

    }

}


Uniform Distribution Test

The details of this test are described fully on aprevious page. We examine the distribution of numbers derived from lower and upper bits of the hash output in sizes of 1 through 16 bits.

Figure 2 shows the results of this test for the FNV hash. This test indicates that the FNV 32-bit hash with xor-folding produces uniformly distributed values for hash tables that are a power of two, up to at least 214, when the key octets are uniformly distributed, distributed similar to alphabetic text, or sparsely distributed.


Figure 2

χ2 test results for FNV 32-bit hash, with xor-folding for the lower-bit tests.

Uniform keys    Text keys     Sparse keys


Bits  Lower  Upper   Lower  Upper   Lower  Upper

1    0.777  0.888   0.888  0.888   0.480  0.480

2    0.967  0.326   0.407  0.197   0.513  0.720

3    0.109  0.390   0.498  0.103   0.573  0.016

4    0.548  0.416   0.649  0.210   0.143  0.469

5    0.360  0.606   0.931  0.992   0.665  0.201

6    0.328  0.753   0.584  0.416   0.882  0.361

7    0.436  0.560   0.995  0.302   0.981  0.124

8    0.297  0.729   0.856  0.730   0.472  0.113

9    0.222  0.349   0.629  0.951   0.701  0.769

10    0.731  0.208   0.066  0.646   0.875  0.551

11    0.813  0.356   0.678  0.820   0.519  0.556

12    0.076  0.229   0.521  0.068   0.091  0.474

13    0.780  0.565   0.719  0.090   0.117  0.132

14    0.225  0.813   0.269  0.251   0.855  0.568

15    0.583  0.005   0.699  0.370   0.571  0.218

16    0.004  0.000   0.117  0.012   0.024  0.211

Figure 3

Avalanche behavior of the FNV 32-bit hash.

For two-octet keys:

Avalanche behavior for FNV hash with 16-bit keys 

For four-octet keys:

Avalanche behavior for FNV hash with 32-bit keys 

For 256-octet keys:

Avalanche behavior for FNV hash with 256-bit keys 

 


The upper bits are not uniformly distributed if you use more than 14 or 15 bits. Because of this, I don't recommend using this hash with hash tables larger than 214 buckets. The results are acceptible up to 216 bits for text keys, so you may be able to use it for that purpose if you carefully test the performance in your particular application.

Avalanche Test

The details of this test are described fully on aprevious page. We examine the diffusion of input bits into different bit positions in the output.

Figure 3 shows the results of this test for the FNV hash. This test indicates that the FNV hash has poor avalanche behavior, as do all simple multiplicative hashes. This means that there will be some classes of keys for which the hash function does not produce uniform output, even for small bucket counts where the χ2 tests succeeded above.

Of particular concern are the two low-order bits. These are always just a simple linear function of the two low-order bits of the keys octets. For example, in the full 32-bit hash value, the low-order bit will always just be a simple XOR of the LSBs of the key octets and the LSB from the intialization constant. Also of concern is that fact that the upper bits of the key octets do not have any influence on the low-order bits of the hash output (without xor-folding). The MSB of each key octet does not diffuse at all into the entire lower 8 bits of the hash value. This indicates that you must follow the xor-folding recommendations for all classes of keys.

Also, the mixing step of this hash is never applied to the last octet of the key. This shows clearly in the avalanche results. The authors offer an alternative form of the hash where the combining step is done before the mixing step, and I recommend that you adopt this alternative if you use FNV. There is no reason to do otherwise, and I'm surprised that the authors do not recommend this by default.



Figure 4

Modified FNV with good avalanche behavior and uniform distribution with larger hash sizes.

public class ModifiedFNV : HashFunction

{

    public override uint ComputeHash(byte[] data)

    {

        const uint p = 16777619;

        uint hash = 2166136261;

        foreach (byte b in data)

            hash = (hash ^ b) * p;

        hash += hash << 13;

        hash ^= hash >> 7;

        hash += hash << 3;

        hash ^= hash >> 17;

        hash += hash << 5;

        return hash;

    }

}


Conclusion

I don't recommend using 32-bit FNV as a general-purpose hash. It can produce uniform output, but its suitability needs to be tested for each class of keys for which you intend to use it. It is likely to produce non-uniform output if you have more than 214 or 215 hash buckets. If the CPU architecture does not have fast integer multiplication, use a shift-add-xor hash instead.

If you want to use an FNV-style hash function, I recommend using the modified version listed in Figure 4. This version passes all the uniform distribution tests above and it achieves avalanche for every tested combination of input and output bits (green squares everywhere). No xor-folding step is required.

The only difference between this version and the original version is that the mixing steps occursafter the combining step in each round, and it adds a post-processing step to mix the bits even further. These two changes completely correct the avalanche behavior of the function. As a result, this version of FNV passes all of the χ2 tests above, all the way up to 216 buckets. I haven't tested larger sizes but I suspect it would be OK there as well.

First page, Next page

Home | E-mail: BretMulvey at Hotmail

Copyright © 2007 Bret Mulvey. All Rights Reserved. 

반응형
반응형

http://goo.gl/24Dq3


괜찮은 문자열 해쉬함수?

오늘 재미있는 글을 하나 읽었습니다. ^^

컴파일 중에 문자열해쉬 만들기....(를 시도해 보자? -_-)
; http://www.gamedevforever.com/50

위의 이야기 중에 보면, x65599 hash 함수 하나를 소개하고 있는데요. 소스 코드가 아래와 같이 실려져 있습니다.

// 65599를 곱하는 해쉬함수. (Red Dragon 책에서 훔쳐옴 -0-)
unsigned int generateHash(const char *string, size_t len)
{
  unsigned int hash = 0;
  for(size_t i = 0; i < len; ++i)
  {
     hash = 65599 * hash + string[i];
  }
  return hash ^ (hash >> 16);
}

마침, 우리 회사에서도 사용하고 있던 해쉬 함수가 있었는데 위의 함수에 비하면 무거운 연산을 하기 때문에 전체 메일로 소개를 했더랬지요. ^^

그러자, 자체 hash 충돌 코드로 테스트한 결과가 피드백으로 날아왔습니다.

테스트 문자열 경우의 수: 14,776,336

x65599 hash  => 해쉬 충돌=722,750
사내 해쉬함수 => 해쉬 충돌=0

충돌 횟수가 비교될 정도로 많습니다. 여기서 갑자기 오기가 발동한 성태... ^^ 곧바로 x65599 해쉬함수 개량에 나섰습니다.

(*** 이후의 테스트 결과들은 확실한 비교를 위해 문자열 경우의 수를 기존 14,776,336 에서 162,539,696 으로 늘렸습니다.)

자... 이제 차근히 x65599 hash 함수를 보았습니다. 우선... 함수의 마지막 return 문에서 수행한 hash 쉬프트는 그다지 영양가가 없다는 판단이 들었습니다. 실제로 shift 연산을 제거하고 테스트했더니 오히려 충돌 횟수가 줄어드는 결과값이 나왔습니다.

x65599 hash
걸린 시간: 61,374 ms, collisions = 35,374,354 (21%)

shift 제거한 hash
걸린 시간: 50,091 ms, collisions = 20,820,228 (12%)

충돌 수도 줄고, 속도도 빨라졌으니 x65599 hash는 일단 다음과 같이 바뀌는 것이 좋겠습니다.

int hash = 0;
int len = chars.Length;
for (int i = 0; i < len; ++i)
{
    hash = 65599 * hash + (int)chars[i];
}

return hash;

약간 나아지긴 했지만, 여전히 충돌 횟수가 걸립니다. 좀더 개선할 수는 없을까요? 가만 보니까 사용된 소수(Prime number)값 - 65599 가 마음에 안 듭니다. 구하려는 해쉬 값 자체가 전체 4byte 인데 겨우 2 바이트 정도에 해당하는 65,599 값만을 쓴다는 것이 왠지 충돌의 원인이 아닐까 싶어서 CRC32 알고리즘에서 사용되는 값의 하나인 0xEDB88320 수로 대체를 해보았습니다. 다행히, 테스트 결과 ... 충돌 횟수가 어느 정도는 줄었습니다.

0xEDB88320 hash
걸린 시간: 53,705 ms, collisions = 2,633,400 (1.62%)

근데... 과연 0xEDB88320 값이 올바른 선택일까요? 짝수라는 것이 왠지 좀 그래서, 또 다른 CRC32 값 중에서 홀수인 0x741B8CD7 값과 혹시나 싶어서 그 값보다 큰 수 중에서 소수를 찾아 (0x741b8cf1) 다시 한번 테스트를 돌려보았습니다.

또 다른 CRC32 0x741B8CD7 hash
걸린 시간: 55,080 ms, collisions = 131,040 (0.08%)

소수 0x741b8cf1 hash
걸린 시간: 58,075 ms, collisions = 3,926,600  (2.42%)

오호... 결과를 보니, 소수라고 해서 항상 정답은 아닌 것 같습니다. ^^ 그러고 보면, CRC-32 에서 사용되는 값이 괜히 채택된 것은 아닌 것 같습니다.

개인적으로 여기서 만족할 수 없더군요. 좀더 값을 분산시켜 볼 수 있지 않을까 싶어서 bit 연산을 생각했습니다. 위에서 테스트 했던 16진수 값을 대상으로 각각 테스트를 해보았는데요. 결과는 의외로 0xEDB88320 의 승리였습니다.

0xEDB88320 + 1bit Left Shift
걸린 시간: 56,407 ms, collisions = 4,500 (0.0028%)

0x741B8CD7 + 1bit Left Shift
걸린 시간: 54,360 ms, collisions = 240,065 (0.148%)

0x741b8cf1 + 1bit Left Shift
걸린 시간: 54,906 ms, collisions = 381,888 (0.235%)

여기까지 하고... 다시 회사에서 사용하던 CRC32 알고리즘과 충돌 횟수를 비교해 보았습니다.

사내 해쉬함수
걸린 시간: 135,649 ms, collisions = 1,265,312 (0.778%)

오호~~~ 압도적으로 "0xEDB88320 + 1bit Shift" 연산이 승리를 했군요. 그래서 최종적으로 제가 만든 해쉬 코드는 다음과 같습니다.

static int hash(char[] chars)
{
    int hash = 0;
    int len = chars.Length;

    unchecked
    {
        uint poly = 0xEDB88320;
        for (int i = 0; i < len; i++)
        {
            poly = (poly << 1) | (poly >> (32 - 1)); // 1bit Left Shift
            hash = (int)(poly * hash + chars[i]);
        }
    }

    return hash;
}




참고로, 테스트를 위한 문자열 생성에 사용된 함수는 다음과 같습니다.

TestHashLib.TestHash hashTable2 = new TestHashLib.TestHash();
x = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

char[] xx = x.ToCharArray();
int tot = 0, dup = 0;
for (int c1 = 0; c1 < x.Length; c1++)
{
    for (int c2 = 0; c2 < x.Length; c2++)
    {
        for (int c3 = 0; c3 < x.Length; c3++)
        {
            for (int c4 = 0; c4 < x.Length; c4++)
            {
                for (int c5 = 0; c5 < x.Length; c5++)
                {
                    if (c5 > 10)
                    {
                        break;
                    }
                    char[] c = new char[] { xx[c1], xx[c2], xx[c3], xx[c4], xx[c5] };
                    int hash = hashFunction(c);
                    tot++;
                    if (hashTable2.containsKey(hash))
                    {
                        dup++;
                    }
                    else
                    {
                        hashTable2.put(hash);
                    }
                    c = null;
                }
            }
        }
    }
}

Console.WriteLine("Total: " + tot + ", time: " + st.ElapsedMilliseconds + ", collisions = " + dup);

위에서 보면, 마지막 c5 변수의 값을 10 이하로 제한했는데요. 왜냐하면, 메모리가 너무 많이 소비되어서 그런 조치를 취한 것입니다. 10으로만 해도 테스트를 위해 5GB 가까운 메모리를 소비하기 때문에 어쩔 수 없이 제약을 두었습니다.

메모리 소비로 인해, 당연히 x86 환경에서는 테스트 할 수 없고 Hash 값을 보관하기 위한 자료구조도 C# 의 것이 아닌 C++/CLI를 통해 CAtlMap 을 가져다 써야만 했습니다.

.NET 64비트 응용 프로그램에서 왜 (2GB) OutOfMemoryException 예외가 발생할까?
; http://www.sysnet.pe.kr/2/0/946

여러분들도 테스트를 직접 해볼 수 있도록, 위의 것들을 테스트 하는 데 사용한 코드를 첨부했습니다.

마지막으로, 위와 같이 결과가 나왔다고 해서 제가 공개한 해쉬 함수가 제일 좋을 것이다라는 판단을 하시면 안됩니다.

Dictionary.Get(A) 대신 Dictionary.Get(A.GetHashCode()) 를 사용해서는 안되는 이유
; http://www.sysnet.pe.kr/2/0/889

해쉬 함수는, 결국 해당 '업무 도메인'에서 사용되는 문자열 셋이 다르기 때문에 (가능하다면) 그에 따른 적절한 테스트를 해보고 선택하시는 것이 좋습니다.



반응형
반응형



http://sapeyes.blog.me/70112636840


LUT (Lookup Table) 룩업테이블 

2011/07/03 23:11

복사http://sapeyes.blog.me/70112636840


영상처리 기법에서 주로 사용되는 알고리즘인 룩업테이블을 알아본다.


1. 룩업테이블이란?


  룩업테이블은 주어진 연산에 대해 미리 계산된 결과들의 집합(배열)을 가리킨다. 이 집합(배열)은 주어진 연산에 대한 결과를 계산하는 시간보다 더 빠르게 값을 취득해 갈 수 있도록 사용되는 레퍼런스로 사용된다. 


 LUT는 주로 실시간 데이터 취득, 실시간 프로세싱 시스템 (embedded system)에서 사용된다. 이러한 시스템에서는 시간내에 연산 결과 취득에 대한 요구사항이 매우 높기 때문이다. 한가지 고려해야 할 점은 LUT는 배열을 최초 초기화 할때에는 연산량이 매우 많다는 점이다. 실시간 시스템에서는 이러한 초기화에서 딜레이가 일어나는 것 정도는 어느정도 감수해 주기 때문에 LUT가 사용되는 빈도가 높다. 


2. LUT 예제


 LUT에 대해 예를 들어본다. 심플하게, 실시간 데이터 취득을 고려해본다. 8비터 숫자로 데이터가 취득되고 있다고 가정해보자. 그리고 이 데이터들은 양수 (0~255)이다. 실시간 시스템은 데이터의 근(root)를 얻는 연산을 필요로 한다. C에서는 해당 연산에 대한 LUT를 다음과 같이 계산할 수 있다.


double LUT_sqrt[256];   /* 미리 전역변수로 선언해야 다른데서 쓰겠죠? */


/* 초기화 코드 부분을 이정도에 넣어야 겠죠? */

for (i=0; i<256; i++)

LUT_sqrt[i] = sqrt(i);   // math.h의 sqrt함수를 사용합시다.


/* 이부분은 실시간 시스템에서 데이터 변환 하는 곳이라고 봅시다. */


result = LUT_sqrt[sample];  /* sqrt(sample) 대신에 사용함으로써 시간 절약됩니다 */


3. 요약


 - 룩업테이블은 결과값을 가진 배열이다.

 - 룩업테이블 배열의 인덱스는 입력값이다. 배열의 값은 출력값이다.

 - 실시간 시스템에서 사전 초기화 모듈이 있는 경우 사용하면 좋다. 


4. 추가적으로 공부해야 할 부분


 - 인덱스에 들어가는 데이터 값이 소수라면 LUT를 사용할 수 있는가? 답은 YES. 


#참조 

http://www.mochima.com/articles/LUT/LUT.html#what_is_a_LUT


written by chim

반응형

'알고리즘 & 자료구조 > 알고리즘&자료구조' 카테고리의 다른 글

Hash Functions (continued) - Fowler/Noll/Vo Hash  (0) 2013.06.04
해쉬함수  (0) 2013.05.23
메모리 폴  (0) 2013.05.12
AVL트리(Adelson-Velskii / Landis)  (0) 2013.04.14
K-d 트리  (0) 2013.04.14
반응형


http://rakeion.blog.me/120124615590



메모리풀이란

공간을 미리 할당하고,

메모리 할당 요청시(new) 미리 할당된 공간의 포인터를 반환하는 방법이다.

 

아래는 new요청시, 메모리풀의 메모리 모습변화이다.

  1. 빨간화살표: 가용한 메모리 공간을 가르키는 포인터
  2. 남색 블록3개: 메모리 Page
  3. 남색 블록1개: 메모리 블록
  4. 초록색 블록: 다음 블록에 대한 Next 포인터

 

아래 흐름처럼, New 요청시,

현재 free 포인터를 반환하고,

Free 포인터값은 초록색 NEXT포인터값으로 할당한다.

 



 

 

반대로, delete요청시,

delete요청된 블록의 Next포인터를 현재 Free포인터값으로 지정하고,

Free 포인터는 delete 요청된 블록주소로 바꾼다.

 

 

 

잘 관찰해보자.

Free포인터가 지정하고 있는 블록공간을 이으면,

바로 SLL( Single Linked List)가 된다.

 

그리고 이 SLL은 모두 가용한 공간들이다.

 

정리하면, 메모리풀은

  1. 가용한 공간들을 링크드리스트로 묶어서 관리한다.
  2. 그 가용공간이 바닥나면, 빈 링크드 리스트를 덧붙이는 방식으로 공간을 늘린다.
    ( Add List to the Tail)
  3. 공간을 해제하면, 그 공간을 링크드리스트 맨 앞쪽에 INSERT한다.
    (Add Node to the Head)

 

#pragma once

 

#include <CASSERT>

#include <CSTDLIB>

 

templateclass TClsint ALLOC_SIZE =10>

class cMemPool

{

public:

    static voidoperator newsize_t in_alloc_size)

    {

        // Check

        assertin_alloc_size == sizeof(TCls));

        assertin_alloc_size >= sizeof(TCls*));

 

        // Check: 공간할당이 안되어 있거나, 공간이 부족하면 재할당

        ifs_pFree ==NULL)

        {

            s_pFree =(TCls*)mallocsizeof(TCls)* ALLOC_SIZE);

            

            // 블록 간에 링크드 리스트를 만든다.( 나중에 해제시 사용가능한 놈들만 링크)

            TCls** pCur =reinterpret_cast<TCls**>(s_pFree);

            forint i =0; i <ALLOC_SIZE -1; i++)

            {

                pCur[i] =s_pFree +i +1;

            }

            pCurALLOC_SIZE -1] =NULL;

        }

 

        // Last

        TClsret =s_pFree;

        s_pFree =* reinterpret_cast<TCls**>( s_pFree);

        s_block_cnt++;

 

        return ret;

    }

    static void operator deletevoidpDel)

    {

        // Check

        ifs_block_cnt ==0) throw "delete error";

 

        // Exec: 삭제할 공간과 Free한 공간 연결

        TCls** pCur =reinterpret_cast<TCls**>( &pDel);

        *pCur =s_pFree;

        s_pFree =*pCur;

        

        // Last

        s_block_cnt--;

    }

 

public:

    cMemPool() {}

    virtual ~cMemPool() {}

 

private:

    static TClss_pFree;

    static int     s_block_cnt;

};

 

templateclass TClsint ALLOC_SIZE>

TClscMemPool<TCls>::s_pFree =NULL;

 

templateclass TClsint ALLOC_SIZE>

int cMemPool<TCls>::s_block_cnt =0;

 



 

[출처] 메모리풀|작성자 라이오스

반응형
반응형

http://blog.naver.com/tlstmdrb8862/70163870063



AVL트리란? 


- 항상 균형을 유지하는 이진트리

- 어느 노드 기준으로 좌측 또는 우측 자식 노드의 트리가 불균형하게 상대적으로 너무 길거나, 잛은 형태의 트리구조를 같은 길이의 형태로 변환 시켜주는 알고리즘을 가지고 있는 트리


 

특징

- 모든 노드의 서브트리 높이 차이가 1이하이다. 만약 높이 차이가 2이상이 된다면 노드들은 스스로 재배치하여 균형 상태를 유지 해야 한다.

- 탐색은 이진트리 탐색과 동일하다.

 

장점

- 탐색속도가 빠르다

- 트리 전체를 재배열시키지도 않아도 트리의 균형이 유지된다.

 

AVL트리의 균형이 깨지는 경우

- 트리의 균형은 노드를 삽입하거나 삭제할 때 깨질 수 있다.

삽입,삭제 연산을 할 경우 근접한 노드 뿐 아니라 루트까지의 경로에 있는 노드에도 영향을 줄 수 있다. 만약 그 영향으로 균형차가 2이상 되는 노드가 생긴다면 그 노드의 서브트리들을 회전 하여 트리의 균형을 잡는다.

 

( N은 삽입된 노드, A는 N에서 가장 가까운 균형 인수 2이상이 된 노드 )

 

LL 타입

- A의 균형인수가 2가 된다. LL( A의 Left자식의 Left자식)에 삽입 되면서 문제가 발생한 경우



해결방법

- B의 오른쪽 자식을 A의 왼쪽 자식으로 연결해준 후, B의 오른쪽 자식을 A로 바꿔준다.

 

RR 타입

- RR( A의 Right자식의 Right자식)에 삽입 되면서 문제가 발생한 경우다.

해결 방법

- LL과 반대로 B의 왼쪽 자식을 A의 오른쪽 자식으로 연결해주고, B의 왼쪽 자식을 A로 연결합니다.

 

LR 타입

- LR( A의 Left자식의 Right자식)에 삽입 되면서 문제가 발생한 경우다.

해결방법

-  처음에 노드를 Left(시계반대)방향로 회전으로 하여 정렬된후 Right(시계)방향으로 또 한번 회전하혀 정렬한다.

 

RL 타입

- RL( A의 Right자식의 Left자식 )에 삽입 되면서 문제가 발생한 경우다.

해결방법

- LR의 반대로 처음에 노드를 Right(시계)방향로 회전으로 하여 정렬된후 Left(시계반대)방향으로 또 한번 회전하혀 정렬한다.

 

참고 URL

http://girlsy7.tistory.com/134

http://blog.naver.com/ellay06?Redirect=Log&logNo=120171026944

반응형

'알고리즘 & 자료구조 > 알고리즘&자료구조' 카테고리의 다른 글

LUT (Lookup Table) 룩업테이블 : 미리 계산된 테이블  (0) 2013.05.19
메모리 폴  (0) 2013.05.12
K-d 트리  (0) 2013.04.14
P vs NP 문제 이야기  (0) 2012.12.23
순회 세일즈맨 문제(TSP)  (0) 2012.12.23
반응형



K-d 트리 

2007/07/13 09:28

복사http://blog.naver.com/slump112/90019717360


10.1 k-d 트리
k-d 트리는 다차원의 점 데이타를 인덱스할 수 있는 가장 간단하면서도 기본적인
데이타 구조이다. k-d 트리는 일반적으로 디스크 저장을 고려하지 않고 주기억장치 상
에서 동작하는 인덱스 구조이다. 따라서 대용량의 데이타에 대해서는 적당하지 않고
소규모의 다차원 점 데이타를 인덱스할때 적당하다.


10.1.1 k-d트리의 데이타 삽입


k-d 트리는 기본적으로 이원 탐색 트리를 다차원 공간으로 확장한 것이다. 따라서
기본적인 구조나 알고리즘은 이원 탐색 트리와 유사하다. 삽입하려는 값이 트리의 기
존 노드의 값보다 작거나 같으면 왼쪽 자식노드에 저장하고, 보다 크면 오른쪽 자식
노드에 저장하게 된다. 이원 탐색 트리의 경우는 값이 하나이므로 그냥 비교를 할 수
있지만, 다차원 데이타의 경우 차원에 따라 여러 개의 값이 있으므로 그냥은 비교할
수 없다. 따라서 k-d 트리의 경우에는 각 차원을 번갈아가면서 비교하게 된다. 예를
들어 3차원의 (x,y,z) 값의 집합이 있을 경우, 트리의 첫번째 레벨에서는 x값에 대하여
비교를 하고,두번째 레벨에서는y값에 대해서 비교를 하고, 세번째 레벨에 대해서는 z
값에 대하여,네번째에서는 다시x값에 대하여 비교를 하는 식으로 트리를 구성하게된다.
k-d 트리의 예로 2차원 공간에 그림 10.1과 같은 10개의 점이 있다고 가정하자.
이 점들을 차례로 k-d 트리에 삽입하여 보자.


 

우선 a점을 삽입하면 그림 10.2와 같이 k-d 트리의 루트로 a가 저장된다. 이 그림
에서 노드에 자식 포인터가 표시되지 않은 경우는 자식 포인터가 널 값을 지니고 있음
을 의미한다.
 

이제 다음 b 점을 삽입하자. 트리의 첫 레벨이므로 x축의 값을 서로 비교해야 한
다. b의 x값인 2는 a의 x값인 5보다 작으므로 트리의 왼쪽 자식 노드를 따라가야한다.
그런데, a의 왼쪽 자식 노드는 널이므로 b는 a의 왼쪽 자식 노드에 그림 10.3과 같이
삽입된다.
 

다음c를 입력하면 마찬가지로 루트노드인a에서 x값을 비교하면 c의 x값인 9가
a의 x값보다 크므로 그림 10.4와 같이 a의 오른쪽 자식노드에 삽입된다.
 

다음d를 입력하면 루트노드에서는a와 x축 값을 비교하여 왼쪽 자식노드인 b로
가게 된다. b와는 두번째 레벨이므로 y축의 값과 비교를 하면 d의 y축 값이 더 작으므
로 다시 왼쪽 자식노드로 이동하게 되고,이 경우 널 값이므로 그림10.5와 같이 b의
왼쪽 자식 노드로 저장된다.
 

마찬가지의 방법으로 나머지 데이타들을 삽입하면 그림 10.6과 같이 k-d 트리가
구성된다.
 

10.1.2 k-d 트리의 데이타 검색

k-d 트리의 검색 역시 삽입과 마찬가지로 트리의 레벨에 따라 각 차원의 값을 번
갈아서 비교한다. 예를 들어, 위와 같은 k-d 트리에서 (4,8) 위치의 점은 무엇인가?
하는 질의가 들어왔다고 가정하자.먼저 트리의 루트 노드에서는x값을 비교하여 질의
값의x값이 더 작으므로 왼쪽 자식 노드b를 방문하고, 다음 b에서는 y값을 비교하여
오른쪽 자식 노드인 j를 방문하게 된다. j와 질의 값을 비교하면 같으므로 (4,8) 위치
의 점은 j임을 알 수 있다. 마찬가지로 (6,2) 위치의 점을 검색한다고 하면 트리의 루
트부터 a, c를 거쳐 e 노드와 질의 값을 비교하게 된다. 여기서 질의의 x값이 e 점의
x값보다 작으므로 왼쪽 자식 노드를 방문해야 되지만,왼쪽 자식 노드가 널이므로 해
당하는 점이 없음을 알 수 있다.
따라서 k-d 트리의 높이를 h라 할 때, 이러한 점 검색 질의의 경우, 최악의 경우
의 수행시간은 O(h)가 된다.
k-d 트리의 가장 큰 단점은 트리가 데이타가 입력되는 순서에 따라 달라진다는
점이다. 따라서 데이타의 분포나 입력 순서에 따라 트리가 균형적으로 구성되지 못하
여 트리의 높이가 높아질 수 있다. 예를 들어, 위와 같은 예에서 데이타가 g, d, b, e,
h, a, f, c, i, j 와 같은 순서로 입력된다면 트리는 그림 10.7과 같이 구성된다. 이 경
우 c 점을 검색하기 위해서는 g, d, b, e, h, a, f 점의 노드를 방문하고 나서야 c점을
찾을 수 있게 된다.


[출처] K-d 트리|작성자 후로그래머

반응형

'알고리즘 & 자료구조 > 알고리즘&자료구조' 카테고리의 다른 글

메모리 폴  (0) 2013.05.12
AVL트리(Adelson-Velskii / Landis)  (0) 2013.04.14
P vs NP 문제 이야기  (0) 2012.12.23
순회 세일즈맨 문제(TSP)  (0) 2012.12.23
해밀턴회로  (0) 2012.12.23
반응형

BLOG main image



결론적으로 말하자면 인간이 추측하는 근사치를 구한다는 말이다


학창시절에 이 알고리즘이 풀리는거냐 안풀리는거냐에 대한 알고리즘을 푼 적이 있는데 이것도 P vs NP 문제에 대한 하나의 부류라고도 할 수 있다



그때 알고리즘의 답이 있는가에 대해 풀면서 생각하길 인간에 대해서도 이것이 풀린다면 대단한 알고리즘이 되겠다  라는 생각을 했던적이 있었다



아래는 P vs NP에 대해 약간 자극적으로 설명을 한 글이 있어서 퍼왔다


p.s 너무 자극적이여서 이해가 안될 수가 없네 ㅋㅋ






http://bazzi1003.blog.me/60102597195


원본 링크

http://ruliweb.nate.com/mypi/mypi.htm?id=jmbhk&num=1459

※로그인 필요

[출처] P vs NP 문제 이야기|작성자 데스모


 

"P대NP 문제": 이거 풀면 기본 13억 받는데, 중요한 건 이거 풀면 13억이 문제가 아니라 돈이 넝쿨 째로 굴러들어 올 수도 있음.


우선 이 문제는 컴공 쪽에서 나온 문제임. 
그러니까.... 오늘은 밤이 늦었으니까 간단히 개요만 설명하고 넘어가도록 하겠음. 


우선 컴퓨터의 알고리듬이 어떻다느니, 연산을 통해서 풀 수 있는 문제가 P(Polynomial-time)네, NP(Non-deterministic P)네 하는 어려운 소리는 다음 편에 하도록 하고, 가장 간단하고 쉽게 이해할 수 있는 예를 들어 보겠음. 이제 너님들은 유명한 패션 디자이너야. 그리고 여기는 너님들의 패션쇼를 보는 장소이고. 올 트렌드가 되는 옷들을 입고 모델들이 무대를 돌고 있어. 그런데 너님들이 쇼만 보고 있자니 지금 심심한거야, 그래서 모델 언니들 슴가를 눈으로 어림짐작 하는 놀이를 하기로 했다고 쳐. 

자, 너님들은 여태까지의 경험과 지식을 바탕으로 슴가 크기를 측정을 했어. A모델은 88, B모델은 84, C모델은 77... 자, 그럼 물어보자. 너님들의 측정은 정확할까? 답부터 말하자면 그럴 수도 있고 아닐 수도 있겠지, 뭐.

여하간 그렇게 너님들이 슴가 크기 측정 놀이를 마치고 난 뒤 얼마 지나서, 이제는 너님들의 패션쇼를 하는 때가 되었어. 그래서 모델 언니들 몸에 맞게 옷을 만드려고 모델 언니들 사이즈를 재게 된 거지. 어머나, 그런데 전번 쇼에서 너님들이 치수 맞추기 놀이의 재료로 채택됐던 모델 언니들이 끼어 있네?

그럼 이제 너님들의 추측과 실제 슴가 크기에 대해서 비교해 보는 게 가능하겠지? 그럼 우선 슴가 크기를 재는 방법에 대해서 생각해 보도록 하자. 아이큐가 아~주 나쁜 디자이너라면 줄자로 치수를 이렇게 잴거야. 


1cm 재보고 이거보다 크네? 그럼 다음 2cm 재보자. 2cm재보고 이거보다 크네? 그럼 다음 3cm 재보자.......


이런식으로 계속해서 한 단위씩 높여가다가 결국 모델 언니의 실제 슴가 크기가 측정이 될 때까지 반복하겠지. 이렇게 측정하는 저능아는 없을 거라고? 맞아, 없을 거야. 그런데 이게 컴퓨터의 계산 방식인거야. 어느정도의 지능을 가진 인간이라면 '아, 저 모델 언니 슴가는 84 쯤 되겠군.'하면서 줄자를 84쯤 부터 시작해서 작으면 '어라, 작은가?' 하면서 치수를 늘리겠지.

그런데 컴퓨터는 이게 안되는 거야. 여기서 바로 인간과 컴퓨터 간의 사고에 결정적인 차이점이 있는거지. 인간의 사고는 '추측'이 가능해. 그래서 어떤 문제를 접했을 때 그 문제에 대해서 '쉬운지/어려운지', '어떤 알고리즘을 사용해야 되는지' 같은 것들을 추측할 수 있는 거지. 그리고 그렇게 추측한 결과를 통해 '연산시간을 비약적으로 단축'할 수도 있어.

고작 슴가 크기로 이야기 하니까 좀 시시해 보이기도 할거야. 그럼 조금 긴 시간을 요하는 연산 문제를 생각해 보자. 


자연수 N에 대하여 N이 10^30(10의 30승)자리의 수라고 할 때, 자연수 N에 대하여 10^10자리의 약수를 하나만 구하라.


무지하게 복잡하지? 이 문제를 풀려면 컴퓨터의 연산속도로는 무지하게 오랜 시간이 걸릴 거야. 그런데 NP가 되면 어떨까? 문제 해결 속도가 비교할 수도 없이 줄어드는 거야. 뭐 쿼드코어 이런 건 물론이고, 이게 가능하게 된다면 지금 가정용 컴퓨터로도 거대 연구소에나 하나씩 있는 슈퍼컴퓨터 정도는 우습게 발라버릴 수 있다는 거지.(물론 이건 좀 오바를 보탠 거지만) 때문에 P=NP냐, P≠NP냐는 겁나게 중요한 문제가 된거야. 

만약 P=NP라면 당장 현재 존재하는 모든 암호체계는 무용지물이 돼. 이걸 푼 인간은 말 그대로 그 무엇으로도 막을 수 없는 '창'을 가지게 된 거지. 만약 너님이 이걸 풀게 된다면, 그냥 13억 안 받고 보안회사 차리는 게 오히려 돈을 떼로 벌 수 있을 걸. 그리고 좀 더 오바해서 생각하면, 터미네이터도 꿈이 아니야. 이 P대NP문제는 인공지능과도 밀접한 연관이 있거든. 추측할 수 있는 연산은 '학습'이나 '반성'이 가능하다는 뜻이기도 하거든. 그럼 정말 기계가 인간을 지배할 지도 모르는 일이지.

여하간 그래서 이 문제는 되게 중요한 거야. 진짜 이거 하나만 풀면 못해도 13억(P≠NP일 경우), 잘하면 세계정복도 가능하니까. 뭐, 최대한 수학적인 이야기를 안하고 쉽게 풀어보려다 보니까 비유가 구린 부분이 좀 있긴 하지만, 그 부분에 대해서는 다음 편에 좀 어렵게 들어가면서 보완할 테니까 이해해 주길 바라.

[출처] P vs NP 문제 이야기|작성자 데스모

반응형

'알고리즘 & 자료구조 > 알고리즘&자료구조' 카테고리의 다른 글

AVL트리(Adelson-Velskii / Landis)  (0) 2013.04.14
K-d 트리  (0) 2013.04.14
순회 세일즈맨 문제(TSP)  (0) 2012.12.23
해밀턴회로  (0) 2012.12.23
근의 공식 알고리즘 소스코드  (0) 2012.11.02
반응형
BLOG main image


블로그 돌아다니다가 TSP에 대한 글이 있길래 올린다..

학창시절 알고리즘 수업시간에 풀었던 문제로 언뜻 기억이 난다 ㅎㅎ 새록새록~

그때 설명해주던 조교형이 있었는데 세일즈맨이 여기저기 들리다가 너무 지처서 좀더 빨리 돌 수 없을까 해서 나타난 문제라고했던 말이

아직도 생각이 난다ㅋ







2012/10/08 01:40

안녕하세요. 삼성 소프트웨어 강남 멤버십 19-1기이자, 엘리트 멤버 2기 김주영 입니다. 지난 시간에 이어 이번에는 유전알고리즘을 활용한 순회 세일즈맨 문제(Traveling Salesperson Problem : TSP)의 해결을 포스팅하려 합니다.

1.  순회 세일즈맨 문제란?

먼저 위키피디아의 글을 첨부하여 순회 세일즈맨 문제를 소개하고자 합니다.

여러 도시들이 있고 한 도시에서 다른 도시로 이동하는 비용이 모두 주어졌을 때모든 도시들을 단 한 번만 방문하고 원래 시작점으로 돌아오는 최소 비용의 이동 순서는 무엇인가그래프 이론의 용어로 엄밀하게 정의한다면, ‘각 변에 가중치가 주어진 완전 그래프(weighted complete graph)에서 가장 작은 가중치를 가지는 해밀톤 회로(Hamiltonian cycle)을 구하라’ 라고 표현할 수 있다.” - Wikipedia <외판원 문제 >

 

위의 설명에서 보듯이 N개의 도시가 있을 때 어떤 경로로 도시들을 순회하는 것이 최적화된 경로일까를 고민하는 문제입니다. 아래 사진과 같이 5개의 도시가 있는 경우 5도시의 번호 조합으로 최적화를 수행하게 됩니다예를 들어 1-2-3-4-5 순서로 방문할 수도 있고, 4-3-1-2-5 처럼 얼핏 보기에도 길게 돌아가는 듯한 경로로 문제를 해결할 수도 있습니다.

위와 같은 조합최적화에서 경우의 수는 N! (5! = 5*4*3*2*1) 에 해당하여도시의 수 N이 커질수록 계산 량이 기하급수적으로 증가하게 됩니다. 잠깐 살펴보자면 도시의 개수가 5개인 경우에야 120가지의 경우의 수를 모두 계산해서 최적의 해를 구하면 그만이지만한국에 있는 시 단위의 행정구역만 세어도 84개입니다각 도시의 최적 순회 경로를 판단하기 위한 84!는 3.3142401345653532669993875791301e+126입니다. 3으로 시작하는 126자리의 수네요.. 1000개의 조합을 1초내로 계산할 수 있는 컴퓨터를 사용하더라도 3.3142401345653532669993875791301e+123초의 수행시간이 필요합니다...

최소값을 구하기 위한 모든 경우의 수를 고려하는 방법은 포기하고유전자 알고리즘을 이용하여 근사값을 구할 수 있도록 해봅시다!

2. 유전 알고리즘 설계

a. 유전자 설계

각 해는 도시의 순회 경로를 표현하도록 합니다. 5개의 도시 문제에서 사용할 유전자의 모양은 12345,23541,13245 등이 될 것입니다. 12345는 도시를 1->2->3->4->5 순으로 순회하는 해를 뜻합니다앞으로 코딩할 예제에서는 40개의 도시를 이용할 예정이니유전자의 길이는 40개가 되겠죠!

b. 적합도 함수 (Fitness Function)

적합도 함수를 계산하기 위해 각 도시별 이동비용을 계산하기 위해 40개의 도시에 단순히 2차원 좌표값을 지정해각 도시의 거리로 비용을 계산하려고 합니다각 도시는 Matlab 예제에서 지원하는 미국 지도 내의 임의의 점으로 표현을 하고각 도시의 위치는 해당 점의 위치로 표현을 하고자 합니다.

각 도시간의 거리를 이용하여 각 해의 적합도를 계산한 후에해의 적합도를 정렬해 순위기반 선택방법을 적용할 수 있도록 디자인 되었습니다.

c. 선택 방법

선택 방법은 이전 포스팅에 사용했었던 룰렛 휠 방법을 그대로 사용하도록 합니다다만 달라진 것은 이전의 룰렛휠 기반은 적합도의 높고 낮은 비중에 따라 선택압이 높고 낮게 결정되었다면이번 포스팅의 예제는 해의 순위에 따라 선택압이 조절된다는 점입니다.

d. 교차 방법

두 해의 교차의 경우에는 꽤 복잡하게 진행됩니다이전의 유전자 알고리즘 예제에서는 랜덤하게 선택된 여러개의 점을 이용해 번갈아 가며 각 부모 유전자를 유전하는 자식을 생성하였습니다. (더 자세히 알고 싶으시면이전 포스팅을 참고해 주세요).

dsdsdv

하지만 이전에 적용하였던 방식은 이번 예제와 같은 조합 최적화 문제에서는 유효하게 적용되지 않습니다그림으로 쉽게 설명 드리자면기존의 다점 교차방식을 사용하게 되면 왼쪽과 같이 자식해에 같은 도시의 번호가 중첩하는 경우가 생기게 되므로 도시 순회 시 같은 도시를 반복하게 되는 경우가 생기게 됩니다.

조합 최적화 문제처럼 순열을 이용한 유전자 표현을 하게 되는 경우 PMX(Partially Mapped Crossover) 방법이 보통 쓰이게 되는데 이 방법은 교환되는 유전자마다 원래 위치의 유전자를 치환해 줌으로써 유전자가 서로 중첩되는 경우를 방지해주며 교차하는 방법입니다.

하지만 저는 이번 예제에서 더욱 간단한 교차를 위해 두 부모의 교배를 포기하고, 한 부모로부터 유전정보를 변형시켜 자식 유전자를 생성시키는 방법을 적용하였습니다부모 유전자의 두 점을 선택하여 점 사이의 유전 정보 순서를 뒤섞는 식으로 교차연산을 대신하였습니다이 방법을 적용함으로써 유전정보가 서로 중첩되는 상황을 간단하게 회피할 수 있고기존에 유전자가 가지고 있던 부모의 특성을 어느 정도 자식에 전수해 줄 수 있도록 디자인 하였습니다.

e. 변이 방법

변이는 교차와 비슷한 방법을 사용하여 선택된 두 점의 유전정보를 서로 교환해주는 형식으로 채택하였습니다.

f. 기타

(1) Elitism을 적용하여 매 세대마다 가장 높은 적합도를 가진 2개의 엘리트를 다음 세대로 보존하도록 설계되었습니다.

(2) 한 세대의 해의 개수를 100개로 고정하여 진행하였습니다.

3. 구현

MATLAB 2009a

지난 포스팅과 같이 C#을 이용해서 코딩을 완성했다가.. 소스코드를 모두 날리는 바람에 홧김에 MATLAB으로 다시 코딩했습니다.(프로젝트는 절대 USB에 보관하지 맙시다..) MATLAB의 Optimization Toolkit 내의GA를 이용하면 편하게 할 수 있겠다라는 생각에 시도를 했는데.. SGA(Simple Genetic Algorithm)의 경우에는 정말 몇 개의 파라미터만 던져주면 잘 동작하더라구요.. 하지만 이번 예제와 같이 유전자의 모양자체가 특이한 경우는 거의 대부분을 Custom 해줘야해 시간이 상당히 오래 걸렸습니다. Matlab이 익숙하지 않은 탓도 너무나 크고요!

4. 결과 분석

a. 초기 도시 모습

그림과 같이 40개의 도시의 최적 순회 경로를 찾기 위해 최적화를 수행했습니다. 위에서 설명드린 바와 같이 각 도시간의 거리를 이용해서 적합도를 계산합니다.

b. 반복 결과 과정

 

17회 반복 최적 경로

21회 반복 최적 경로

다음과 같은 경로가 현재의 Best Fitness에 해당합니다아직은 정말 아무렇게나 효율없이 이동하고 있습니다몇 세대를 더 진행해보며 유전알고리즘의 수렴이 어떻게 이루어지고 있는지 살펴보도록 합시다!

 

50회 반복 최적 경로

61회 반복 최적 경로

점점 경로의 가닥이 잡혀가기 시작합니다. 21회~50회의 반복을 거치면서 가장 최적이 되는 해의 모양이 급격히 변경되었고 일반적으로 전체 지도를 한바퀴돌면서 경로를 찾아가는 모양의 경로가 나타나기 시작합니다.

 

73회 반복 최적 경로

93회 반복 최적 경로

 

104회 반복 최적 경로

129회 반복 최적경로

해의 모양이 우리가 생각하는 최적 경로 모양과 비슷하게 잡혀갑니다. 129회의 최적 경로가 최종적으로 발견된 최적 경로의 것으로 판단되고 유전자 알고리즘이 171회까지 해를 유지한 후 종료합니다. (평균 적합도의 변화량이 기준보다 낮을 경우 유전 알고리즘의 반복이 종료되도록 설정되었습니다. 최대 반복수는 1000 세대 입니다.)

 

171회 반복 최적 경로<수렴 완료>

해의 모양에 있어서 조금 아쉬운 부분들이 보이긴 하지만, 대체적으로 유전자 알고리즘의 수렴을 알아볼 수 있는 좋은 모양을 보여주며 수렴을 종료합니다. 171회 반복 종료 후의 해의 모양이 전역해의 모습은 아닌 듯 하니 더욱더 해의 품질을 높일 수 있는 방법을 생각해보고자 합니다.

c. 한 세대 최대 해 수에 따른 수렴 정도 관찰

 한 세대당 100개의 해를 가지는 유전 알고리즘과 세대당 10000개의 해를 가지는 유전 알고리즘의 수렴정도를 비교해 보았습니다.

10000개의 해

100개의 해

상대적으로 10000개의 해를 가진 왼쪽 그림이 확실히 수렴이 빠른 것을 알 수 있습니다. 뿐만 아니라, 해의 분포가 넓게 위치하기 때문에 평균 적합도(파란 점)의 이동이 적고, 전역해를 찾기에 더욱 유리한 모습을 보이고 있습니다. 100개의 해를 가진 유전 알고리즘은 해의 분포가 전체적으로 고르지 못하기때문에 평균의 이동이 비교적 불규칙적으로 보입니다. 또한 적합도의 수렴이 10000개의 해에 비해 느려 더 많은 반복을 수행하게 됩니다.

5. 마치며..

이번 포스팅에서는 유전알고리즘을 활용한 TSP의 해결에 대한 이야기를 풀어봤습니다. 코딩에 걸린 시간에 비해 포스팅으로 설명할 수 있는 부분이 좀 적었다고 생각되네요. 포스팅에 대한 지적사항은 정말 겸허히 받아들이고 더 좋은 학습을 위해 수정을 하도록 하겠습니다! 질문이나 지적사항은 ohgreat11@gmail.com 으로 보내주시면 감사하겠습니다!

이번 포스팅은 코딩으로 많은 시간을 쏟은 탓일까 아쉬움이 큽니다.. 이 아쉬움을 다음 포스팅에서 풀도록 하겠습니다. 다음 포스팅은 아마도 이번 예제의 소스코드 설명과 함께 MATLAB Optimization Toolkit 사용법에 관련된 내용일듯 하네요!

좋은 하루 되시구요! 다음에 또 뵙겠습니다!





위에 블로그 찾은김에 성능에 대해서도 하나 나오길래 같이 올려둠



http://the2384.blog.me/10106444843





TSP (Traveling Salesman Problem) 이란 문자 그대로,

 

물건을 판매하기 위해 여행하는 세일즈맨의 문제입니다.

 

물건을 팔기 위해서 어떤 도시를 먼저 방문해서 최소한의 비용으로

 

도시들을 모두 순회하고 돌아올 수 있는 지를 구하는 것이 이 알고리즘의 핵심입니다.

 

TSP는 컴퓨터 사이언스의 대표적인 난제로, 컴퓨터 사이언스에서 가장 많은 연구가 이루어지고 있는 주제 중 하나 입니다.

 

문제를 직관적으로 이해하기란 간단하지만 답을 도출 시키는 것은 쉽지 않기 때문입니다.

 

TSP는 가장 대표적인 NP-Hard 문제입니다.

 

번외로, NP-Hard란 Nondeterministic Polynomial - Hard의 약자입니다.

 

즉 결정석 다항식으로 구성할 수 없다는 것이죠.

 

e.g) x-2 = 8 이것은 대표적인 다항식 문제입니다. 답을 도출해내기도 쉽죠.

 

하지만 A, B, C라는 도시 세가지를 놓고 어떤 도시를 먼저 방문하는 것이 총 비용상으로

 

가장 효율적인가(Optima)를 구하는 것에 대해서는 방정식을 세울 수 없습니다.

 

물론 다항식으로 나타낼 수 있을 수도 있습니다. NP하드라고 해서 다항식이 아니라는 것은 아닙니다.

 

다만 다항식으로 표현될 수 있을 지의 여부가 아직 알려지지 않았다라는 것입니다.

 

오늘날의 컴퓨터는 P(Polynomial)에 관한 문제는 일정 term을 주면 해결가능 할 것입니다.

 

하지만 NP는 그와 반대로 엄청난 실행 사이클을 잡아 먹을 것입니다.

 

즉, NP-Hard란 TSP문제와 같이 모든 경우의 수를 일일히 확인해보는 방법이외에는 다항식 처럼 답을 풀이 할 수 없는 문제들을 말합니다.

 

지금까지 알려진 방법으로는 Polynomial time내에 어느 성능 이상의 TSP 최적해를 보장할 수 있는 방법이 없습니다.

이론적인 근사 알고리즘이 있을 뿐입니다. <Christofides, 1976>

 

 

 

<그림 1>

 

그림 1과 같이 원으로 저렇게 산발적으로 퍼진 점들을 어떤 점의 순서로 이어야 최소한의 비용으로 모든 점을

 

순회할 수 있는 지를 구하는 것이 바로 TSP의 목적입니다.

 

<그림 2>

 

그림 2와 같이 미대륙 내의 모든 도시를 방문하는 것도 TSP의 문제중 하나입니다.



 

보스턴에서 시작을 하고 모든 도시를 방문한다고 가정을 합시다.

Boston -> San Diego -> Atlanta -> St.Louis -> New York 의 경로로 갈 수 있겠지만

Boston -> San Diego -> Atlanta -> St.Louis -> Atlanta -> New York의 경로가 더 나을 수도 있습니다.

 

이 처럼 도시간의 비용을 모두 따져서 최적의 순회 방법을 찾는 것이 TSP입니다.

위와 같은 문제는 쉽게 풀이 할 수 있습니다.

왜냐하면 n의 숫자가 너무 작고 (n이란 도시의 숫자를 말합니다.) 방향성을 띄고 있기 때문입니다.

 

위의 문제에서 업그레이드를 해서 쌍방향으로 이동이 가능하다고 가정하고 n이 100이상으로 커진다고 하면

사람이 풀이하는 데 많은 시간이 걸릴 것입니다.

반응형

'알고리즘 & 자료구조 > 알고리즘&자료구조' 카테고리의 다른 글

K-d 트리  (0) 2013.04.14
P vs NP 문제 이야기  (0) 2012.12.23
해밀턴회로  (0) 2012.12.23
근의 공식 알고리즘 소스코드  (0) 2012.11.02
Red-Black Tree  (0) 2012.11.01
반응형

BLOG main image


경로는 그냥 연결된 구조이고

회로란 처음 시작점에서 출발하여 다시 시작점으로 오는구조이다


아래 포스팅한것의 결과를 보면 1에서 출발했다가 다시 1로 왔음을 알 수 있다

퍼오긴했는데 그래프는 안나온다(아래 블로그로 가도 마찬가지)






http://junghong456.tistory.com/11

해밀턴회로

프로그래밍/Algorithm 2011/02/25 23:19

Hamilton Circuit

해밀턴 회로는 모든 정점을 방문하여 처음 출발점으로 돌아 오는 회로이다. 해밀턴 회로를 사용하려면 일단 그래프를 배열로 표현해야 한다. 만일 다음과 같은 그래프가 있다고 하자

<!--[if !vml]--><!--[endif]-->

그래프는 2차원 배열로 나타내게 된다. 간선이 연결된 곳은 1, 그렇지 않은 곳은 0으로 저장한다. 위의 그래프를 배열로 표현하면 다음과 같다.


1

2

3

4

1

0

1

1

0

2

1

0

1

1

3

1

1

0

1

4

0

1

1

0

해밀턴은 정점을 두 번이상 방문하지 않아야 하므로 처음 false 로 해두었다가 한번 방문하면 true로 바꾸어 정점을 체크하는 것만 오일러 코드에 추가하면 해밀턴 코드가 된다. 해밀턴 회로에 대한 입력 예제가 다음과 같다.

입력

4 5

1 2

1 3

2 3

2 4

3 4

출력

1->2->4->3->1

입력의 첫 번째 줄은 정점의 개수와 간선의 개수를 나타낸다. 다음에 간선의 개수 만큼 한 줄씩 간선으로 연결되는 두 정점의 번호가 입력된다.


===========================================================================================================================


문제 자체는 상당히 쉬운 문제이다.

다른 모든 정점을 탐색하기 전에는 시작점으로 가지 않고

탐색했던 정점을 다시 가지 않도록만 해주면 된다.

문제 푸는 방식은 당연히 재귀호출이 편하다.




#include<stdio.h>
#include<stdlib.h>
int p[10][10],n;
int check[10][10];
int g[10];
void input()
{
    FILE *fp=fopen("input.txt","r");
    int a,x,y,i;
    
    fscanf(fp,"%d %d", &n, &a);
    for(i=1;i<=a;i++)
    {
        fscanf(fp,"%d %d", &x, &y);
        p[x][y]=1;
        p[y][x]=1;
    }
    fclose(fp);
}
void process(int k, int cnt)
{    //세팅해주어야 할 것들:
    //1. 다른 정점을 모두 탐색하기 전까진 시작점으로 오지 않도록 한다.
    //2. 갔던 곳은 가지 않도록 한다.
    int i;

    if(k==1) //k가 1일때 다음 else if()문을 가지 않게 하는 기능도 한다.
    {
        if(cnt>n) // 다른 정점을 모두 탐색했을 경우 출력
        {
            printf("%d ", k);
            for(i=1;i<=n;i++)
                printf("%d ", g[i]);
            printf("\n");
        }    
    }
    // 다른 정점을 모두 탐색하지 않았는데 k가 1이라면 return
    else if(cnt <= n && k == 1) 
        return;
    for(i=1;i<=n;i++)
    {
        //갈 수 있는 정점이고 탐색하지 않았던 곳이라면 탐색
        if(p[k][i]==1 && check[k][i] == 0) 
        {
            check[k][i]=1;
            check[i][k]=1;
            g[cnt]=i; // 경로 입력
            process(i,cnt+1);
            check[k][i]=0;
            check[i][k]=0;
        }
    }
}
void main()
{
    int i;

    input();
    process(1,1);
}

반응형
반응형

oid QuadraticEquation(double A,double B,double C)
{

 if ( A == 0 )
 {
  printf("This equation is not Quadratic\n");
  return ;
 }

 double Temp ;
 Temp = B*B - 4*A*C ;

 if ( Temp >= 0 )
 {
  double ans1,ans2 ;
  ans1 = ( -1*B + sqrt(Temp) ) / ( 2*A ) ;
  ans2 = ( -1*B - sqrt(Temp) ) / ( 2*A ) ;

  printf("Two Real Roots: x1 = %f x2 = %f\n",ans1,ans2);
 }
 else 
 {
  double real , complex ;
  real = ( -1*B ) / ( 2*A ) ;
  complex = sqrt(-1*Temp) / ( 2*A ) ;

  printf("Complex Conjugate Roots :\n");
  printf("x1,2 = %f +- %f i \n",real,complex);
 }


}

반응형
반응형

http://cpu815.blog.me/110035113430


 Balanced Binary Search Tree(ex : Red-Black Tree) 
역시 매우 중요한 자료구조균형 잡힌(!) 이진 탐색 트리는 추가삭제검색을 모두 로그 시간 O(logn)에 수행할 수 있는 매우 우수한 자료구조이다비록 해쉬 테이블 상수 시간에 이루어지지는 않지만,탐색 트리의 장점은 키 값들이 정렬이 되어있다는 것이다따라서 위의 전화번호부 예에서 사람 이름에 대해서 소팅을 많이 해야 한다면이진 트리를 사용해야 한다.

이진 트리는 아래와 같이 생긴 자료구조이다그러나 최악의 경우 모든 원소가 한 쪽에만 달려있다면,이는 리스트와 전혀 다름이 없다.

 

 

 

 

 

 

따라서양쪽 노드의 개수를 "적절히분배를 해주는 것이 가장 중요하다만약 똑같이 분배가 되어있다면탐색 및 연산 작업이 로그 시간에 수행될 수 있다최대 트리의 높이만큼 순회하면 되므로 log 시간이 걸리는 것이다이렇게 연산 시간을 로그 시간이 될 수 있도록 보장하는 이진 트리를 Balanced Binary Search Tree라고 부른다여기에는 정말로 많은 자료구조가 있으며 해야 할 얘기도 무척 많다그러나 대표적인 Red-Black Tree만 얘기해보자.

 

 

 

 

그림에서 보듯이각각의 노드에 빨강검정색의 속성을 추가한다그리고 빨간 노드의 자식은 모두 검정 노드여야 한다와 같은 여러 속성을 정하고삽입/추가할 때 마다 이 속성이 유지되도록 보정을 해준다. (: rotation) 그래서 노드가 균형을 유지하도록 한다Red-Black Tree는 양쪽 노드의 깊이가 서로 두배 이상 차이가 나지 않음을 보장한다

반면AVL Tree라고 불리는 녀석은 양쪽 노드의 길이가 최대 하나 밖에 차이가 나지 않도록 한다.훨씬 더 엄격하게 균형을 맞춘다따라서 당연히 이 균형을 맞추는 보정 작업에 소요되는 시간이 Red-Black Tree보다 크다.

키와 데이터 쌍을 저장할 때검색이 가장 우선 순위라면 당연히 해쉬 테이블을 사용해야 할 것이다.그러나 이 키 값들이 정렬이 되고 싶다면 Red-Black Tree가 가장 좋은 자료구조이다.
 


정리

형태

순서여부

색인여부

삽입/삭제 

검색속도

중복여부

List

Yes

No

Fast (constant time)

Slow O(n)

Yes

Array

Yes

By int (constant time)

Slow O(n) except if inserting at end, in which case constant time

Slow O(n)

Yes

(Hash)Map

No

By key (constant time)

Fast (constant time)

Fast (constant time)

No (keys) Yes (values)

Red-Black

Yes

By key O(log n)

Fast O(log n)

Fast O(log n)

No

 

[출처] Red-Black Tree|작성자 카푸치노

반응형
반응형

[출처] : http://myhome.hanafos.com/~kukdas/doc/cpp/c_use-13.html


---------- 반올림 함수 1 -----------

#include <math.h> 
double round(double x)
/* 반올림을 해주는 함수,음수일 경우 반내림을 한다. */
{
   return ((x>0) ? floor(x+.5) : ceil(x-.5));
}

(설명)

C에는 특별히 반올림을 해주는 함수가 없다.
그러나 아주 간단하게 반올림을 할 수 있는 방법이 있다.
즉, x가 양수인 경우에 floor(x+0.5) 라고 해주면
반올림이 저절로 된다.
그러나 음수에서는 반내림(-1.9 => -2.0)을 하는 것이
오히려 상식적이므로 반올림을 하는 함수를 새로 제작.
ceil()은 올림을 하는 함수이고, floor()는 내림을 하는
함수이다.
단순히 return floor(x+.5) 또는
return (double)(int)(x+.5) 라고 하면 양수일 때만
원하는 결과를 가져온다.
반드시 math.h를 include 해주어야 한다.

---------- 반올림 함수 2 -----------

#include <math.h>

double round2(double a, int b)
/* 소수점 아래자리에서도 반올림 해주는 함수 */
/* a-반올림 할 수, b-반올림할 소수점 자리  */
{
   a*=pow10(b-1);
   a=(a>0) ?  floor(a+0.5) : ceil(a-0.5);
   a*=pow10(-(b-1));
   return a;
}

(설명)

앞서의 round() 함수는 소수점 아래자리에서는
반올림할 수가 없었다.
이것을 pow10() 함수를 써서 매개변수에 소수점 정보를
추가한 것이다.
그래서 b를 그 소수점 정보로 하여 원하는 위치로
10의 배수를 곱하여, 첫째자리에서 반올림을 한후
다시 원상복귀시킨 것이다.

   ex) p=round2(3.141592, 3); 
         => p에는 3.140000 이 저장된다.
 

반응형
반응형

http://blog.daum.net/broodsc/2714333



오늘은 스택을 이용한 CALC 유틸리티(이하 CALC)에 관해서 설명(??)하겠습니다. ㅋ

CALC는 그냥 계산기 정도로 생각하시면 될듯...

CALC 유틸리티

수식을 중위표기법(Infix notation)으로 받아서 그걸 후위표기법(Postfix notation)으로 바꾸고...

이 후외 표기법의 수식을 스택을 이용하여 연산하여서 화면에 출력하게 하는 것...

이게 이 프로그램의 작동방식(??) 입니다. ;;;

여기서도 제한을 둡시다.

이 CALC는 간단한 정수와 사칙연산만 가능하게 한다는것.

그리고 입력시 스페이스를 넣으면 원하지 않으면 결과가 나올 수도 있다는 것.

뭐 다른 기능을 포함시키는것도 이 소스를 바탕으로 개선하시면 될듯 합니다.

방법 )

1. 수식의 표기법

보통 우리가 사용하는 수식은 뭘까요??

2 + 3 * (1 + 2) 이런 수식이죠??

이런걸 중위표기법(Infix notation)이라고 한답니다.

이유는?? 연산자가 두 피연산자 사이에 있어서... ;;;

1 + 2 를 보면 피연산자 1, 2 사이에 연산자 '+' 가 있죠?? ㅋ

그럼 연산자가 뒤에나 앞에 올 수도 있을까요?? 정답은 "그렇다" 입니다.

1 + 2 를 전위표기법(Prefix notation)으로 고치면 + 1 2 가 되겠고...

후위표기법(Postfix notation)으로 고치면 1 2 + ㅇㅋ??

그렇다면 왜 중위표기법을 놔두고 후위표기법을 이용하여 계산을 할려고 하는지??

그 이유가 궁금하지 않으신가요?? ;;;

그 이유는 중위표기법은 우리가 이해하기는 쉽지만 괄호를 만드시 써야하는 단점이 있다는 ;;;

위에보면 2 + 3 * (1 + 2) 이 식 있죠??

이 식에서 괄호를 제외한다면... 2 + 3 * 1 + 2 ...

두개의 수식이 서로 같나요?? 다르죠??

그럼 후위표기법은 괄호 없이도 가능하다는 말인가?? 정답은 "그렇다" 입니다. ㅋ

2. 중위표기법을 후위표기법으로 변환하는 방법 1

우선 뭄풀기(쫌 힘들지만 ;;) 단계로... 이해부터 하기 위해서...

한가지 제한을 두고 합시다.

뭐냐하면... 수식을 쓸때 무조건 괄호를 씌우는걸로...

예를들면 2+3*2 같은 경우에도 (2+(3*2)) 이런 식으로...

그럼 이걸 어떻게 후위표기법으로 변환할까요??

1. '(' 문자는 무시하고 넘어간다.

2. 피연산자는 그대로 출력한다.

3. 연산자는 스택에 푸시한다.

4. ')' 를 만나면 스택에서 팝하여 출력한다.

예를 들어 봅시다. (A + (B * C)) 라는 식...

'('는 무시한다고 그랬으니깐... 넘어가고 그 다음 'A'

'A'는 여기서 피연산자죠?? 그렇다면 출력. ( 출력 : A | 스택 : )

그 다음 '+' 이건 연산자... 그러니깐 푸시 ( 출력 : A | 스택 : + )

다음은 '(' ... 이건 무시.

그 다음 'B' 피연산자니깐 출력 ( 출력 : A B | 스택 : + )

다음은 '*' 연산자니깐 푸시 ( 출력 : A B | 스택 : * + )

그 다음 'C' 피연산자이므로 출력 ( 출력 : A B C | 스택 : * + )

그 다음 ')' ...

')' 만나면 스택에서 팝하여 출력이니깐... (출력 : A B C * | 스택 : + )

그 다음 또 ')'를 만났으니깐... 팝 하여 출력 (출력 : A B C * + | 스택 : )

그럼 (A + (B * C)) 의 후위표기법은 A B C * + 라는 결과가 나왔죠??

이제 이걸 가지고 C로 만들어 봅시다.

void postfix1(char *dst, char *src)

{

char c;

init_stack(); /* 스택을 초기화 */

while(*src)

{

if(*src==')') /* ')'를 만나면 팝하여 출력(dst에 저장) */

{

*dst++=pop();

*dst++=' '; /* 문자의 구분을 위해 */

src++;

}

else if(*src=='+' || *src=='-' || *src=='*' || *src=='/')

{ /* 연산자이면 스택에 푸시 */

push(*src);

src++;

}

else if(*src>='0' && *src<='9')

{ /* 숫자는 피연산자. 숫자를 그대로 복사 */

do

{

*dst++=*src++;

} while(*src>='0' && *src<=9');

*dst++=' ';

}

else /* 그 외에는 무시 */

src++;

}

*dst=NULL; /* 마지막에 NULL 문자 추가해서 문자열로... */

}

이건 쫌 쉽죠?? ;;;

3. 중위표기법을 후위표기법으로 변환하는 방법 2

위에 방법은 괄호가 꼭 있어야 했습니다.

그렇다면 이제 괄호가 없어도 우선순위에 맞춰서 변환하는걸 해 봐야겠죠??

이게 실제적으로 더 편리하고 유용한거니깐...

일단 우선순위를 고려해 줘야하니깐... 연산자별로 우선순위를 줍시다.

'(' 는 0, '+' 와 '-' 는 1, '*' 와 '/' 는 2 ...

1. '(' 를 만나면 스택에 푸시한다.

2. ')' 를 만나면 스택에서 '(' 가 나올 때까지 팝하여 출력하고 '(' 는 팝하여 버린다.

3. 연산자를 만나면 스택에서 그 연산자보다 낮은 우선순위의 연산자를 만날 때까지 팝하여 출력한 뒤에 자신을 푸시한다.

4. 피연산자는 그대로 출력한다.

5. 모든 입력이 끝나면 스택에 있는 연산자들을 모두 팝하여 출력한다.

후... 역시 이 책의 최대 약점이 또 다시 느껴지는군요...

직접 만들어볼 틈을 주기전에 이런 알고리즘을 제공하니깐... ;;;

보고 또 보고 해서 우리걸로 만들면 되겠죠?? 전 그렇게 할 수 있다고 스스로 믿을겁니다. ^^

예를 들어 봅시다.

(2 * (3 + (6 / 2) + 2) / 4 + 3 ... 차례대로... (그림 눌러서 보세요. 3자가 잘 안보이네요 ㅋ)

휴~

이걸 가지고 함수를 만들기 전에...!!

몇가지 간단한 함수부터 만들고 넘어갑시다. ^^

먼저...

int get_stack_top(void)

{

return (top < 0) ? -1 : stack[top];

}

이 함수는 스택의 최상단값을 리턴해주는 겁니다.

푸시될 연산자보다 높은 순위의 연산자가 스택의 상단에 있는지 없는지 보기 위해서...

그 다음 주어진 문자가 연산자인지 아닌지 판별하는 함수

int is_operator(int k)

{

return (k == '+' || k == '-' || k == '*' || k == '/');

}

두개 다 간단하죠??

그 다음... 연산자 우선순위를 수치로 바꿔주는 함수

int precedence(int op)

{

if (op == '(') return 0;

if (op == '+' || op == '-') return 1;

if (op == '*' || op == '/') return 2;

else

return 3;

}

흠... 이제 이 세 함수를 사용해서... 중위표기법 → 후위표기법 함수를 만들어 볼까요?? ;;;

void postfix(char *dst, char *src)

{

init_stack(); /* 스택 초기화 */

while (*src)

{

if (*src == '(') /* ( 를 만나면 푸시 */

{

push(*src);

src++;

}

else if (*src == ')') /* ) 를 만나면 ( 가 나올 때까지 팝 */

{

while (get_stack_top() != '(')

{

*dst++ = pop();

*dst++ = ' ';

}

pop();

src++;

}

else if (is_operator(*src)) /* 연산자이면 */

{

while (!is_stack_empty() &&

precedence(get_stack_top()) >= precedence(*src))

{ /* 우선순위가 높은 연산자들을 모두 팝 */

*dst++ = pop();

*dst++ = ' ';

}

push(*src); /* 그리고 푸시 */

src++;

}

else if (*src >= '0' && *src <= '9') /* 피연산자는 그냥 출력 */

{

do

{

*dst++ = *src++;

} while (*src >= '0' && *src <= '9');

*dst++ = ' ';

}

else

src++;

}

while (!is_stack_empty()) /* 모두 끝났으면 스택에 있는 모든 내용을 팝 */

{

*dst++ = pop();

*dst++ = ' ';

}

dst--;

*dst = 0;

}

4. 후위표기법 수식의 평가

후위 표기법 계산을 나타내는건 의외(??)로 쉽습니다.

1. 숫자를 만나면 스택에 푸시

2. 연산자를 만나면 두번 팝해서 그 두 데이터를 가지고 연산한 다음 다시 스택에 푸시

쉽... 죠?? 그러리라 믿습니다. ㅋ

예를들어 2 + 3 * 4 ... 이걸 후위표기법으로 나타내면 2 3 4 * +

2 3 4 는 숫자이므로 차례대로 푸시 ( 스택 : 4 3 2 )

그 다음 * ... '*' 는 연산자니깐 팝 두번하면 4 3 이거 두개를 * 연산해주면 12

12을 다시 푸시 ( 스택 : 12 2 )

그 다음 + ... '+' 는 연산자니깐 팝 두번하면 12 2 이거 두개를 + 연산하면 14 끝 !!

간단하죠??

단 한가지만 조심하면 됩니다. '-' 와 '/' 연산...

'+' 나 '*' 는 교환법칙이 성립하므로 팝 두번하여 그냥 계산하면 됩니다.

그러나 '-' 또는 '/' 는 교환법칙이 성립안합니다. 그러므로 팝을 따로 해주어야합니다.

그냥 pop() - pop() 를 하면 앞에 팝이 먼저 실행될지 뒤에 팝이 먼지 실행될지 모르기때문에...

그래서 팝을 한번 해서 저장해 둔 뒤에 그 값과 다음에 팝 하는 값을 계산해 주어야함.

그럼 이제 계산하는 함수를 만들어 볼까요??

int calc(char *p)

{

int i;

init_stack();

while (*p)

{

if (*p >= '0' && *p <= '9')

{

i = 0;

do

{

i = i * 10 + *p - '0';

p++;

} while (*p >= '0' && *p <= '9');

push(i);

}

else if (*p == '+')

{

push(pop() + pop());

p++;

}

else if (*p == '*')

{

push(pop() * pop());

p++;

}

else if (*p == '-')

{

i = pop();

push(pop() - i);

p++;

}

else if (*p == '/')

{

i = pop();

push(pop() / i);

p++;

}

else

p++;

}

return pop();

}

한가지 눈여겨 볼것 더... 바로 숫자열을 정수로 변환하는 것...

atoi() 라는 아시는 분이라면 쉽게 이해하실텐데...

int atoi(char *s)

{

int i=0;

while(*s>='0' && *s<='9')

i = i * 10 + *s++ - '0';

return i;

}

이게 바로 atoi() 함수입니다. 보시고 쪼끔만 생각하시면 이해하실듯...

결과 )

실행은

"CALC(파일이름) 수식" 하면 됩니다.

단, 스페이스를 사이에 집어 넣으면 원치 않은 결과가 나오니깐...

(공백이 있다면 argc 3으로 컴퓨터가 이해하니깐 ;;;) 조심~

반응형
반응형

DAG 공정 모델링( 공정 흐름 - 순서적인 걸 따질때 )

Reachabilitiy : 도달 가능성(방향 그래프에서 어느정점에서 어느 정점까지 도달 할 수 있는 지를 보는 것)

Transitive Closure : 우회적으로 어느 정점에서 어느 정점까지 도달 할 수 있는 것을 보느 것

결국 두개는 모든 정점에 대해서 해본다

강한결합( Strongly Connected Components) 방향성 있는 순환 노드 = 사이클을 찾을때, 특징 강한결합으로 이루어진 노드중 하나를 하나 제거해도

ex A,B,C 가 강한 결합인데 A 를 제거해도 B 와 C 는 연결 된다

위상 정렬(Topological sort) -AOV Network( 비선형 구조를 선형 구조로 탐색 하는 방법 )=흐름에 따른 여러 공정 모델링을 한사람이 처리 하는 정렬방법

(A처리 된다음 B 가 처리 되고 B 처리 된다음 C,D 가 동시에 처리 될 수 있고 C 다음 F 가 처리 되고 의 흐름을 선형적으로 한사람이 처리 하는 과정으로

정렬 하는 방법)

역위상 정렬(Reverse Topological sort)

위상정렬의 반대로 처리 하는 것 A->B->C, D 의 순이 위상이라면 C,D -> B ->A 순으로 처리 하는 것

위상정렬에서 AOE Network 이 나오는데 AOE Network 으로 간선에 작업을 놓고 간선에 비용을 쓰는 걸 이용해서 작업이 처리되는 소요 비용 , 시간

등을 알 수 있고 , 임계영역(=임계경로Critical Activity) 를 구할 수 있다 = 작업이 모두 끝나는 시간(= 가장 오래 걸리는 경로)

임계영역을 찾음으로써 임계영역의 처리 시간들을 줄이게 되면 공정의 작업 시간을 줄일 수 있다

반응형
반응형

A,B,C,D 작업을 간선으로 하여 공정을 의미하게 하는 것 AOV Network

[AOE Network]

C라는 작업은 A 가 걸처와야 작업이 가능하고

B라는 작업도 A 가 걸처와야 작업이 가능하다

D 라는 작업은 B,C 가 거처 와야지만 가능하다

AOE 처럼 하게 되다 보면 비용이 0(더미 엣지) 인 간선이 생길 수 있다

그런데 c,d 를 제거 하면 되지 안느냐 라는 의문이 생길 수 있는데

제거 하게 되면 b 와 e 의 중간 에 있는 작업들이 구분이 되지 않게 된다 => 어떤 작업이 끝나는지 명확히 구분하기 힘들다

그래서 중간에 비용이 0 인 더미 간선을 둔다 그러면서 c,d 를 두게 된다

그런데 c 는 없고 d 만 존재해도 가능한 형태이다

* b,e 사이에 C 가 있고 d,e 사이에 c 가 있으므로 구문이 가능하다


A ,B 중간에 있는 숫자가 소요 시간

전체 공정이 완료되는 시간은 최대 비용을 찾으면 된다

A->B->C->D->E->I->K->L->M 이 최대 비용 이 작업을 임계 작업이라 한다

이 작업들이 작업들이 실제로 작업 공정에 전체 완료 일자에 큰 영향을 미치므로

=> 이 공정에서 시간을 줄이면 새로운 임계경로가 잡히게 된다

즉 무엇을 줄여야 임계작업을 줄일 수 있는지를 알아 보는것이 AOV NetWork 이다


위의 아래 그래프에서는 위로 가는 쪽이 임계작업(임계경로)이다 (9)

최장시간, 최단시간은 C++ 알고리즘 참고

최장, 최단 시간은 각 노드에서 걸리는 최장, 최단 시간을 말한다

반응형
반응형

Acyclic(에싸이클릭) = 비순환

즉 강한결합요소가 없는 그래프이다

(강한결합요소 = 순환)

왼쪽 그림 순환, 오른쪽 그림 비순환

[공정모델링]

오른쪽 그림에서 A 가 완료 되어야 E,D,C,B 를 할 수 있고

B 와 D 가 완료 되어야 C 를 할 수 있다

F는 D 가 완료 되어야 할 수 있다

왼쪽 그래프는 공정모델링이 안된다 순환됨으로 개념적으로 끝나는 부분이 없다

선행작업 : 먼저 이루어져야 하는 작업

왼쪽 처럼 표를 만들고 나면 오른쪽 처럼 그래프로 만들 수 있다

D,G,L 작업이 끝나야만 모든 작업이 끝날 수 있다

시작은 A 하나

(D,E,F,G) 등이 동시에 공정이 진행 될수 있는 부분

(H,I,J)

위와 같은 그래프를 AOV Network 이라 한다


위상정렬 : 동시에 하지 않고 혼자서 어떤 순서로 공정을 거처야 하는가? 를 결정하는 것

비선형 모양을 순서대로 나열 하는 것

ex)G는 마지막이니 제일 나중에 해도 된다

indegree : 공정의 시작( 내차수 )

outdegree : 공정의 끝나는 지점들 ( 외차수 )

[위상정렬]

하는 법은 A 를 접근한 후 B 의 내차수는 1 이니 A->B 로 연결되는 간선을 없애고 B 의 내차수 는 들어오는 선이 1 이니 이것을 0 으로 만들어서

B 는 이제 위의 공정에서 시작점이 된다 (반복)

C 를 지울때 C 와 연결된 간선을 모두 지운다

그러면 D,E,F,G 들은 내차수가 0 이 됨으로 D,E,F,G 를 큐에 넣는다

D 를 큐에서 빼내고 D를 방문한 다음에 D 를 지우고

E를 큐에서 빼고 방문한다음 E 와 인접한 K 와의 간선을 지운다 하지만 K 의 내차수 0 이 아님으로 아직 큐에 넣지 않는다

그다음 F 가 없어지면 연결된 노드들의 차수는 0 이 되고 이 노드들을 큐에 넣고, 위 과정 반복...

[큐에서 나올때 방문한다]

[내차수가 0 이 되면 큐에 넣는다]

[역위상]

외차수가 0 인점을 선택해서 위상의 반대로 실행해준다

위상정렬을 위해 큐나, 스택을 사용하면 된다

[큐에서 나올때 방문한다]

[외차수가 0 이 되면 큐에 넣는다]

반응형
반응형

 방향그래프 , 깊이우선탐색 Warshall 알고리즘

강한결합요소 찾기 문제

위상정렬/ 역위상정렬

AOE Network

Network 방향그래프 이면서 간선의 가중치가 있는 그래프


방향그래프 : 간선에 방향성이 한방향으로만 연결되어 있는 그래프

(Tip : 무방향그래프의 간선은 양방향 간선이다)

내차수(In-degree) : 정점으로 들어오는 간선수

외차수(Out-degree) 정점에서 나가는 간선수


도달가능성(Reachability)

한 정점에서 다른 모든 정점을 방문할 수 있는 건 아니다(방향성이 있기 때문)

(깊이우선 탐색 : 모든 노드를 탐색 하는 방법)

그래서 깊이우선 탐색으로 시작점을 줄 수 있는 깊이우선 탐색으로 조금 변경한 후

해당 노드에서 인접노드를 스택에 넣고, 스택에 넣었던 노드중 하나를 꺼내어서 꺼낸 노드를 방문하고

이 노드의 인접 노드를 다시 스택에 넣고 또다시 방문하고를 바복하면서

방향성으로 갈 수 있는 노드를 따라 가다보면 시작하려고 했던 노드에서의 방향성이 어디까지 접근 할 수 있는지

도달가능범위를 알 수 있다


Transitive closure( 이행적 폐쇄 )

어떤 정점 A 에서 C 로 가는 방향에 대해서 직접 경로는 없지만, 우회 경로가 있을때 A->C 로 의 간선을 연결한 그래프( 방향으로 갈 수 있을 때 )


Warshall 알고리즘

그리고 A->A 도 가능하기에 배열 테이블의 대각선 값은 1 이 된다

A->B 와 B-C 를 만족하는 경로가 있으면 A->C로 가는 경로도 존재한다

그래서 A->C 로 접근 가능하다를 구현한 것 => 배열 테이블에서 A->C 로 가는 값을 세팅해준다

A->B 로 가는 값이 1로 세팅이 되어 있고 B 에서 C 로 가는 값에도 1 로 세팅 되어 있으면

A에서 C 로 가는 값을 1 로 세팅해준다( 접근 가능경로로 만든다 ) => warshall

그다음 반복...

위에서 A->C 로 가는 값이 1 이므로

C->F 로 가는 값이 1 이고 F->D 로 가는 값 또한 1 이라면 C->D 로 가는 값을 1 로 세팅해준다

[C++ 알고리즘 참고]


[강한결합요소]

: 정점 A 에서 B로 가는 방향성 경로도 존재하고 B 에서 A로가는 방향성경로도 존재

( 사이클로 이루어지고 한쪽 방향으로 도는 경우를 말함 )

- 강한결합요소는 정점 하나를 제거해도 연결은 유지된다

( 강한결합요소의 경우 정점 하나를 제거해도 나머지 노드들끼리는 연결되어 있다는 것 )

중요한 부분에서 는 강현 결합요소로 연결해주면 좋다

VS

이중연결(biconnectivity) : 한쪽 말고도 다른쪽으로도 연결 되어 있는 구조

[강한결합요소 찾기]

왼쪽 트리의 간선에 비용을 매겨서 깊이우선 탐색으로 모든 노드 방향에 대해 탐색 하는 경로를 얻은 후

오른 쪽에는 깊우우선 탐색으로 얻은 것을 먼저 그린후( 빨간선 제외)

모든 노드를 연결하는 경로 외의 선(오른쪽 그림에서 붉은 선) 에 대해서

왼쪽 방향대로 연결해주었을때 오른쪽 그림의 가장 하위 정점에서 자기자신 을 가르키게 되면 그것이 강한결합이 된다

(각 노드에서 빨간색을 타고 올라갔을때 1의 노드(비트리간선으로 자기자신)가 다시 나오면 강한결합이 된다)

오른쪽 그림에는 두가지의 강한결합을 보여주고 있다

빨간색선 : 난트리엣지( = 방문하지 안은 엣지)

파랑색 : 트리엣지 ( = 방문한 엣지)

1. A->D->F->E ->A , A 로 시작해서 가장 하위로 갔을때 그것이 자기자신을 가르키는 경우를 강한 요소라 한다

2. G->J->I ->G

반응형
반응형


가중그래프에 최소비용신장트리, 최단경로 찾기 등이 있다

최소비용신장트리(MCST) 에서의 우선순위 탐색은 비용 자체이지만

최단경로찾기의 우선순위탐색은 누적된 간선 비용값이 우선순위가 된다

반응형
반응형

[point]

해당 노드에서 직접 바라보는 부모노드와의 거리보다 다른 연결로 들어온 경로의 거리가 더 적다면

적은 거리로 온 노드를 부모로 바라본다


시작 A

모든 노드는 A 를 가르키고 (parent)

직접 가르키지 못하는 노드들은 무한대로 거리를 설정한다

1.A 를 처음 방문하고 방문하지 않은 최소 비용으로 연결된 노드에 대해서 탐색을 시작한다

2. 그래서 처음 C 를 방문하게 된다

(노드를 방문하지 않은 것중에 distance 가 적은 것의 노드를 방문한다 )

3. C 를 살펴보면 C 는 D 와 연결 되어 있고 D 는 A 를 가르키고 있다

그런데 A에서 C 그리고 C 에서 D 로 가는 비용이 3 이고 A 에서 D 로 가는 비용이 2 총 3 이기 때문에

D 가 더 빨라 D 가 가르키는 부모와 distance를 바꾸지 않아도 된다

[반복..] 인접한 것과 방문은 다른 것

4. 노드를 방문하지 않은 것중에 distance 가 적은 것의 노드를 방문한다 (D)

5. D 와 인접한 G 와 F 노드가 있는데

먼저 F 를 보면 F의 비용은 무한대 이기 때문에 D 에서 F 로 누적된 6 만큼 이동 하는 것이 더 빠르기 때문에

(F A 무한대) 를 (F D 6) 으로 바꿔준다

G 도 무한대 비용인데, D 에서 G 로 가는 누적된 6 이 더 작음으로

(G A 6) 을 ( G D 6 ) 으로 바꿔준다 (이렇게 parent 를 세팅해나간다)

6. 다시 노드를 방문하지 않은 것중에 distance 가 적은 것의 노드를 방문한다 이번에는 (E) 가 선택 된다

반복...

결과

최종적으로 Vertex 가 바라보는 Parent 로 연결되는 선을 연결해주면 위 그림처럼 나오고

최단 경로는 시작점 A 에서부터 모든 점에 대한 최단 거리를 연결해 놓은 형태가 됨으로

A 가 아닌 어떤 노드에서 부모를 따라 가면 어떤노드에서 A 까지 가는 최단 경로가 나온다

반응형
반응형

MCST [Minimum Cost Spanning Tree]

신장트리(Spanning Tree) : 모든 정점(노드)을 방문하는 트리

트리 : 사이클이 없는 그래프를 말한다

그런데 간선에 비용이 있다 => 모든 정점을 방문하면서도 간선의 비용의 합이 최소인 트리를 신장트리라 한다

MCST = Minimum cost spanning tree

최소비용 신장트리로 알려진 알고리즘 들에는

1. 우선순위 탐색

2. Kruskal

3. Sollin

4. Prim

etc...

[1,2 가 성능이 좋다]


크루스칼 알고리즘 - 최소비용 신장트리( 사이클 없이 모든 노드를 연결하기 )

모든 노드를 사이클 없이 가장 적은 비용의 가선으로 연결 하는 알고리즘

[집합 Union-Find 알고리즘이 도입됨]

노드를 집합으로 보고 집합의 포함이 되어 있는지로 판별하면서 간선을 연결 한다

*모든 노드와 간선은 연결되어 있는 상황이고

*간선에는 비용이 있다

간선의 비용이 가장 적은 간선에 연결된 두개의 노드를 각각의 집합으로 만든다

그다음 순서의 간선을 선택 한다음 두개의 노드 모두가 기존 집합(각 한그룹에 두개가 모두)에 포함되어 있는 상태라면

경로가 사이클이 생김으로 간선을 연결하지 않는다, 기존 집합에 포함 하지도 않음(이미 조재 함으로)

자세한 내용은 C++ 알고리즘 참고

반응형
반응형

Find - 어떤 원소가 어떤 집합에 포함되는지 아닌지를 판별

Union - 서로 다른 두개의 집합을 하나의 집합으로 합병

=> 상향식 트리구조로 구현 하는 것이 효율적이다

상향식트리구조 : 위에서 아래로 내려오는 구조가 아닌 아래에서 위로 올라가는 구조

일반적으로 알고 있는 하향식 트리구조는 부모가 자식들만큼의 포인터 개수를 가지고 있어야 하지만

상향식은 자식이 부모를 가르키는 구조이므로 각 노드는 하나의 포인터만 가지고 있으면 된다

그러므로 어느 집합에 포함된 노드의 parent 를 계속 따라가다보면 최종 root 인 어느 집합인지를 알 수 있게 된다

(각 root 집합 명)

반응형
반응형

MCST [Minimum Cost Spanning Tree]

신장트리(Spanning Tree) : 모든 정점(노드)을 방문하는 트리


트리 : 사이클이 없는 그래프를 말한다

그런데 간선에 비용이 있다 => 모든 정점을 방문하면서도 간선의 비용의 합이 최소인 트리를 신장트리라 한다

MCST = Minimum cost spanning tree


최소비용 신장트리로 알려진 알고리즘 들에는

1. 우선순위 탐색

2. Kruskal

3. Sollin

4. Prim

etc...

[1,2 가 성능이 좋다]


최소비용신장트리 - 우순순위 검색 에 의한 방법

( PrioityQueue 가 필요 ( PrioityQueue 는 힙으로 만든 것이 적절하다 Heap 은 O(logN)의 성능을 갖는다) )

* 비용을 우선순위로 둔다

모든 노드를 연결하는데 가장 최소의 비용으로 모든 노드를 연결하는 방법

간선 자체를 우선순위로 보고 비교한다

[C++ 알고리즘 참고]


[최단경로찾기 ]

A 부터 Z 까지 가는 길 중에 각 길마다 비용이 있고 가장 적은 비용으로 Z 까지 가는 비용을 얻기 위한 방법

* 누적으로 최단 경로를 구한다

먼저

출발점이 A 라면 A 와 인접한 것들을 우선순위 큐에 넣고 ( 이때 우선순위 큐에는 가작 작은 값이 최 상단에 위치하게 한다)

p 큐 가장 위에 있는 것을 뽑아와서 선을 연결 한다

뽑아온 노드의 인접 노드를 다시 p큐에 넣는다

이때!! 큐에 넣기전 현재 도착 노드가 p큐에 있는지 보고 p큐에 도착 노드가 있다면 이제까지 누적해온 경로의 길이를

비교해봐서 p큐보다 값이 작다면 기존 p큐에 있는 노드를 지우고 누적값이 적은 노드값으로 대체한다

- 최소 비용으로 모든 정점을 연결하는 방법

반응형
반응형

insert : 삽입을 하는 동작에서만의 시간

remove : 삭제를 하는 동작에서만의 시간

O(1) = 상수시간

[ArraySequenceMap] = 배열 => 검색이 O(N) 이 걸리는 이유는 첫번째 부터 찾을때까지 각 배열에 대해 맞는 것을 찾으려니 선형적으로 걸린다

[ListSequenceMap] = 리스트

BinMap(Binary) =이분검색

=> 트리를 이용하지 않는다!!!

=> 자료집합은 정렬 되어 있어야 한다, 정렬이 되어 있는 상태에서만 작동 가능

=> 삽입될때 만약 배열에 자료들에 배열에 들어가 있는 상태라면 정렬된 상태를 유지하기 위해서 들어갈 이후의 값들이 모두 뒤로 이동 되어야 한다

itpMap(interpolation) = 비례식에 의한 찾기 방식으로 검색은 자료가 선형적으로 있을때 빠른 속도를 보인다 , 수치자료에서만 사용 가능

하지만 O(loglogN) 삽입삭제는 역시 느리다

BinTreeMap(바이너리트리맵)

=> 이진 트리를 이용한 검색방법이다

=> 균형이 맞으면 O(logN) 의 성능을 보이지만 삽입시 균형이 맞지 않으면 한쪽으로 치우치는 최악인 경우 O(N) 의 성능을 보인다

=> chain 은 중복처리를 리스트에서 하기 때문에 저장공간이 자료수보다 큰 필요는 없다

[HashMap,ChainMap 은 상수 시간에 찾기 삽입, 지우기가 끝난다]

=> 관건은 해쉬 펑션을 어떻게 정의 하느냐 이고, 보통 Hash 는 입력 자료수의 2배 만큼의 공간으로 잡을때 성능이 좋다

HashMap(정적인 상황에 적합)

ChainMap (연결 법) => 해쉬 테이블에 이중리스트로 연결하여 데이터를 추가( 동적인 상황에 적합) , 최신 데이터를 리스트의 앞에 넣어줌으로서

노드의 뒤쪽으로 이동하는 시간을 줄일 수 있다

(=>chainmap 은 중복된 값을 리스트에 넣는 것이므로 값을 최근 헤드에 연결된 것을 리턴해 주면 됨으로 검색도 상수시간이다)

[radix tree,radix trie 둘다 자료가 비트로 표현될 수 있어야 한다]

이진트리에서의 한쪽으로 치우치는 현상을 방지된 구조가 된다 => 비트에 의해 좌,우가 결정 됨으로

그래서 검색,삽입,삭제 가 O(logN) 의 성능을 보인다

Radix Tree => 모든 노드가 정보노드

Radix Trie => 정보노드는 leaf 에, 내부(중간)노드는 분기노드

[B-Tree : 하나의 노드에 여러개의 자료가 들어 갈 수 있으면서 균형을 맞추는 구조]

O(logN) 의 성능을 보인다

[RB-Tree : 벨런슬르 자동으로 맞춰주면서 이진트리 구조의 형태를 지닌다]

find 도 O(logN) 이며 insert ,remove 도 O(logN) 이지만 실제 다른 logN 의 성능을 가진 것보다 상당히 빠른 속도를 보인다

검색 : 이진트리 검색과 동일

삽입 : 칼라상향변환 + 회전

삭제 : 칼라하향변환 + 회전

으로 이루어진다

[모든 벨런스 트리는 기본적으로 중복이 불가능하다]

chain, 이나 hash 가 가장 빠르다

반응형
반응형

Red Black Tree 의 특징 : 2-3-4 트리를 Binary Tree 로 표현 한것

(2-3-4 트리는 3차 B-Tree 와 돌아가는 것이 동일하다)

먼저 2-3-4 트리를 알아야 하는데

노드의 자식이 2,3,4 개가 존재 할 수 있다고 하여 2-3-4 트리이다

A 를 가지고 있는 노드는 2개의 자식을 갖는다

A B 를 가지고 있는 노드는 3개의 자식을 갖는다

A B C 를 가지고 있는 노드는 4개의 자식을 갖는다

Red Black 트리는?

2-3-4 트리를 이진트리로 나타낸 것

그림에서 2노드 인 경우 A 를 검정색 A 노드로 표현

3노드 인 경우 B 를 빨강으로 내리거나 A 를 빨강으로 내려 표현

4노드의 경우 B 를 제외한 양쪽을 빨강으로 내려 표현

Red-Black 트리에서는 이렇게 자식으로 내리지만 2-3-4 트리에서는 하나의 노드로써 결합의 의미를 갖는다

2-3-4 트리를 보면 B-Tree 의 형태 임을 알 수 있다( 동작도 동일하다 )

즉 B-Tree 를 이진 트리 형태로 바꾼것!!!


이진트리와 동일하게 부모노드의 왼쪽 서브트리보다 크고 오른쪽 서브트리보다 작다

Root 에서 Leaf 로 가는 경로의 검정노드의 수는 모두 같다 => B-Tree 에서 빨강이 자식으로 확장된 형태이므로

원래 B 트리는 모든 자식의 레벨이 같다, 그래서 확장된 빨강 노드를 제외하면 루트에서 자식까지 가는 검정노드의 수는 갖게 된다


색변환 (color flip)

상향 색변환 : 빨강을 위로 올림 => 검정색의 자식의 두개의 빨강 노드일경우 부모를 빨강으로 바꾸고 두 자식을 검정색으로 바꾸는 것

하향 색변환 : 위에 있는 빨강을 자식을 빨강으로 바꾸는 것 -> 부모는 검정이 된다 (상향 색변환과 반대)

회전 : 이진트리의 규칙을 깨지 않고 트리를 재구성 하는 방법(우회전, 좌회전) => 좌우의 트리 높이를 맞추는 방향으로 회전함=>AVL Tree 의 기본 동작

이렇게 분할 하는 이유는 자료를 삽입하기 위함 ( 트리의 균형을 깨지 않고 )

(노드안에 자료가 3개면 => 4노드)

위 그림이 상향식 변환 ( 위의 그림에서 녹색 부분이 B-Tree 의 설명 아래 있는 것이 RB-Tree )

루트는 원래 검정이라 빨간색으로 자식을 내리건 안내리건 간에 Root 는 검정색이된다

루트가 검정색


pivot 은 회전의 축이 된다

회전하면서 자식들의 위치는 이진트리의 부모의 왼쪽은 작고 오른쪽은 큰 순서를 유지하기 위한 순서대로 구성해준다


- 트리에서는 노트의 끝을 노드로 표현하지 않고 0 로 표현 할 수 있다

- RedBlack 을 표현하기 우해 bool red; 로 표현 한다

- RB 트리도 기본적으로 중복을 허용하지 않는다( list 등으로 노드에서 중복을 허용하는 구조로 만들 순 있다)

[RB-Tree 검색]

검색 알고리즘은 이진 트리와 동일하다( 키 값으로 비교하여 왼쪽, 또는 오른쪽으로 이동 )


[RB-Tree 삽입]

single rotation, double rotation 으로 이루어진다

구조는 이진트리와 동일하기 대문에 노드 순서 규칙은 동일

균형을 맞추기 위해 색변환 -> 노드 분할, 결합등을 하게 된다

=> 이때 빨간노드가 연속되는 상황이 벌어질 수 있는데 이때의 균형을 맞추기 위해 Rotation 을 한다

=> 자료를 삽입할때 부모가 빨간노드이면 Rotation 을 한번 한 후 삽입 한다

right rotation, left rotation => single rotation

right rotation 한다음 left rotation , left rotation 한다음 right rotation => double rotation

[Single rotation]

1. 상향색 조정을 했을때 빨간 노드가 연달아 나타나게 되는 상황이 발생되면

2. 회전을 하면서 노드의 색깔이 변경되고, 그럼으로서 노드의 균형을 맞춘다

회전시키기 위해선 t, p, gp(부모의 선조), ggp 의 4개가 있어야 한다

그림상 B-Tree 부분 에서(각 이미지의 아래에 그려진 트리) 노드안의 자료가 3개일때 분할하는 모습을 볼 수 있다(이것이 rotation 의 회전과 동일)


[double rotation]

left-right rotation 그림중에서 1번 을 보면 single rotation 에서와는 달리 두개 연달아 나오는 레드 노드 위로의 선이 반대인 것을 볼 수 있는데

이때는 두번 회전으로 노드를 정리해줘야 한다 => double rotation

1.상향 색조정을 아래에서 한번 하게 되면 레드 노드가 연달아 나오게 된다

2.Right-left rotation 에서 보면 C 를 pivot(중심)으로 해서 한번 회전 하면 선이 펴지게 된다 => 색변환 일어나지 않는다

3. 그다음 C 가 회전 되어진다 => single rotation 처럼 회전 => 색변환이 일어난다


[키값에 의한 회전]

왼쪽 상단 그림을 보면 G 를 키 값으로 주고 E 에서 G 쪽으로 향하는 방향으로 회전을 시켜주고

3번째 그림에서는 E 값을 키 값으로 놓고 E 에서 C 의 방향으로 회전을 하게 된다

pivot, child, gchild 는 아직 정해지지 않은 각 노드 포인터

*회전 호출시 pivot 을 정하고 넘겨준다

gchild 를 키값으로 하여 gchild 에서 child 의 방향으로 회전

회전의 방향 = 키값(노드안의 데이터)

회전의 위치 = pivot( pivot 의 왼쪽 또는 오른쪽 노드에 의해서 child 가 결정된다 )

child 와 키 값에 의해서 gchild 가 결정되면서 회전하게 된다

pivot 과 키값(gchild)의 비교에 의해서 child 가 가르킬 노드를 정해준다

child 와 gchild 의 위치관계를 pivot 과 키값에 의해 따져서 회전시켜준다

if (key > child->data || key == child->data)

Rotate left

else

rotate right

// key == child->data 일때도 오른쪽인 이유는 나중에 내부노드 삭제를 할때 루트보다 바로 큰 노드와 바꿔치기 하여 루트를 삭제 하기 위한

처리와 맞춰주기 위함 즉 삭제시 루트에서 오른쪽으로 한번간 후 왼쪽으로 계속가서 루트보다 바로 큰 노드를 찾기 위한 오른쪽 탐색을

하는 처리와 맞춰주기 위함이다

끝난 후 변견된 트리의 pivot 의 자식을 설정해 주어야 하는데

gchild 가 상단으로 올라감으로 gchild 를 pivot 의 자식으로 주어야 한다

그런데 왼쪽으로 줄것인지 오른쪽으로 줄것인지를 결정하기 위하여

key 와 pivot->data 의 비교로 위치를 결정한다


[insert ]

삽입시 자식노드 둘다 레드이면 칼라상향조정

칼라 조정후 부모가 레드면 회전

회전하려고 할때 노드 연결이 단방향이 아니고 꺽여 있으다면 double rotation

t,p,gp,ggp 의 관계를 살펴본다

if( p->red ){

if( value > gp-data != value > p->data )

rotation(value, gp);

t= rotation(value,ggp);

t->Red=true;

}

마지막 하단에 노드를 삽입할때도 그때의 부모가 레드면 로테이션( or double rotation) 후 삽입

회전이 다 된후 노드가 맞는 위치에 삽입된다

그래서 좌우 트리가 두배 이하로 유지가 된다


[delete]

C++ 알고리즘 참고

delete 에서 color demotion 이 나온다, 왜냐하면 삭제시 노드가 아에 없어지면 자식을 갖지 못하는 상화이 벌어 질 수 있으므로

쉽게 위해 하기 위해서는 B-Tree 로 전 후를 만들어 본 다음 만든 트리를 Red-Black 트리로 변행해 본다음

바뀐것들으 알아보는 것


[Red-Black 트리 분석]

트리의 높이

black height => black 노드로 이루어진 레벨의 개수

둘다 black 노드의 높이는 2 이다 , black height

black height 는 2 인데 개수는 왼쪽이 3개, 오른쪽이 15개

N+1 = 노드개수(3)+1 은

s^B <= N+1 <= 4^B (B = black height)

왼쪽은 H(Height) = B(Black Height) = 2

오른쪽은 H(Height) = 2B(Black height) = 4

O(logN) 의 성능을 보인다 => 성능이 좋다

반응형
반응형

시스템들이 내부적으로 사용하는 검색 구조( 데이타베이스, 오라클 등등 => 인덱스 검색 구조이다)

외부검색에 적절한 형태 => 하드디스크 Tap 등 외부 장치의 검색에 잘 사용됨

내부검색 => 램 같은 내부적인 부분

이진트리 문제점 : 좌우 균형이 맞지 않으면 비효율적이다

트리의 성능은 트리의 높이에 있다

그래서 트리의 높이를 낮춰주면 성능이 올라갈 수 있다

그래서 트리의 좌우 균형을 맞춰 주는 것이 중요하다

균형트리 : 삽입 삭제시 필요하면 스스로 균형을 유지하는 트리

AVL, 2-3(-4), Red-Black Tree-BTree....

-삽입은 split (분할) 동작 으로 삽입


Balanced Tree : 스스로 균형을 유지하는 트리

A 를 찾기 위해서 균형이 안맞으면 느릴 수 있다

균형이 안맞으면 링크드 리스트처럼 검색성능을 보인다

위 그림은 트리를 회전함으로써 이진트리의 규칙을 유지하면서 트리의 높이를 낮추는 방법을 보여준다

RB Tree & B Tree 의 방법에 AVL Tree 가 포함된다


하나의 노드에 여러개의 노드가 들어올 수 있다

M 차 => 한 노드에 자료가 가장 많이 들어간 수의 차수를 말한다

이 그림에서 M = 5차 > v,w,x,y,z

B - Tree 규칙.1

1. 노드의 자료수가 N 이라면 , 그 노드의 자식의 수는 N+1 이어야 한다 => left, right 가 있어야 하므로

자료수 = (노드 안에 있는 자료의 개수)

2. 각 노드의 자료는 정렬된 상태여야 한다 --> 방향으로

3. 노드의 자료 D_k 의 왼쪽 서브트리는 D_k 보다 작은 값들이고

D_k 의 오른쪽 서브트리는 D_k 보다 큰 값들이어야한다

( 이진트리에서 부모보다 왼쪽은 작은 트리, 오른쪽은 부모보다 큰 트리를 말한다 )

ex) 그림에서 C의 왼쪽은 C 보다 작은 A,B 이고, D,E 는 C 보다는 큰지만 F 보다는 작은 자료이다

B - Tree 규칙.2

1. Root 노도는 적어도 2개 이상의 자식을 가져야 한다.

2. Root 노드를 제외한 모든 노드는 적어도 floor(M/2) [<= 내림] 개의 자료를 가지고 있어야 한다.

( 위 그림에서는 노드들이 모두 적어도 2개 이상의 자료를 가지고 있다

3. 외부 노드(leaf node) 로 가는 경로의 길이는 모두 같다

4. 입력자료는 중복될 수 없다.( list 등을 같이 쓰면 중복을 허용 할 수도 있다 )


B-Tree 구조

시작을 나타내는 노드가 있는데 헤드에 대한 포인트 노드만 사용하고 끝은 0 로 처리 해서 만든다

끝을 NULL 로 처리 하는 것이 가능

[노드 안의 글자가 '키' 를 의미한다]

B-Tree 검색

루트에서부터 노드의 자료들과 찾을 값을 비교하여 노드가 찾을 것보다 작으면 왼쪽 크면 오른쪽으로 이동하면서 찾아간다

-노드내에서는 순차검색, 전체적으로는 트리검색


B-Tree 삽입

1.자료는 항상 leaf 노드에 추가된다 (leaf 노드의 선택은 삽입될 키의 하향 탐색에 의해 결정)

2. 추가될 leaf 노드에 여유가 있다면 그냥 삽입

3. 추가될 leaf 노드에 여유가 없다면 '분할' 하기

4. 삽입할 때 같은 키 값이 있으면 안됨으로 해당 노드에 삽입하기 전에 같은 값이 있는지 find 해봐야 한다

[루트노드 분할]

위 그림은 왼쪽은 Root(뿌리) 노드일경우에 가능한 경우이다

H 를 넣으려고 할때 노드를 3개로 분할 한 후 H 를 넣는 모습

Root 노드이기 때문에 floor(M/2) 와 관계 없이 G 가 하나로 존재 가능하다 G 가 루트가 되는 것

[일반노드 분할]

위 그림의 오른쪽이 일반노드에 대한 분할인데

만약 A B C D E 노드를 C 를 중심으로 분할 한다면 C 를 부모노드로 올려주고 C 를 기준으로

A B 를 왼쪽 D E 를 C 의 오른쪽 노드로 배치시켜서 분할 한다

(M 이 홀수 인 경우로 가정 =>

현재 차수 M = 5 로 가정 하였으므로 5/2=2 2개씩 나누어야 반씩 나눠 짐으로 왼쪽 두개,

오른쪽 두개 로 나눈다 3번째가 부모로 올라가는 형태 )

이때 부모로 올려야 하는 문제가 있는데 그것은 부모가 꽉찬 노드 일경우에 발생한다

이때는 부모를 분할 한 후 그다음 원래 분할 하려고 했던 노드를 분할 한다

분할 => split

* 삽입할때 현재 중간 노드들이 풀이면 M(차수) 만큼 풀이면 분할을 하고 그 위치에서 다시 삽입할 위치를 찾아간다 => 미리 분할 해 준다

- 마지막 leaf 노드까지 가서 마지막 leaf 노드가 풀이면 분할 후 삽입한다


[ 외부노드(leaf node) B-Tree 삭제]

삭제하고자 하는 자료가 있는 노드에 자료를 삭제 후 자료수가 M/2 이상이 되도록 보장해야 함

루트를 제외한 모든 노드는 M/2 보다 자료수가 많아야 한다, 이것이 보장 되어야 B-Tree 이다

이것의 해결 방법이 형제에게서 빌리기 VS 형제와 결합하기 이다

1. 빌리기 : 형제가 M/2 보다 맣은 자료를 가지고 있다면 빌려온다

2. 결합하기 : 형제에게서 빌릴 수 없다면=>M/2 보다 작다면 합친다

[형제에게서 빌리기] - Borrow key => 회전

왼쪽 그림은 B 를 삭제 하려고 하는데 A 하나 밖에 자료가 안남게 된다

그래서 D 를 빌려 올려고 하지만 D 를 A 노드 쪽으로 가져오면 부모인 C 와 순서(왼쪽이 작은쪽) 이 맞지 안으므로

D(형제) 를 부모 에게 주고 C 를 A 노드 쪽으로 주게 되면 => [자료를 돌리는 식]

B 를 삭제 할 수 있게 된다( 오타 : 그림상에서 D K 는 D G K 이다 )

이렇게 B Tree 의 규칙을 어기지 않고도 Balance 조정이 가능하다

[형제와 결합하기] - BindNode => 중간이 되는 부모 자료와 같이 결합하기

오른쪽 그림은 결합하기인데

B 를 삭제하기위해서 보면 A 가 있는 노드의 자료가 하나만 남기 때문에 그냥 지워선 안되고

D를 빌려오자니 D 와 함께 있는 E 가 하나만 남게 되어 빌려오는 상황도 여의치 안다

그래서 이때는 결합하기로 가야한다

먼저 A B 와 D E 중간에 위치한 부모의 C 를 아래로 내려서 그림과 같이 A B C D E 로 합친다 그 후 B 를 제거


[ 내부노드(leaf node가 아닌 ) B-Tree 삭제]

1.

i 를 삭제 하려고 할때 I 를 제거 하면 i 를 삭제하고 재 구성할 수 없으므로 대채하는 것으로 가야한다 => 키 순서상 재구성이 힘들다

이진트리와 동일하게 i 보다 바로 작은값 또는 i 보다 바로 큰 값으로 대채한 후 대채한 값을 삭제 하면 된다

Right Subtree 중 가장 작은 값인 J 로 i 대신 대채한 후 j 를 다시 삭제 하러 가면 된다

이때 j 를 삭제하기 위해 내려갈때 삭제시 M/2 이하인 노드는 형제에게서 빌리든지, 결합을 해야 한다

=> 삭제를 할때 부족한 노드가 있으면 안됨으로 balance 를 유지하기 위해

2. bindNode 를 할때 오른쪽 노드를 왼쪽으로 결합 하면 오른쪽 노드는 없어지게 되고 부모노드에 있는 자료중 하나가 아래려 내려 오게

되는데 이때 부모가 root 이고 부모의 자료 개수가 0 개가 된다면 합쳐진 왼쪽 노드를 부모노드로 해주면 된다

3. swap 루트에서 오른쪽으로 한번 이둥후 왼쪽으로 계속이동하면 I 보다 바로 큰 값을 찾을 수 있다

그 후 대채할 키를 다시 루트에서부터 삭제하는과정을 거치면 된다


이진트리의 경우 레벨(높이) 는 (log_2 N)+1 이하 이다 이때 밑이 2 인 것은 이진트리에서 자식은 2개씩 나갈수 있기 때문

B-Tree 의 경우 자식은 최소 M/2 개 이상이어야 함으로 밑이 M/2 이다

삽입/삭제/검색 성능 complexity 를 말할때는 굳이 밑을 쓰진 않는다 O(logN) 으로 성능이 좋다

-이진트리의 경우 평균 시간 복잡도가 nlogn 이며 최악의 경우 n 의 시간 복잡도를 갖는다

이를 보안할 것이 AVL-Tree, Red-Black Tree , B-Tree , Splay Tree 등이 있다

-최악의 경우가 없다

-차수가 높을 경우 노드내에서 순차검색대신 이분검색을 사용 하는 것도 속도를 높일 수 있는 방법이다

-모든 leaf 가 같은 레벨에 위치한다


tip : AVL Tree = leaf node 의 차이를 2 이하로 구성하는 트리 삽입시 이를 위반하면 자동 재구성한다

splay tree = 가장 최근에 사용된(삽입 삭제 검색) 노드를 루트로 항상 유지하는 트리이다

스케줄 알고리즘 등에서 LRU(Least Recently Used => 페이지교체 알고리즘) 같은 알고리즘을 구현하는데 적합

반응형
반응형

이진트리 형태로 구성

왼쪽노드는 키가 0 오른쪽 노드는 키가 1

서브트리는 루트의 왼쪽 하위는 0 오른쪽은 1 => 이진검색트리 특성과 유사하다

-각 트리의 레벨의 수치에 대한 비트수치를 적용

-왼쪽부터 오른쪽으로 비트가 증가하는 형태

Degenerated case = 한쪽으로 트리가 쏠리는 현상

같은 키를 넣으면 허용 않함

검색할때 최 하위 비트부터 동일한 비트인쪽으로찾아 검색해간다

즉 비트가 검색키이다

노드 삭제는 이진검색트리와 동일하게 삭제

삽입은 레벨에 따른 비트 검사를 통해 leaf 로 이동하여 leaf 에 추가한다

하위비트부터 살펴봐서 비트가 0이면 왼쪽으로 그다음 비트가 또 0 이면 다시 왼쪽으로 그다음 비트가 1 이면 오른쪽으로 이동


분기노드 = 네모 점 데이터는 없다

비트에 따라서 좌우를 판단하는 것은 동일하다

그래서 자료의 입력 순서와는 무관하다

키의중복은 허용되지 않는다( 중복을 허용하려면 링크드리스트를 이용 )

트리의 높이는 log_2(8) + 1 = 4, [8 leaf 노드 수]

자료는 2^M 개 입력될 수 있다, [m 은 키의 비트 개수 = 3]

trie : 키 비교를 한번만 한다 => 키비교가 복잡할때 사용되면 유용


삽입은 이전 값과 차이나는 비트까지만 분기를 하면서 노드를 하단에 위치하게 한다

차이가 나는 비트의 노드가 들어올때 분기 노드를 만들면서 노드는 가장 하위에 유지되도록 한다

C++ 알고리즘 참고


트리의 높이만큼 비교, 검색 하기 때문에 O(logN) 의 성능을 가진다

성능이 좋다

Radix Tree => 모든 노드가 정보노드

Radix Trie => 정보노드는 leaf 에, 내부(중간)노드는 분기노드

반응형
반응형




key 를 통해서 원하는 레코드를 가져온다

알고리즘 성능은 삽입,검색,삭제를 다 따져야 판단할 수 있다

array 는 무순서로 처음부터 끝까지 비교해가면서 찾아내는 방법

bin 은 자료가 들어올때 재 정렬 한다 => 재 정렬할때 삽입삭제는 느리지만 검색은 그만큼 빠르다

검색 알고리즘은 검색이 관건

- 이분검색의 경우 삽입할때 정렬해야 하기때문에 O(N) 삭제는 자식을 땡겨와야 하기 때문에 O(N)

검색은 좋은 성능인 lonN 을 보인다

내분검색의 검색은 loglogN 으로 성능이 좀 더 좋지만 실제 어느것이 빠를지는 해봐야 안다

결론적으로 순차검색 array, list 는 O(N) 으로 시간이 오래 걸린다 => 적은 자료집합에서는 사용할만지만

많아질경우 이분,내분으로 접근한다

반응형

+ Recent posts