데이터 구조 및 알고리즘(5): 알고리즘 특수 Hash, BitMap, Set, Bloom 필터, 중국어 단어 분할, Lucene 반전 인덱스

알고리즘 특수 Hash, BitMap, Set, Bloom 필터, 중국어 단어 분할, Lucene 반전 인덱스

해시시

생각하다:

  1. N(1<N<10)개의 자연수를 주고 각 수의 범위는 (1~100)입니다. 이제 어떤 숫자가 이 N 숫자 안에 있는지 가장 빠른 속도로 판단하고 패키지 클래스를 사용해서는 안 됩니다.
  2. N(1<N<10)개의 자연수를 주고 각 수의 범위는 (1~10000000000)입니다. 이제 특정 숫자가 이 N 숫자 내에 있는지 여부를 가장 빠른 속도로 판단하고 패키지 클래스를 사용하지 않아야 하며 이를 달성하는 방법은 무엇입니까? A[] = 새로운 정수[N+1]?

해시 테이블

해시 테이블은 우리가 흔히 해시 테이블이라고 부르는 것을 영어로 Hash Table이라고 합니다. 많이 들어보셨을 텐데요.

해시 테이블은 배열이 첨자에 따라 데이터에 대한 임의 액세스를 지원하는 기능을 사용합니다. 散列表其实就是数组的一种扩展,由数组演化而来。배열이 없으면 해시 테이블이 없다고 할 수 있습니다.

사실 이 예제는 이미 해싱이라는 개념을 사용했습니다. 이 예제에서 N은 자연수이고 배열의 첨자와 일대일 매핑을 형성하므로 배열의 첨자를 기반으로 하는 랜덤 액세스 기능을 사용합니다.

검색의 시간 복잡도는 O(1)로, 시퀀스에 요소가 존재하는지 여부를 빠르게 확인할 수 있습니다.

해시 충돌

개방형 주소 지정: 개방형 주소 지정 방법의 핵심 아이디어는 해시 충돌이 있는 경우 빈 위치를 다시 감지하여 삽입한다는 것입니다.

해시 테이블에 데이터를 삽입할 때 특정 데이터가 해시 함수에 의해 해시되고 저장 위치가 이미 점유된 경우 현재 위치에서 시작하여 하나씩 역방향으로 검색하여 비어 있는 위치가 있는지 확인합니다. 그것..

결점:

  1. 삭제에는 특별한 처리가 필요합니다.
  2. 너무 많은 데이터가 삽입되면 해시 테이블에서 많은 충돌이 발생하고 검색이 순회로 퇴화될 수 있습니다.

링크 주소: 링크드 리스트 사용, 링크드 리스트 방법은 더 일반적으로 사용되는 해시 충돌 해결 방법이며 개방 주소 지정 방법보다 훨씬 간단합니다.

HashMap 해시 충돌의 연결 목록 최적화

연결된 목록의 구조에는 몇 가지 단점이 있으므로 JDK에서 최적화되었으며 보다 효율적인 데이터 구조가 도입되었습니다.

레드 블랙 트리

  1. 초기 크기: HashMap의 기본 초기 크기는 16입니다. 이 기본값은 설정할 수 있습니다. 대략적인 데이터 양을 미리 알고 있으면 기본 초기 크기를 수정하여 동적 확장 횟수를 줄일 수 있으므로 성능이 크게 향상됩니다. HashMap의.

  2. 동적 확장: 기본적으로 최대 로딩 계수는 0.75이며 HashMap의 요소 수가 초과되면 0.75*capacity(용량은 해시 테이블의 용량을 나타냄) 확장이 시작되고 각 확장은 원래 크기의 두 배로 확장됩니다.

  3. 해시 충돌 해결: JDK1.7은 하단의 연결 목록 방식을 사용합니다. JDK1.8 버전에서는 HashMap을 더욱 최적화하기 위해 red-black 트리를 도입했습니다. 그리고 링크드 리스트의 길이가 너무 길면(디폴트는 8초과) 링크드 리스트는 레드-블랙 트리로 변환된다. Red-Black 트리의 특성을 이용하여 빠르게 추가, 삭제, 수정 및 확인하여 HashMap의 성능을 향상시킬 수 있습니다. Red-Black 트리 노드의 수가 8보다 작으면 Red-Black 트리는 연결 리스트로 변환됩니다. 적은 양의 데이터의 경우 red-black tree는 균형을 유지해야 하며 연결 ​​목록에 비해 성능 이점이 명확하지 않기 때문입니다.

int hash(Object key) {
    
    
 int h = key.hashCode()return (h ^ (h >>> 16)) & (capitity -1); //capicity表示散列表的大小,最好使用2的整数倍
}

효율적인 엔터프라이즈급 해시 테이블 설계

여기에서 HashMap의 디자인 아이디어에서 배울 수 있습니다.

  1. 효율적이어야 합니다. 즉, 삽입, 삭제 및 조회가 빨라야 합니다.
  2. 메모리: 너무 많은 메모리를 차지하지 말고, B+Tree, HashMap 10억, 하드 디스크 저장 알고리즘: mysql B+tree와 같은 다른 구조를 사용하는 것을 고려하십시오.
  3. 해시 함수: 실제 상황에 따라 고려해야 합니다.%
  4. 확장: 데이터의 크기를 추정하는 것으로 HashMap의 기본 공간은 16? 10000개의 번호를 저장해야 한다는 것을 알고 있습니다.2^n > 10000 or 2^n-1
  5. 해시 충돌을 해결하는 방법: 연결 목록 배열

해시 애플리케이션

  1. 암호화: MD5 해시 알고리즘, 되돌릴 수 없음
  2. 비디오가 반복되는지 여부 결정
  3. 유사성 감지
  4. 로드 밸런싱
  5. 분산된 하위 데이터베이스 및 하위 테이블
  6. 분산 저장
  7. 조회 알고리즘 HashMap

멀티스레딩에서 해시 확장 알고리즘의 문제점

  1. 다중 스레드 넣기 작업, get은 무한 루프()입니다. 예를 들어 용량을 확장할 때 공유 어레이를 사용하는 대신 새 어레이를 엽니다.
  2. 다중 스레드 넣기로 값 가져오기 오류가 발생할 수 있음

무한 루프가 있는 이유는 무엇입니까?

  1. 해시 충돌이 있는 경우 충돌하는 값을 저장하기 위해 체인 구조를 사용합니다. 1->2->3->null과 같이 연결된 목록 자체를 순회하는 경우
  2. 3 자체로 순회하면 null이 되어야 하는데 이때 누군가가 이 null의 값을 null => 1->3으로 계산하면 끝이다. , 그리고 이제 다시 변경됩니다. Cheng은 1을 가리키고 이 1은 3을 가리키므로 계속 반복됩니다.

비트맵

장면

  • 데이터 가중치 판단
  • 데이터를 반복하지 않고 정렬

결점

  • 데이터는 반복할 수 없습니다
  • 데이터 양이 적을 때 이점이 없습니다.
  • 문자열 해시 충돌을 처리할 수 없습니다.

생각하기: 3억 개의 정수(0~2억) 중 어떤 숫자가 존재하는지 판단하는 방법은? 메모리 제한 500M, 하나의 시스템

  • 나누다:
  • 블룸 필터: 아티팩트
  • 레디스
  • Hash: 3억개 오픈, HashMap(2억개)?
  • 배열: 연령 질문, 데이터[200000000]?
  • 비트: 비트맵, 비트맵;

유형 기준: 컴퓨팅에서 가장 작은 메모리 단위는 비트이며 0, 1만 나타낼 수 있습니다.

  • 1바이트 = 8비트
  • 1int = 4바이트 32비트
  • 플로트 = 4바이트 32비트
  • 롱=8바이트 64비트
  • 문자 2바이트 16비트

Int a = 1, 이 1은 계산에 어떻게 저장됩니까?
0000 0000 0000 0000 0000 0000 0000 0001 toBinaryString (1) =1

운영자 기반

왼쪽으로 이동<<

  • 8 << 2 = 8 * (2 ** 2) = 8 * 4 = 32
  • 8 << 1 = 8 * (2 ** 1) = 8 * 2 = 16
    0000 0000 0000 0000 0000 0000 0000 1000
    << 2
    0000 0000 0000 0000 0000 0000 0010 0000

오른쪽으로 이동>>

  • 8 >> 2 = 8 / (2 ** 2) = 8 / 4 = 2
  • 8 >> 1 = 8 / (2 ** 1) = 8 / 2 = 4
    0000 0000 0000 0000 0000 0000 0000 1000
    >> 2
    0000 0000 0000 0000 0000 0000 0000 0010

비트와 &

  • 8 & 7
    0000 0000 0000 0000 0000 0000 0000 1000 = 8
    &
    0000 0000 0000 0000 0000 0000 0000 0111 = 7
    =
    0000 0000 0000 0000 0000 0000 0000 0000 = 0

비트 또는 |

  • 8 | 7
    0000 0000 0000 0000 0000 0000 0000 1000 = 8
    &
    0000 0000 0000 0000 0000 0000 0000 0111 = 7
    =
    0000 0000 0000 0000 0000 0000 0000 1111 = 2 3 + 2 2 + 2 1 + 2 0 = 8+4+2+1=15

비트맵

int는 32비트를 차지합니다. 32비트 각각의 값으로 숫자를 표현한다면 32개의 숫자를 표현할 수 있을까, 즉 32개의 숫자는 int가 차지하는 공간만 있으면 되고 순식간에 할 수 있다. 32회

예를 들어 가장 큰 수의 N{2, 3, 64}가 있다고 가정하면 데이터를 저장하기 위해 int 배열 MAX만 열면 됩니다 . 자세한 내용은 다음 구조를 참조하세요.int[MAX/32+1]

Int a : 0000 0000 0000 0000 0000 0000 0000 0000여기에 32개의 위치가 있습니다. 각 위치에 0 또는 1을 사용하여 이 위치에 숫자가 있는지 여부를 나타낼 수 있으므로 다음 저장 구조를 얻을 수 있습니다.

Data[0]: 0~31 32位
Data[1]: 32~63 32位
Data[2]: 64~95 32位
Data[MAX / 32+1]
/**
 * 解决:
 * 1. 数据去重
 * 2. 对没有重复的数据排序
 * 3. 根据1和2扩展其他应用,比如不重复的数,统计数据
 * 缺点: 数据不能重复、字符串hash冲突、数据跨多比较大的也不适合
 *
 * 求解2亿个数,判断某个数是否存在
 *
 * 最大的数是64  data[64/32] = 3
 * data[0] 0000 0000 0000 0000 0000 0000 0000 0000  0~31
 * data[1] 0000 0000 0000 0000 0000 0000 0000 0000  32~63
 * data[2] 0000 0000 0000 0000 0000 0000 0000 0000  64~95
 *
 *  数字 2 65 2亿分别位置
 *  2/32=0 说明放在data[0]数组位置, 2%32=2说明放在数组第3个位置
 *  65/32=2 说明放在data[2]数组位置, 65%32=1说明放在数组第2个位置
 *
 *  2亿=M
 *  开2亿个数组 2亿*4(byte) / 1024 /1024 = 762M
 *  如果用BitMap: 2亿*4(byte)/32 / 1024 /1024 = 762/32 = 23M  (int数组)
 *  判断66是否存在 66/32=2 -> 66%32=2 -> data[2]里面找第三个位置是否是1
 */
public class BitMap {
    
    

    byte[] bits; //如果是byte那就是只能存8个数
    int max; //最大的那个数

    public BitMap(int max) {
    
    
        this.max = max;
        this.bits = new byte[(max >> 3) + 1]; // max / 8 + 1
    }

    public void add(int n) {
    
     //添加数字
        int bitsIndex = n >> 3; // 除8就可以知道在哪一个byte
        int loc = n % 8; // n % 8 与运算可以标识求余 n & 8

        //接下来要把bit数组里面bitsIndex下标的byte里面第loc个bit位置设置为1
        // 0000 0100
        // 0000 1000
        // 或运算(|)
        // 0000 1100
        bits[bitsIndex] |= 1 << loc;
    }

    public boolean find(int n) {
    
    
        int bitsIndex = n >> 3;
        int loc = n % 8;

        int flag = bits[bitsIndex] & (1 << loc);

        if (flag == 0) {
    
    
            return false; //不存在
        } else {
    
    
            return true; //存在
        }
    }


    public static void main(String[] args) {
    
    
        BitMap bitMap = new BitMap(100);
        bitMap.add(2);
        bitMap.add(3);
        bitMap.add(65);
        bitMap.add(64);
        bitMap.add(99);

        System.out.println(bitMap.find(2));
        System.out.println(bitMap.find(5));
        System.out.println(bitMap.find(64));
    }
}

세트

다양한 용기 비교

  • List:
    객체를 반복적으로 저장할 수 있음
    삽입 순서와 순회 순서가 일치 공통
    구현 방법: 연결 목록 + 배열(ArrayList, LinkedList, Vector)
  • 설정:
    중복 개체는 허용되지 않으며,
    각 요소의 삽입 및 출력 순서는 보장할 수 없으며, 순서가 없는 컨테이너입니다.
    TreeSet은 정렬되고 일반적으로
    사용되는 구현 방법: HashSet, TreeSet, LinkedHashSet(출력 순서와 삽입 순서가 일관되도록 강제됨, 이중 연결 목록, 공간 소비)
  • 맵:
    맵은 키-값 쌍의 형태로 저장되며 키+값이 있을 것입니다:
    맵은 동일한 키가 나타나는 것을 허용하지 않으며 맵 시퀀스를 덮어씁니다
    . 기본 구조도 레드-블랙 트리입니다. )

블룸 필터(없으면 없어야 함)

구현된 아이디어:

  • 삽입: K개의 해시 함수를 사용하여 삽입된 요소에 대해 k개의 계산을 수행하고, 획득한 해시 값에 해당하는 비트 배열의 첨자를 1로 설정합니다.
  • 검색: 삽입과 동일하며 k 함수를 사용하여 검색된 요소에 대해 k 계산을 수행하고 얻은 값에 해당하는 비트 배열 첨자를 찾아 1인지 여부를 판단합니다. 모두 1이면 값이 가능함을 의미합니다. 시퀀스에 있어야 합니다. 그렇지 않으면 확실히 시퀀스에 없습니다.

순차적으로 가능한 이유는? 오탐율이 있습니다

  • 삭제: 이 항목은 삭제를 지원하지 않음을 매우 명확하게 알려줍니다.
/**
 * 应用场景 -- 允许一定误差率 0.1%
 * 1. 爬虫
 * 2. 缓存击穿 小数据量用hash或id可以用bitmap
 * 3. 垃圾邮件过滤
 * 4. 秒杀系统
 * 5. hbase.get
 */
public class BloomFilter {
    
    

    private int size;

    private BitSet bitSet;

    public BloomFilter(int size) {
    
    
        this.size = size;
        bitSet = new BitSet(size);
    }

    public void add(String key) {
    
    
        bitSet.set(hash_1(key), true);
        bitSet.set(hash_2(key), true);
        bitSet.set(hash_3(key), true);
    }

    public boolean find(String key) {
    
    
        if (bitSet.get(hash_1(key)) == false)
            return false;
        if (bitSet.get(hash_2(key)) == false)
            return false;
        if (bitSet.get(hash_3(key)) == false)
            return false;
        return true;
    }

    public int hash_1(String key) {
    
    
        int hash = 0;
        for (int i = 0; i < key.length(); ++i) {
    
    
            hash = 33 * hash + key.charAt(i);
        }
        return hash % size;
    }

    public int hash_2(String key) {
    
    
        int p = 16777169;
        int hash = (int) 2166136261L;
        for (int i = 0; i < key.length(); i++) {
    
    
            hash = (hash ^ key.length()) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        return Math.abs(hash) % size;
    }

    public int hash_3(String key) {
    
    
        int hash, i;
        for (hash = 0, i = 0; i < key.length(); ++i) {
    
    
            hash += key.charAt(i);
            hash += (hash << 10);
            hash ^= (hash >> 6);
        }
        hash += (hash << 3);
        hash ^= (hash >> 11);
        hash += (hash << 15);
        return Math.abs(hash) % size;
    }

    public static void main(String[] args) {
    
    
        BloomFilter bloomFilter = new BloomFilter(Integer.MAX_VALUE);//21亿/8/1024/1024=250M
        System.out.println(bloomFilter.hash_1("1"));
        System.out.println(bloomFilter.hash_2("1"));
        System.out.println(bloomFilter.hash_3("1"));

        bloomFilter.add("1111");
        bloomFilter.add("1112");
        bloomFilter.add("1113");
        System.out.println(bloomFilter.find("1"));
        System.out.println(bloomFilter.find("1112"));
    }
}

구글 구아바 테스트

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId> <!-- google bloomfilter 布隆过滤器 -->
    <version>22.0</version>
</dependency>
public class GoogleGuavaBloomFilterTest {
    
    
    public static void main(String[] args) {
    
    
        int dataSize = 1000000; //插入的数据N
        double fpp = 0.001; //0.1% 误判率

        BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), dataSize, fpp);

        for (int i = 0; i < 1000000; i++) {
    
    
            bloomFilter.put(i);
        }

        //测试误判率
        int t = 0;
        for (int i = 2000000; i < 3000000; i++) {
    
    
            if (bloomFilter.mightContain(i)) {
    
    
                t++;
            }
        }
        System.out.println("误判个数:" + t);
    }
}

중국어 분사

트라이 트리

  1. 트리 트리란: 트리 트리는 우리가 일반적으로 사전 트리라고 부르는 것으로 문자열 일치를 처리하는 데 특별히 사용되는 데이터 구조입니다. 특히 많은 문자열 중에서 특정 문자열을 빠르게 찾는 데 적합합니다. 접두사 트리, 허프만 트리, 접두사 인코딩

  2. Trie 데이터 구조: 다음과 같은 영어 단어가 있다고 가정합니다. my name apple age sex, 특정 문자열이 존재하는지 확인하려면 어떻게 찾습니까? 문자열의 공통 접두사를 사용하여 반복되는 것을 결합하여 트리를 형성합니다. 이것이 우리가 말하는 트리 트리입니다.

  3. 트리 트리 구성: 단어를 하나씩 문자로 나눈 다음 트리에 차례로 삽입해야 합니다. 오른쪽에 표시된 것처럼 루트 노드 루트, 앱을 삽입하려면
    먼저 앱을 a, p, p로 나눈 다음 루트 지점에서 시작하여 레이어별로 삽입합니다.
    P가 중단됩니다. 아래에서 A p의 뒤에는 이전 p에 매달려 있습니다. 단어 끝에 보라색을 사용합니다.
    여기서 주의할 점은 각 레이어의 글자를 삽입할 때 순서가 있다는 것입니다.

  4. Trie의 검색:
    검색하려면 루트 지점에서 시작하여 첫 번째 레이어에서 첫 번째 문자를 찾은 다음 원하는 단어를 차례로 찾습니다.
    단어 검색은 끝에 있는 표시를 찾아야 완료됩니다. 예를 들어 app, 우리는 ap를 찾고 있는데,
    사전 트리에 ap가 있지만 이 p는 보라색이 아니지만 사전 트리에는 ap가 존재하지 않습니다.

  5. 트리 트리 구현: 트리 트리는 다중 포크 트리입니다. 여기서 우리는 다소 유사한 B+Tree&B-Tree를 생각해야 합니다.
    Trie 트리는 정확히 26개의 영문자가 있으므로 26 길이의 배열을 열 수 있기 때문에 배열의 첨자를 영리하게 사용합니다.
    A[] = new int[26];
    A[0] = 'a' => 첨자는 'a'-97입니다. 바로 여기 ascii 계산을 사용하여 0입니다.
    따라서 데이터 구조는 다음과 같아야 합니다.

class TrieNode {
    
    
  Char c;//存储当前这个字符
  TrieNode child[26] = new TrieNode[];//存储这个字符的子节点
}
  1. 트리 트리 분석:
  • 시간 복잡도: 매우 효율적 O(단어 길이)
  • 공간 복잡성: 효율성을 위해 공간을 교환하는 데이터 구조. 각 단어에는 이론적으로 26개의 하위 노드가 있으므로 공간 복잡도는 26^n이며 여기서 n은 트리의 높이를 나타냅니다.
  • 최적화:
    1. 반복되는 문자는 반복되지 않습니다
    2. 노드를 저장하기 위해 각 노드에 대해 26개의 공간을 열었기 때문입니다. 하지만 실제 상황은 그렇게 많이 필요하지 않을 수 있으므로 여기서는 실제로 해시 테이블을 사용하여 구현하는 것을 고려할 수 있습니다.여기에서 IK의 소스 코드로 이동할 수 있습니다.
      자식 노드가 적을 때는 배열이지만 3개 이상의 노드가 있을 때 사용 hashMap, 이것은 어느 정도 많은 공간을 절약할 수 있습니다.

여기에 이미지 설명 삽입

중국어 분사

  1. 단어 분할의 원리:
    (1) 영어 단어 분할: 내 이름은 WangLi입니다.
    (2) 중국어 단어 분할: 나는 중국인입니다.

  2. 중국어 단어 분리기 IK

  3. 중국어 단어 분할에는 두 가지 방법이 있으므로 특별한 주의를 기울여야 합니다.

    • 스마트: 지능적인 단어 분할, 여기서는 문장의 모든 상황을 분리하지 않습니다. 예를 들어 I am Chinese, me/is/Chinese로 나뉩니다.
    • 가장 작은 입자(비스마트): 문장의 모든 상황을 구분합니다. 예를 들어 내가 중국인이면 나/예/중국/중국어/중국어로 나뉩니다.
  4. 중국어의 모호성: 동의어 사전, 복잡한 AI 기술, 기계 학습 알고리즘 등 사용
    우한 양쯔강 다리

    • 우한시/양쯔강 대교
    • 우한/시장/강 다리

여기에 이미지 설명 삽입

Lucene 반전 인덱스

여기에 이미지 설명 삽입

검색 엔진

  • 데이터 구조: 데이터 구조가 모든 문서에 나타나는 횟수입니다. 10개의 기사가 있는 경우 모든 기사에 데이터 구조가 나타납니다. 별차이가 없다는 뜻인가요

  • TF: 단어 빈도 문서에 포함된 단어 수는 많을수록 관련성이 높습니다. 문서 수만 계산

  • DF: 문서 빈도에 이 단어가 포함된 총 문서 수, DF는 문서에서 한 번 계산됩니다.

  • IDF: DF의 반전은 1/DF입니다. 단어를 포함하는 문서가 적을수록, 즉 DF가 작고 IDF가 클수록 이 문서에서 단어의 중요성이 커집니다.

  • TF-IDF: TF*IDF의 주요 아이디어는 TF의 빈도가 높은 기사에 단어나 구가 등장하고 다른 기사에서는 거의 나타나지 않는 경우 해당 단어나 구가 범주 구분이 잘 된 것으로 간주됩니다. 능력, 이 기사의 점수가 높을수록.
    문제가 무엇입니까? 길이는 다양합니다.기사는 2단어입니다.그 중 하나를 검색하는 것은 가중치의 50%입니다.100단어와 48단어의 기사가 있습니다.

  • 정규화 처리: 기사 길이에 따라 주로 TF-IDF 처리

  • 점수 맞춤 보너스

추천

출처blog.csdn.net/menxu_work/article/details/130368439