알고리즘

문자열 검색 알고리즘

25G 2023. 1. 25. 19:46

알고리즘은 일을 진행하는 스탭, 절차 등이라고 생각하면 이해하기가 쉽다.

완전 탐색 알고리즘

1.텍스트의 맨 앞부터 패턴을 비교 조회합니다.
2.만약 문자와 패턴이 일치하지 않으면, 패턴을 오른쪽으로 1칸씩옮긴다.
3.패턴이 배열을 벗어날 때 까지 이 처리를 반복한다
시간복잡도 : 최선일 경우 O(n)

function searchString(text, pattern) {
let i = 0;
while (i <= text.length - pattern.length) { //패턴이 텍스트의 길이를 넘지 않는 동안
let p = 0; //p가 패턴의 맨 앞 위치를 가리키도록 한다
while (p < pattern.length) { //패턴의 길이를 넘지 않는 동안
if (text[i + p] != pattern[p]) { //텍스트와 패턴 문자가 일치하지 않으면
break; //반복을 종료한다
}
p = p + 1; //패턴의 비교 위치를 오른쪽으로 1칸 옮긴다
}
if (p === pattern.length) { //패턴의 모든 문자가 텍스트와 일치하면
return i; //텍스트와 일치한 시작 위치를 결과로 반환하고 종료한다
}
i = i + 1; //텍스트의 위치를 오른쪾으로 1칸 옮긴다
}
return -1; //-1을 결과로 반환하고 종료한다
}

이동테이블을 활용하는 kmp 알고리즘

  • 커누스 모리스 프랫 알고리즘

완전 탐색 알고리즘은 텍스트와 패턴이 일치하지 않으면, 텍스트를 기준으로 패턴의 위치를 오른쪽으로 1칸 옮긴 다음에 패턴의 첫 번째 문자부터 다시 검색합니다. 즉 완전 탐색 알고리즘은 비교하는 도중에 얻은 정보를 버린다는 뜻.

kmp 알고리즘은 패턴을 오른쪽으로 한칸씩 옮기지 않습니다. 비교하는 도중에 얻은 정보를 활용해 패턴의 문자까지 텍스트와 일치했는지 확인한 후 텍스트를 기준으로 패턴의 위치를 몇칸 옮길지 정합니다.
패턴에 포함된 각 문자와 텍스트가 일치하지 않을때 패턴을 몇 칸 옮기는 것이 최선인지 조사하고 그 결과를 테이블로 만듭니다. 이 테이블을 이동 테이블이라고 부름, 검색할때는 이동테이블을 바탕으로 패턴을 몇칸 옮겨야 할지 정합니다.

  • 시간 복잡도 : On이지만 실질적으로 이동테이블을 만들기위한 전처리가 시간이 걸려 완전 탐색 알고리즘이 더 효율적일 수도 있다.
std::size_t KMP::FindString(std::string pattern, std::size_t startIndex)
{
    if (false == CheckString(pattern, startIndex))
        return std::string::npos;

    std::string haystack = this->haystack.substr(startIndex);

    std::vector<std::size_t> skipInfo(pattern.size(), 0);
    std::size_t index = 0;
    for (int i = 0; i < pattern.size(); ++i) // i : incorrect index when haystack and pattern are compared
    {
        for (int j = 0; j < i - 1; ++j) // j : index for finding the longest border in equivalent prefix of haystack and pattern
        {
            for (index = 0; index <= j; ++index)
                if (pattern[index] != pattern[(std::size_t)i - 1 - j + index])
                    break;
            if (index > j)
                skipInfo[i] = (std::size_t)i - (j + 1);
        }
        if (0 == skipInfo[i])
            skipInfo[i] = 2 > i ? 1 : i;
    }

    std::size_t i = 0; // i : beginning index of comparison
    while (i <= haystack.size() - pattern.size())
    {
        for (index = 0; index < pattern.size(); ++index)
            if (haystack[i + index] != pattern[index])
                break;
        if (pattern.size() == index)
            return startIndex + i;
        i += skipInfo[index];
    }

    return std::string::npos;
}

보이어 무어 알고리즘

완전 탐색 알고리즘 및 kmp 알고리즘은 패턴의 첫번째 문자부터 비교. 그러나 보이어 무어 알고리즘은 패턴의 마지막 문자부터 비교. 일치하지 않을 경우에는 일지하지 않은 문자를 기준으로 패턴을 몇칸 옮길지 결정한다.

  • 패턴의 마지막 문자부터 비교
  • 패턴의 마지막 문자가 일치하지 않으면 전체 패턴이 일치하지 않는다는 뜻
  • 패턴의 위치를 패턴의 길이 만큼 오른쪽으로 옮긴다.

하지만 무작정 패턴 길이 만큼 오른쪽으로 옮기면 중간부터 시작되는 패턴을 빠뜨릴 수 있으므로, 보이어 무어 알고리즘 이동테이블을 이용해서 패턴에 있는문자가 패턴의 마지막 문자를 기준으로 했을때 몇번째 위치에 있는지를 기록
패턴에 있는 문자가 패턴의 마지막 문자를 기준으로 몇번째 위치에 있는지기록. 그 외의 문자는 패턴속 문자의 갯수만큼 이동하도록 테이블에 기록한다.
시간복잡도 : 최악일때 O(n)
평균 : O(n/m)

ex)

function doSearch() {
    var inputForm = document.forms.inputForm;
    var text = inputForm.text.value;
    var pattern = inputForm.pattern.value;
    var i;
    i = searchString(text, pattern);
    if (i >= 0) {
     alert(i + " 번째 문자에서 찾았습니다.");
    } else {
     alert("찾을 수 없습니다.");
    }
   }
   // 함수 searchString(text, pattern)
   //   입력: text 검색 대상 문자열, pattern 검색 문자열(키워드)
   //   출력: 0 이상 찾은 위치, -1 찾지 못했을 때
   function searchString(text, pattern) {   
    var i;
    i = text.indexOf(pattern);
    return i;
   }

Rabin Karp

Rabin Karp 방식은 문자의 아스키코드 값을 이용한 해시를 사용한다.
본문에서 차례대로 패턴 크기만큼 문자열의 해시값을 구하고 미리 구해놓은 패턴의 해시값을 비교해 본다. 이 알고리즘의 핵심은 문자들의 아스키코드 값을 가지고 어떻게 해시값을 만들어내느냐가 관건.