실용적인 알고리즘에 대한 기본 소개





실용적인 알고리즘에 대해 무엇을 알아야 합니까? 이 기사는 당신에게 자세한 이해를 제공할 것입니다. 수집을 환영합니다!


URL 블랙리스트(블룸 필터)  


100억 개의 블랙리스트 URL, 각각 64B, 이 블랙리스트를 어떻게 저장하나요? URL이 블랙리스트에 있는지 확인

해시 테이블:

블랙리스트를 세트로 간주하여 해시맵에 저장하면 너무 커서 640G가 필요한 것으로 보이며 이는 명백히 비과학적인 것입니다.

블룸 필터:

실제로는 긴 이진 벡터와 일련의 무작위 매핑 함수입니다.

요소가 집합에 있는지 여부를 확인하는 데 사용할 수 있습니다 . 장점은 적은 양의 메모리 공간만 차지하며 쿼리 효율성이 높다는 것입니다. Bloom 필터의 경우 그 본질은 비트 배열 입니다 . 비트 배열은 배열의 각 요소가 1비트만 차지하고 각 요소는 0 또는 1만 될 수 있음을 의미합니다.

배열의 각 비트는 이진 비트입니다. Bloom 필터에는 비트 배열 외에도 K개의 해시 함수가 있습니다. Bloom 필터에 요소가 추가되면 다음 작업이 수행됩니다.

  • K 해시 함수를 사용하여 요소 값에 대해 K 계산을 수행하여 K 해시 값을 얻습니다.

  • 획득된 해시 값에 따라 해당 첨자 값은 비트 배열에서 1로 설정됩니다.


▐단어 빈도 통계(파일로 구분)  


20억 개의 정수 중에서 가장 빈번한 숫자를 찾는 2GB 메모리

일반적인 접근 방식은 해시 테이블을 사용하여 나타나는 각 숫자에 대한 단어 빈도 통계를 만드는 것입니다. 해시 테이블의 키는 정수이고 값은 정수가 나타나는 횟수를 기록합니다. 이 질문의 데이터 양은 20억 개입니다. 오버플로를 방지하기 위해 해시 테이블의 키는 32비트(4B)이고 값도 32비트(4B)입니다. ) 그러면 해시 테이블의 레코드는 8B를 차지해야 합니다.

해시 테이블 레코드 개수가 2억개일 경우 16억 바이트(8*2억)가 필요하며, 최소 1.6GB의 메모리가 필요합니다(16억/2^30,1GB==2^30바이트== 1000000000). ). 그러면 20억 개의 레코드에는 최소 16GB의 메모리가 필요하며 이는 질문 요구 사항을 충족하지 않습니다.

해결책은 해시 함수를 사용하여 20억 개의 숫자가 있는 대용량 파일을 16개의 작은 파일로 나누는 것입니다. 해시 함수에 따르면 20억 개의 데이터를 16개의 파일로 균등하게 분배할 수 있습니다. 해시 함수가 충분하다고 가정하면 다른 작은 파일에서. 그런 다음 각 작은 파일에 대해 해시 함수를 사용하여 각 숫자의 발생 횟수를 계산하여 16개 파일 중에서 가장 많이 발생하는 숫자를 얻은 다음 16개 숫자 중에서 가장 많이 발생하는 키를 선택합니다.


나타나지 않는 숫자(비트 배열)  


  • 음이 아닌 정수 40억개 중에서 나타나지 않는 수를 찾아보세요.


원래 문제의 경우, 나타난 숫자를 저장하기 위해 해시 테이블을 사용한다면 최악의 경우 40억 개의 숫자가 다르므로 해시 테이블은 40억 개의 데이터를 저장해야 하며 32비트 정수는 4B, 그러면 40억 * 4B = 160억 바이트입니다. 일반적으로 약 10억 바이트의 데이터에는 1G의 공간이 필요하므로 약 16G의 공간이 필요하므로 요구 사항을 충족하지 않습니다.

방식을 바꿔서 비트 배열을 적용해 보겠습니다. 배열 크기는 약 40억 비트입니다. 40억/8=5억 바이트이므로 비트 배열의 각 위치에는 두 가지 상태가 필요합니다. 0과 1. 그러면 이 비트 배열을 어떻게 사용합니까? 하하, 배열의 길이는 정수의 숫자 범위와 일치하며, 배열의 각 첨자 값은 4294967295의 숫자에 해당하고 40억 개의 부호 없는 숫자를 하나씩 순회합니다. 예를 들어 20이 나타나면 bitArray입니다. [20]= 1; 666이 발견되면 bitArray[666]=1, 모든 숫자를 순회한 후 배열의 해당 위치를 1로 변경합니다.


  • 음수가 아닌 정수 40억개 중에서 나오지 않는 숫자를 찾아보세요. 메모리 제한은 10MB입니다.


10억 바이트의 데이터를 처리하려면 약 1GB의 공간이 필요하므로 메모리 10MB로 1천만 바이트의 데이터, 즉 8천만 비트를 처리할 수 있다. 40억 개의 음수가 아닌 정수에 대해 비트 배열을 적용하면 40억 비트가 된다. /080백만 비트=50이면 처리하는 데 최소 50블록이 소요됩니다. 64블록을 사용하여 분석하고 답해 보겠습니다.


  • 고급 솔루션 요약


  1. 10MB 메모리 제한에 따라 두 번째 순회 동안의 bitArr 크기인 통계 간격의 크기를 결정합니다.

  2. 간격 계산을 사용하여 카운트가 부족한 간격을 찾으십시오. 이 간격에는 표시되지 않는 숫자가 있어야 합니다.

  3. 이 간격에 있는 숫자의 비트맵 매핑을 수행한 다음 비트맵을 탐색하여 나타나지 않는 숫자를 찾습니다.


  • 내 의견


숫자만 찾는 경우 높은 비트 모듈러스 연산을 수행하고 이를 64개의 서로 다른 파일에 쓴 다음 가장 작은 파일에서 bitArray를 통해 한 번에 처리할 수 있습니다.


  • 부호 없는 정수 40억 개, 메모리 1GB, 두 번 나타나는 숫자 모두 찾기


원래 문제의 경우 비트맵을 사용하여 숫자 발생을 나타낼 수 있습니다. 구체적으로는 길이가 4294967295×2인 비트형 배열 bitArr을 적용하는 것입니다. 숫자의 단어 빈도를 나타내기 위해 두 위치가 사용되므로 1B는 8비트를 차지하므로 길이가 4294967295×2인 비트형 배열이 됩니다. 1GB의 공간을 차지합니다. 이 bitArr 배열을 사용하는 방법은 무엇입니까? 40억 개의 부호 없는 숫자를 탐색합니다. num이 처음 발견되면 bitArr[num 2+1] 및 bitArr[num 2]를 01로 설정합니다. num이 두 번째로 발견되면 bitArr[num 2+1 ] 을 설정합니다. 및 bitArr[num 2]는 10으로 설정됩니다. num이 세 번째로 발생하면 bitArr[num 2+1] 및 bitArr[num 2]가 11로 설정됩니다. 나중에 num을 다시 만나면 bitArr[num 2+1]과 bitArr[num 2]가 이때 11로 설정되어 있어서 더 이상의 설정은 하지 않는다는 것을 알게 됩니다. 순회가 완료된 후 bitArr을 순차적으로 순회합니다. bitArr[i 2+1] 및 bitArr[i 2]가 10으로 설정된 경우 i는 두 번 나타나는 숫자입니다.


▐중복 URL(머신별)  


  • 100억 개의 URL 중 중복된 URL 찾기


원래 문제에 대한 솔루션은 빅 데이터 문제를 해결하기 위해 해시 함수를 통해 대용량 파일을 머신에 할당하거나 해시 함수를 통해 대용량 파일을 작은 파일로 분할하는 기존 방법을 사용합니다. 이 분할은 분할 결과가 자원 제약을 충족할 때까지 수행됩니다. 먼저, 면접관에게 메모리 요구 사항, 컴퓨팅 시간 등을 포함하여 리소스 제약이 무엇인지 물어봐야 합니다. 제한 요구 사항을 명확히 한 후 각 URL은 해시 함수를 통해 여러 시스템에 할당되거나 여러 개의 작은 파일로 분할될 수 있습니다. 여기서 "여러 개"의 정확한 수는 특정 리소스 제한에 따라 계산됩니다.

예를 들어, 100억 바이트의 대용량 파일이 해시 함수를 통해 100개의 머신에 배포되고, 각 머신은 자신에게 할당된 URL에 중복된 URL이 있는지 여부를 계산하는 동시에 해시 함수의 성격에 따라 여부가 결정됩니다 . 동일한 URL은 URL을 다른 컴퓨터에 배포 하거나 단일 컴퓨터의 해시 기능을 통해 큰 파일을 1000개의 작은 파일로 분할한 다음 각 작은 파일에 대한 해시 테이블 탐색을 사용하여 중복된 URL을 찾는 것이 불가능합니다 . 머신에 배포하거나, 파일을 분할한 후 정렬하고, 정렬 후 중복된 URL이 있는지 확인하세요. 즉, 많은 빅 데이터 문제는 오프로딩과 분리될 수 없다는 점을 명심하십시오. 해시 함수가 대용량 파일의 내용을 다른 시스템에 배포하거나 해시 함수가 대용량 파일을 작은 파일로 분할한 다음 각각의 작은 수의 컬렉션을 처리합니다. .


TOPK 검색(작은 루트 파일)  


  • 수많은 단어를 검색하여 가장 인기있는 TOP100 단어를 찾아보세요.


처음에는 수백억 개의 데이터가 포함된 어휘 파일을 다른 기계로 분류하기 위해 해시 전환이라는 아이디어를 사용했습니다. 구체적인 기계 수는 면접관이나 더 많은 제한 사항에 따라 결정되었습니다. 각 머신에 대해 메모리 부족이나 기타 문제로 인해 분산된 데이터의 양이 여전히 큰 경우 해시 함수를 사용하여 각 머신의 분산 파일을 더 작은 파일로 분할하여 처리할 수 있습니다.

각 작은 파일을 처리할 때 해시 테이블은 각 단어와 해당 단어 빈도를 계산합니다. 해시 테이블 레코드가 설정된 후 해시 테이블을 탐색하는 동안 크기 100의 작은 루트 힙을 사용하여 가져오기를 선택합니다. 각 작은 파일의 상위 100개(정렬되지 않은 전체 상위 100개) 각 작은 파일에는 단어 빈도의 자체 작은 루트 힙(정렬되지 않은 전체 상위 100개)이 있습니다. 단어 빈도에 따라 작은 루트 힙의 단어를 정렬하면 각 작은 파일의 정렬된 상위 100개가 얻어집니다. 그런 다음 외부에서 각 작은 파일의 상위 100개를 정렬하거나 계속해서 작은 루트 힙을 사용하여 각 시스템의 상위 100개를 선택합니다. 그런 다음 서로 다른 머신 간의 상위 100개를 외부에서 정렬하거나 작은 루트 힙을 계속 사용하여 최종적으로 전체 수백억 개의 데이터 중 상위 100개를 얻습니다. 상위 K 문제의 경우 해시 테이블을 이용한 해시 함수 전환 및 단어 빈도 통계 외에도 힙 구조 및 외부 정렬을 사용하여 처리하는 경우가 많습니다.


▐중앙값 (단방향 이진 검색)  


  • 10MB 메모리, 100억 개의 정수의 중앙값 찾기


  1. 충분한 메모리: 메모리가 충분하다면 걱정할 필요가 없습니다. 100억 개의 항목을 모두 정렬한 다음 버블링을 사용하고... 중간에 있는 항목을 찾으면 됩니다. 그런데 면접관이 기억력을 줄 것 같나요? ?

  2. 메모리 부족: 질문에는 정수라고 되어 있지만 부호 있는 정수라고 생각하므로 4바이트이고 32비트를 차지합니다.

100억 개의 숫자가 큰 파일에 저장되어 있다고 가정하고, 파일의 일부를 순서대로(메모리 제한을 초과하지 않음) 메모리로 읽어들이고, 각 숫자를 이진수로 표현하고 이진수의 가장 높은 비트(비트 32, 부호 비트, 0은 양수, 1은 음수), 숫자의 가장 높은 비트가 0이면 해당 숫자는 file_0 파일에 기록되고, 가장 높은 비트가 1이면 해당 숫자는 file_1 파일에 기록됩니다.

따라서 100억 개의 숫자가 두 개의 파일로 나누어집니다. file_0 파일에는 60억 개의 숫자가 있고 file_1 파일에는 40억 개의 숫자가 있다고 가정합니다. 그런 다음 중앙값은 file_0 파일에 있으며 file_0 파일의 모든 숫자를 정렬한 후의 10억 번째 숫자입니다. (file_1의 숫자는 모두 음수이고, file_0의 숫자는 모두 양수이다. 즉 총 40억 개의 음수만 있으므로 정렬 후 50억 번째 숫자는 file_0에 위치해야 한다.)

이제 file_0 파일만 처리하면 됩니다(더 이상 file_1 파일을 고려할 필요가 없습니다). file_0 파일의 경우 위와 동일한 조치를 취합니다. file_0 파일의 일부를 순서대로 메모리로 읽고(메모리 제한을 초과하지 않음) 각 숫자를 이진수로 표시하고 이진수의 두 번째로 높은 비트(31번째 비트)를 비교합니다. 두 번째로 높은 비트가 0이면 file_0_0 파일에 쓰고, 두 번째로 높은 비트가 1이면 file_0_1 파일에 씁니다.

이제 file_0_0에 30억 개의 숫자가 있고 file_0_1에 30억 개의 숫자가 있다고 가정하면 중앙값은 file_0_0의 숫자 다음의 10억 번째 숫자가 작은 것에서 큰 것으로 정렬됩니다.

file_0_1 파일을 버리고 다음으로 높은 숫자(30번째 위치)에 따라 file_0_0 파일을 계속 나눕니다. 이번에 분할된 두 파일은 file_0_0_0에 5억 개의 숫자가 있고 file_0_0_1에 25억 개의 숫자가 있다고 가정합니다. file_0_0_1 파일의 모든 숫자를 정렬한 후 5억 번째 숫자입니다.

위의 아이디어에 따르면 분할된 파일을 메모리에 직접 로드할 수 있을 때까지 숫자를 직접 빠르게 정렬하여 중앙값을 찾을 수 있습니다.


▐Short Domain Name System(캐싱)  


  • 긴 URL을 짧은 URL로 변환하는 짧은 도메인 이름 시스템을 설계합니다.


  1. 번호 할당자를 사용하면 초기값은 0이다. 짧은 링크 생성을 요청할 때마다 번호 할당자의 값이 증가한 후 이 값을 첫 번째와 같이 62개의 16진수(a-zA-Z0-9)로 변환한다. request 요청 시 숫자 할당자의 값은 16진수 a에 해당하는 0입니다. 두 번째 요청에서는 숫자 할당자의 값이 16진수 b에 해당합니다. 숫자 할당자는 10000이며 16진수 표기법은 sBc입니다.

  2. 짧은 링크 서버 도메인 이름을 짧은 링크의 URL인 문자열로 할당자의 62자리 16진수 값과 연결합니다(예: t.cn/sBc).

  3. 리디렉션 프로세스: 짧은 링크를 생성한 후 짧은 링크와 긴 링크 간의 매핑 관계, 즉 sBc -> URL을 저장해야 합니다. 브라우저가 짧은 링크 서버에 접속하면 해당 링크에 따라 원본 링크를 가져옵니다. URL 경로를 선택한 다음 302 리디렉션을 수행합니다. 매핑 관계는 Redis 또는 Memcache와 같은 KV를 사용하여 저장할 수 있습니다.


▐대량 댓글 저장(메시지 큐)  


이런 시나리오가 있다고 가정해 보겠습니다. 뉴스에 대한 댓글 수가 많을 수 있습니다. 댓글을 읽고 쓰는 방법은 무엇입니까?

프런트 엔드 페이지는 사용자에게 직접 표시되며 메시지 대기열을 통해 비동기적으로 데이터베이스에 저장됩니다.

읽기는 읽기 및 쓰기와 분리될 수 있으며 인기 댓글은 정기적으로 캐시에 로드됩니다.


온라인/동시 사용자 수(Redis)  


  • 웹사이트의 온라인 사용자 수를 표시하기 위한 솔루션 아이디어

  1. 온라인 사용자 테이블 유지
  2. Redis 통계 사용

  • 웹 사이트의 동시 사용자 수를 표시합니다.

  1. 사용자가 서비스에 액세스할 때마다 사용자의 ID가 ZSORT 대기열에 기록되고 가중치는 현재 시간입니다.
  2. 가중치(예: 시간)를 기준으로 1분 이내에 조직 Zrange의 사용자 수를 계산합니다.
  3. 1분 이상 만료된 사용자 Zrem을 삭제합니다.


▐인기 문자열(접두사 트리)  


현재 1,000만 건의 레코드가 있다고 가정해보자(이러한 쿼리 문자열의 반복 정도는 상대적으로 높다. 총 1,000만 건이지만 반복을 제거하면 300만 건을 넘지 않는다). 가장 많이 사용되는 쿼리 문자열 10개를 세어 보세요. 필요한 메모리는 1G를 초과할 수 없습니다. (쿼리 문자열의 반복 횟수가 많을수록 더 많은 사용자가 쿼리하고 인기가 높아집니다.)

  • 해시맵 방법


총 문자열 수는 상대적으로 많지만 중복 제거 후 300w를 초과하지 않습니다. 따라서 모든 문자열과 해당 발생 시간을 HashMap에 저장하는 것을 고려할 수 있습니다. 4는 정수가 차지하는 4바이트를 나타냅니다. 1G의 메모리 공간이면 충분하다는 것을 알 수 있다.


  • 아이디어는 다음과 같습니다


먼저 문자열을 탐색하여 맵에 없으면 직접 저장하고, 맵에 있으면 해당 값에 1을 더합니다 O(N).

그런 다음 맵을 순회하여 10개 요소의 작은 상단 힙을 만듭니다. 순회된 문자열의 발생 횟수가 힙 상단의 문자열 발생 횟수보다 크면 이를 바꾸고 힙을 작은 상단으로 조정합니다. 더미.

순회가 완료된 후 힙에 있는 10개의 문자열이 가장 많이 나타나는 문자열입니다. 이 단계의 시간 복잡도입니다 O(Nlog10).


  • 접두사 트리 방법


이러한 문자열에 동일한 접두사가 많이 있는 경우 접두사 트리를 사용하여 문자열 발생 횟수를 계산할 수 있습니다. 트리의 노드는 문자열 발생 횟수를 저장하며 0은 발생하지 않음을 의미합니다.


  • 아이디어는 다음과 같습니다


문자열을 순회할 때 접두사 트리에서 검색하면 노드에 저장된 문자열 수에 1을 추가하고, 그렇지 않으면 이 문자열에 대한 새 노드를 구축한 후 해당 문자열의 발생을 추가합니다. 리프 노드의 횟수는 1로 설정됩니다.

마지막으로, 작은 최상위 힙은 여전히 ​​문자열 발생 횟수를 정렬하는 데 사용됩니다.


▐빨간 봉투 알고리즘  


N-1개의 칼날을 한 간격으로 절단하는 선형 절단 방식입니다. 빠를수록 좋다

이중평균법, [0~남은수량/남은인원수*2]에서 무작위, 비교적 균일함


▐손글씨 빠른 정렬  


public class QuickSort {    public static void swap(int[] arr, int i, int j) {        int tmp = arr[i];        arr[i] = arr[j];        arr[j] = tmp;    }    /* 常规快排 */    public static void quickSort1(int[] arr, int L , int R) {        if (L > R)  return;        int M = partition(arr, L, R);        quickSort1(arr, L, M - 1);        quickSort1(arr, M + 1, R);    }    public static int partition(int[] arr, int L, int R) {        if (L > R) return -1;        if (L == R) return L;        int lessEqual = L - 1;        int index = L;        while (index < R) {            if (arr[index] <= arr[R])                swap(arr, index, ++lessEqual);            index++;        }        swap(arr, ++lessEqual, R);        return lessEqual;    }    /* 荷兰国旗 */    public static void quickSort2(int[] arr, int L, int R) {        if (L > R)  return;        int[] equalArea = netherlandsFlag(arr, L, R);        quickSort2(arr, L, equalArea[0] - 1);        quickSort2(arr, equalArea[1] + 1, R);    }    public static int[] netherlandsFlag(int[] arr, int L, int R) {        if (L > R) return new int[] { -1, -1 };        if (L == R) return new int[] { L, R };        int less = L - 1;        int more = R;        int index = L;        while (index < more) {            if (arr[index] == arr[R]) {                index++;            } else if (arr[index] < arr[R]) {                swap(arr, index++, ++less);            } else {                swap(arr, index, --more);            }        }        swap(arr, more, R);        return new int[] { less + 1, more };    }
// for test public static void main(String[] args) { int testTime = 1; int maxSize = 10000000; int maxValue = 100000; boolean succeed = true; long T1=0,T2=0; for (int i = 0; i < testTime; i++) { int[] arr1 = generateRandomArray(maxSize, maxValue); int[] arr2 = copyArray(arr1); int[] arr3 = copyArray(arr1);// int[] arr1 = {9,8,7,6,5,4,3,2,1}; long t1 = System.currentTimeMillis(); quickSort1(arr1,0,arr1.length-1); long t2 = System.currentTimeMillis(); quickSort2(arr2,0,arr2.length-1); long t3 = System.currentTimeMillis(); T1 += (t2-t1); T2 += (t3-t2); if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) { succeed = false; break; } } System.out.println(T1+" "+T2);// System.out.println(succeed ? "Nice!" : "Oops!"); }
private static int[] generateRandomArray(int maxSize, int maxValue) { int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; for (int i = 0; i < arr.length; i++) { arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); } return arr; } private static int[] copyArray(int[] arr) { if (arr == null) return null; int[] res = new int[arr.length]; for (int i = 0; i < arr.length; i++) { res[i] = arr[i]; } return res; } private static boolean isEqual(int[] arr1, int[] arr2) { if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) return false; if (arr1 == null && arr2 == null) return true; if (arr1.length != arr2.length) return false; for (int i = 0; i < arr1.length; i++) if (arr1[i] != arr2[i]) return false; return true; } private static void printArray(int[] arr) { if (arr == null) return; for (int i = 0; i < arr.length; i++) System.out.print(arr[i] + " "); System.out.println(); }}


▐필기 병합  


public static void merge(int[] arr, int L, int M, int R) {    int[] help = new int[R - L + 1];    int i = 0;    int p1 = L;    int p2 = M + 1;    while (p1 <= M && p2 <= R)        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];    while (p1 <= M)        help[i++] = arr[p1++];    while (p2 <= R)        help[i++] = arr[p2++];    for (i = 0; i < help.length; i++)        arr[L + i] = help[i];}public static void mergeSort(int[] arr, int L, int R) {    if (L == R)        return;    int mid = L + ((R - L) >> 1);    process(arr, L, mid);    process(arr, mid + 1, R);    merge(arr, L, mid, R);}public static void main(String[] args) {    int[] arr1 = {9,8,7,6,5,4,3,2,1};    mergeSort(arr, 0, arr.length - 1);    printArray(arr);}


▐손으로 쓴 스택  


// 堆排序额外空间复杂度O(1)public static void heapSort(int[] arr) {    if (arr == null || arr.length < 2)         return;    for (int i = arr.length - 1; i >= 0; i--)         heapify(arr, i, arr.length);    int heapSize = arr.length;    swap(arr, 0, --heapSize);    // O(N*logN)    while (heapSize > 0) { // O(N)        heapify(arr, 0, heapSize); // O(logN)        swap(arr, 0, --heapSize); // O(1)    }}// arr[index]刚来的数,往上public static void heapInsert(int[] arr, int index) {    while (arr[index] > arr[(index - 1) / 2]) {        swap(arr, index, (index - 1) / 2);        index = (index - 1) / 2;    }}// arr[index]位置的数,能否往下移动public static void heapify(int[] arr, int index, int heapSize) {    int left = index * 2 + 1; // 左孩子的下标    while (left < heapSize) { // 下方还有孩子的时候        // 两个孩子中,谁的值大,把下标给largest        // 1)只有左孩子,left -> largest        // 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest        // 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest        int largest = left+1 < heapSize && arr[left+1]> arr[left] ? left+1 : left;        // 父和较大的孩子之间,谁的值大,把下标给largest        largest = arr[largest] > arr[index] ? largest : index;        if (largest == index)            break;        swap(arr, largest, index);        index = largest;        left = index * 2 + 1;    }}public static void swap(int[] arr, int i, int j) {    int tmp = arr[i];    arr[i] = arr[j];    arr[j] = tmp;}public static void main(String[] args) {    int[] arr1 = {9,8,7,6,5,4,3,2,1};    heapSort(arr1);    printArray(arr1);}


▐손으로 쓴 싱글톤  


public class Singleton {        private volatile static Singleton singleton;        private Singleton() {}        public static Singleton getSingleton() {        if (singleton == null) {              synchronized (Singleton.class) {            if (singleton == null) {                  singleton = new Singleton();            }        }        }        return singleton;    }}


▐손으로 쓴 LRUcache  


// 基于linkedHashMappublic class LRUCache {    private LinkedHashMap<Integer,Integer> cache;    private int capacity;   //容量大小    public LRUCache(int capacity) {        cache = new LinkedHashMap<>(capacity);        this.capacity = capacity;    }    public int get(int key) {        //缓存中不存在此key,直接返回        if(!cache.containsKey(key)) {            return -1;        }        int res = cache.get(key);        cache.remove(key);   //先从链表中删除        cache.put(key,res);  //再把该节点放到链表末尾处        return res;    }    public void put(int key,int value) {        if(cache.containsKey(key)) {            cache.remove(key); //已经存在,在当前链表移除        }        if(capacity == cache.size()) {            //cache已满,删除链表头位置            Set<Integer> keySet = cache.keySet();            Iterator<Integer> iterator = keySet.iterator();            cache.remove(iterator.next());        }        cache.put(key,value);  //插入到链表末尾    }}
//手写双向链表class LRUCache {    class DNode {        DNode prev;        DNode next;        int val;        int key;    }    Map<Integer, DNode> map = new HashMap<>();    DNode head, tail;    int cap;    public LRUCache(int capacity) {        head = new DNode();        tail = new DNode();        head.next = tail;        tail.prev = head;        cap = capacity;    }    public int get(int key) {        if (map.containsKey(key)) {            DNode node = map.get(key);            removeNode(node);            addToHead(node);            return node.val;        } else {            return -1;        }    }    public void put(int key, int value) {        if (map.containsKey(key)) {            DNode node = map.get(key);            node.val = value;            removeNode(node);            addToHead(node);        } else {            DNode newNode = new DNode();            newNode.val = value;            newNode.key = key;            addToHead(newNode);            map.put(key, newNode);            if (map.size() > cap) {                map.remove(tail.prev.key);                removeNode(tail.prev);            }        }    }    public void removeNode(DNode node) {        DNode prevNode = node.prev;        DNode nextNode = node.next;        prevNode.next = nextNode;        nextNode.prev = prevNode;    }    public void addToHead(DNode node) {        DNode firstNode = head.next;        head.next = node;        node.prev = head;        node.next = firstNode;        firstNode.prev = node;    }}


▐필기 스레드 풀  


package com.concurrent.pool;import java.util.HashSet;import java.util.Set;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;public class MySelfThreadPool {    //默认线程池中的线程的数量    private static final int WORK_NUM = 5;    //默认处理任务的数量    private static final int TASK_NUM = 100;    private int workNum;//线程数量    private int taskNum;//任务数量    private final Set<WorkThread> workThreads;//保存线程的集合    private final BlockingQueue<Runnable> taskQueue;//阻塞有序队列存放任务    public MySelfThreadPool() {        this(WORK_NUM, TASK_NUM);    }    public MySelfThreadPool(int workNum, int taskNum) {        if (workNum <= 0) workNum = WORK_NUM;        if (taskNum <= 0) taskNum = TASK_NUM;        taskQueue = new ArrayBlockingQueue<>(taskNum);        this.workNum = workNum;        this.taskNum = taskNum;        workThreads = new HashSet<>();        //启动一定数量的线程数,从队列中获取任务处理        for (int i=0;i<workNum;i++) {            WorkThread workThread = new WorkThread("thead_"+i);            workThread.start();            workThreads.add(workThread);        }    }    public void execute(Runnable task) {        try {            taskQueue.put(task);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }    public void destroy() {        System.out.println("ready close thread pool...");        if (workThreads == null || workThreads.isEmpty()) return ;        for (WorkThread workThread : workThreads) {            workThread.stopWork();            workThread = null;//help gc        }        workThreads.clear();    }    private class WorkThread extends Thread{        public WorkThread(String name) {            super();            setName(name);        }        @Override        public void run() {            while (!interrupted()) {                try {                    Runnable runnable = taskQueue.take();//获取任务                    if (runnable !=null) {                        System.out.println(getName()+" readyexecute:"+runnable.toString());                        runnable.run();//执行任务                    }                    runnable = null;//help gc                } catch (Exception e) {                    interrupt();                    e.printStackTrace();                }            }        }        public void stopWork() {            interrupt();        }    }}
package com.concurrent.pool;
public class TestMySelfThreadPool { private static final int TASK_NUM = 50;//任务的个数 public static void main(String[] args) { MySelfThreadPool myPool = new MySelfThreadPool(3,50); for (int i=0;i<TASK_NUM;i++) { myPool.execute(new MyTask("task_"+i)); } } static class MyTask implements Runnable{ private String name; public MyTask(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("task :"+name+" end..."); } @Override public String toString() { // TODO Auto-generated method stub return "name = "+name; } }}


▐손으로 쓴 소비자 생산자 패턴  


public class Storage {    private static int MAX_VALUE = 100;    private List<Object> list = new ArrayList<>();    public void produce(int num) {        synchronized (list) {            while (list.size() + num > MAX_VALUE) {                System.out.println("暂时不能执行生产任务");                try {                    list.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            for (int i = 0; i < num; i++) {                list.add(new Object());            }            System.out.println("已生产产品数"+num+" 仓库容量"+list.size());            list.notifyAll();        }    }
public void consume(int num) { synchronized (list) { while (list.size() < num) { System.out.println("暂时不能执行消费任务"); try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < num; i++) { list.remove(0); } System.out.println("已消费产品数"+num+" 仓库容量" + list.size()); list.notifyAll(); } }}
public class Producer extends Thread { private int num; private Storage storage; public Producer(Storage storage) { this.storage = storage; } public void setNum(int num) { this.num = num; } public void run() { storage.produce(this.num); }}
public class Customer extends Thread { private int num; private Storage storage; public Customer(Storage storage) { this.storage = storage; } public void setNum(int num) { this.num = num; } public void run() { storage.consume(this.num); }}
public class Test { public static void main(String[] args) { Storage storage = new Storage(); Producer p1 = new Producer(storage); Producer p2 = new Producer(storage); Producer p3 = new Producer(storage); Producer p4 = new Producer(storage); Customer c1 = new Customer(storage); Customer c2 = new Customer(storage); Customer c3 = new Customer(storage); p1.setNum(10); p2.setNum(20); p3.setNum(80); c1.setNum(50); c2.setNum(20); c3.setNum(20); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); }}


▐필기 차단 대기열  


public class blockQueue {    private List<Integer> container = new ArrayList<>();    private volatile int size;    private volatile int capacity;    private Lock lock = new ReentrantLock();    private final Condition isNull = lock.newCondition();    private final Condition isFull = lock.newCondition();    blockQueue(int capacity) {        this.capacity = capacity;    }    public void add(int data) {        try {            lock.lock();            try {                while (size >= capacity) {                    System.out.println("阻塞队列满了");                    isFull.await();                }            } catch (Exception e) {                isFull.signal();                e.printStackTrace();            }            ++size;            container.add(data);            isNull.signal();        } finally {            lock.unlock();        }    }    public int take() {        try {            lock.lock();            try {                while (size == 0) {                    System.out.println("阻塞队列空了");                    isNull.await();                }            } catch (Exception e) {                isNull.signal();                e.printStackTrace();            }            --size;            int res = container.get(0);            container.remove(0);            isFull.signal();            return res;        } finally {            lock.unlock();        }    }}
public static void main(String[] args) { AxinBlockQueue queue = new AxinBlockQueue(5); Thread t1 = new Thread(() -> { for (int i = 0; i < 100; i++) { queue.add(i); System.out.println("塞入" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { for (; ; ) { System.out.println("消费"+queue.take()); try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } }
}); t1.start(); t2.start();}


▐손으로 쓴 멀티스레드 대체 인쇄 ABC  


package com.demo.test;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class syncPrinter implements Runnable{    // 打印次数    private static final int PRINT_COUNT = 10;    private final ReentrantLock reentrantLock;    private final Condition thisCondtion;    private final Condition nextCondtion;    private final char printChar;    public syncPrinter(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, char printChar) {        this.reentrantLock = reentrantLock;        this.nextCondtion = nextCondition;        this.thisCondtion = thisCondtion;        this.printChar = printChar;    }    @Override    public void run() {        // 获取打印锁 进入临界区        reentrantLock.lock();        try {            // 连续打印PRINT_COUNT次            for (int i = 0; i < PRINT_COUNT; i++) {                //打印字符                System.out.print(printChar);                // 使用nextCondition唤醒下一个线程                // 因为只有一个线程在等待,所以signal或者signalAll都可以                nextCondtion.signal();                // 不是最后一次则通过thisCondtion等待被唤醒                // 必须要加判断,不然虽然能够打印10次,但10次后就会直接死锁                if (i < PRINT_COUNT - 1) {                    try {                        // 本线程让出锁并等待唤醒                        thisCondtion.await();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        } finally {            reentrantLock.unlock();        }    }
public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition(); Thread printA = new Thread(new syncPrinter(lock, conditionA, conditionB,'A')); Thread printB = new Thread(new syncPrinter(lock, conditionB, conditionC,'B')); Thread printC = new Thread(new syncPrinter(lock, conditionC, conditionA,'C')); printA.start(); Thread.sleep(100); printB.start(); Thread.sleep(100); printC.start(); }}


▐FooBar를 교대로 인쇄  


//手太阴肺经 BLOCKING Queuepublic class FooBar {    private int n;    private BlockingQueue<Integer> bar = new LinkedBlockingQueue<>(1);    private BlockingQueue<Integer> foo = new LinkedBlockingQueue<>(1);    public FooBar(int n) {        this.n = n;    }    public void foo(Runnable printFoo) throws InterruptedException {        for (int i = 0; i < n; i++) {            foo.put(i);            printFoo.run();            bar.put(i);        }    }    public void bar(Runnable printBar) throws InterruptedException {        for (int i = 0; i < n; i++) {            bar.take();            printBar.run();            foo.take();        }    }}
//手阳明大肠经CyclicBarrier 控制先后class FooBar6 { private int n; public FooBar6(int n) { this.n = n; } CyclicBarrier cb = new CyclicBarrier(2); volatile boolean fin = true; public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { while(!fin); printFoo.run(); fin = false; try { cb.await(); } catch (BrokenBarrierException e) {} } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { try { cb.await(); } catch (BrokenBarrierException e) {} printBar.run(); fin = true; } }}
//手少阴心经 自旋 + 让出CPUclass FooBar5 { private int n;
public FooBar5(int n) { this.n = n; } volatile boolean permitFoo = true; public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; ) { if(permitFoo) { printFoo.run(); i++; permitFoo = false; }else{ Thread.yield(); } } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; ) { if(!permitFoo) { printBar.run(); i++; permitFoo = true; }else{ Thread.yield(); } } }}


//手少阳三焦经 可重入锁 + Conditionclass FooBar4 { private int n;
public FooBar4(int n) { this.n = n; } Lock lock = new ReentrantLock(true); private final Condition foo = lock.newCondition(); volatile boolean flag = true; public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { lock.lock(); try { while(!flag) { foo.await(); } printFoo.run(); flag = false; foo.signal(); }finally { lock.unlock(); } } }
public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n;i++) { lock.lock(); try { while(flag) { foo.await(); } printBar.run(); flag = true; foo.signal(); }finally { lock.unlock(); } } }}
//手厥阴心包经 synchronized + 标志位 + 唤醒class FooBar3 { private int n; // 标志位,控制执行顺序,true执行printFoo,false执行printBar private volatile boolean type = true; private final Object foo= new Object(); // 锁标志
public FooBar3(int n) { this.n = n; } public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { synchronized (foo) { while(!type){ foo.wait(); } printFoo.run(); type = false; foo.notifyAll(); } } }
public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { synchronized (foo) { while(type){ foo.wait(); } printBar.run(); type = true; foo.notifyAll(); } } }}

//手太阳小肠经 信号量 适合控制顺序class FooBar2 { private int n; private Semaphore foo = new Semaphore(1); private Semaphore bar = new Semaphore(0); public FooBar2(int n) { this.n = n; }
public void foo(Runnable printFoo) throws InterruptedException { for (int i = 0; i < n; i++) { foo.acquire(); printFoo.run(); bar.release(); } } public void bar(Runnable printBar) throws InterruptedException { for (int i = 0; i < n; i++) { bar.acquire(); printBar.run(); foo.release(); } }}

¤읽기   확장¤

3DXR 기술  |  오디오 및 비디오 기술  | 

服务端技术 | 技术质量 | 数据算法


本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

一个不知名的开源项目可以带来多少收入 微软中国 AI 团队集体打包去美国,涉及数百人 华为官宣余承东职务调整 15 年前被钉在“FFmpeg 耻辱柱”,今天他却得谢谢咱——腾讯QQ影音一雪前耻? 华中科技大学开源镜像站正式开放外网访问 报告:Django 仍然是 74% 开发者的首选 Zed 编辑器在 Linux 支持方面取得进展 知名开源公司前员工爆料:技术 leader 被下属挑战后狂怒爆粗、辞退怀孕女员工 阿里云正式发布通义千问 2.5 微软向 Rust 基金会捐赠 100 万美元
{{o.name}}
{{m.name}}

추천

출처my.oschina.net/u/4662964/blog/11126406