Java(コンピュータ関連)面接大量データ問題処理(1)分割統治・ハッシュ・ソート

面接では大量データ処理についてよく質問されますが、以下では大量データ処理に関するいくつかのアイデアと事例を段階的に紹介します。

理解した上で面接を恐れないでください

ビッグデータ処理のアイデア: 分割統治/ハッシュ マッピング + ハッシュマップ統計 + ヒープ/クイック/マージ ソート

  • 分而治之/hash映射: データが大きすぎてメモリが限られているため、次のことしかできません: 大きなファイルを小さなファイルに変換 (モジュロ マッピング)、つまり 16 文字のポリシー: 大小、それぞれを分割し、スケールを縮小し、それらを一つ一つ解決していきます

  • hash_map统计: 大きなファイルが小さなファイルに変換される場合、頻度統計には従来の hash_map (ip, value) を使用できます。

  • 堆/快速排序: 統計が完了したら、ソート (ヒープ ソートを使用できます) を実行して、最も回数の多い IP を取得します。  

ケース 1: 大量のログ データ、特定の日に Baidu へのアクセス数が最も多かった IP を抽出する

分析: 「まず、この日ですが、Baidu への訪問ログにある IP を 1 つずつ取り出して、大きなファイルに書き込みます。IP は 32 ビットで、最大 2^32 個あることに注意してください。」 IP マッピングも使用可能 %1000 などの方法では、大きなファイル全体を 1000 個の小さなファイルにマッピングし、各小さなファイル内で最も高い頻度を持つ IP を見つけます (hash_map を使用して、すべての IP の頻度統計を実行できます) 「1000 個のファイル内で、それらを順番に見つけます。各ファイルで最も高い頻度を持つ IP と、対応する頻度を見つけます。次に、1000 個の最大の IP の中で最も高い頻度を持つ IP を見つけます。これが目的のものです。」

このトピックに関しては、さらに次のような質問があります。

  • ハッシュ モジュロは同等のマッピングです。同じ要素が異なる小さなファイルに分散される状況はありません。つまり、ここでは mod1000 アルゴリズムが使用されます。その場合、同じ IP は、ハッシュ モジュロの後にのみ同じファイルに含まれます。これは、同じファイルには含まれません。分散される。2 つの IP が等しい場合、Hash(IP) の後のハッシュ値は同じであり、ハッシュ値のモジュロ (モジュロ 1000 など) も依然として等しい必要があるためです。
  • では、ハッシュ マッピングとは正確には何ですか? 簡単に言うと、コンピューターが限られたメモリ内でビッグ データを処理しやすくすることで、マッピング ハッシュ法 (たとえば、残り) メソッドは小さなツリーにマップされてメモリに保存されるか、大きなファイルが複数の小さなファイルにマップされます)、このマッピング ハッシュ メソッドは通常ハッシュ関数と呼ばれるものです。適切に設計されたハッシュ関数は、データが均等に分散され、競合が軽減されます。データが別の別の場所にマッピングされても、データは元のデータのままですが、これらの元のデータを置き換えて表現する形式が変更されています。

ケース 2:人気のあるクエリを検索し、300 万のクエリ文字列の中から最も人気のある 10 個のクエリを数えます

検索エンジンは、ユーザーが検索ごとに使用したすべての検索文字列をログ ファイルに記録します。各クエリ文字列の長さは 1 ~ 255 バイトです。現在 1,000 万件のレコードがあるとします (これらのクエリ文字列の繰り返しは比較的多く、合計は 1,000 万件ですが、繰り返しを除くと 300 万件を超えることはありません。クエリ文字列の繰り返しが多いほど、これはクエリを実行することを意味します (ユーザーが多いほど人気が​​高くなります)。最も人気のある 10 個のクエリ文字列を数えてください。必要なメモリは 1G を超えることはできません。

回答: 上記の最初の質問から、大きなデータは小さなデータとして分類されることがわかります。たとえば、トップ 10 を見つけるために 1 億の IP がある場合、まず IP を %1000 で 1000 個の小さなファイルに分割し、 1 つのタイプの IP がファイル内に表示され、各小さなファイル内の IP に対してハッシュマップ カウント統計を実行し、数量ごとに並べ替えます。最後にマージまたはミニヒープを実行して、各小さなファイルの上位 10 個を順番に処理して、最終結果を取得します。 。

しかし、データ サイズが比較的小さい場合、一度にメモリにロードできますか? たとえば、2 番目の質問では、1,000 万個のクエリがありますが、繰り返しが多いため、実際には 3 個しかありません。 100 万個のクエリがあり、各クエリは 255 バイトであるため、すべてをメモリに入れることを検討できます (300 万個の文字列は繰り返しがなく、最大長であると想定されているため、最大メモリ使用量は 3M*1K/4=0.75G になります)したがって、すべての文字列はメモリ処理に保存できます)、必要なのは適切なデータ構造だけですが、ここでは間違いなく HashTable が推奨されます。

したがって、分割統治/ハッシュ マッピングのステップを放棄し、ハッシュ統計に直接アクセスしてから並べ替えます。したがって、このような典型的な TOP K 問題の場合、多くの場合、ハッシュマップ + ヒープで対策が行われます。次のように:

  • hash_map统计: まず、この大量のデータのバッチを前処理します。具体的な方法は次のとおりです。キーがクエリ文字列、値がクエリの出現回数であるハッシュテーブルを維持します(つまり、hash_map(Query, Value))。文字列がクエリ文字列にない場合は、毎回クエリを読み取ります。 「テーブル」に文字列を追加し、「値」の値を 1 に設定します。文字列がテーブルにある場合は、文字列のカウントに 1 を加えます。最終的に、ハッシュ テーブルを使用して、O(N) の時間計算量内で統計を完了しました。ヒープの並べ替え: 2 番目のステップは、ヒープのデータ構造を使用して上位 K を見つけ、時間計算量は N です。 'logK. つまり、ヒープ構造の助けを借りて、ログレベルの時間を見つけて調整/移動できます。したがって、サイズ K (このトピックでは 10) の小さなルート ヒープを維持し、300 万件のクエリを走査してルート要素とそれぞれ比較します。したがって、最終的な時間計算量は次のようになります: O(N) + N' * O(logK) (N は 1,000 万、N' は 300 万)。

この記事で説明されているヒープ ソートの考え方を忘れないでください。「k 要素の最小ヒープを維持する、つまり、走査された最初の k 個の数値を格納するために容量 k の最小ヒープを使用し、それらが最大の k 個であると仮定します。」数値、ヒープの構築には O(k) 時間がかかり、ヒープの調整後 (時間のかかる O(logk))、k1>k2>...kmin になります (kmin は小さな上部ヒープの最小要素として設定されます) ).要素 x がヒープの最上位要素と比較されるたびに、シーケンスをトラバースし続けます。x>kmin の場合、ヒープは更新されます (x がヒープに入り、時間は logk になります)。それ以外の場合、ヒープは更新されませんこのようにして、合計時間は O(k*logk+(nk) logk)=O ( n logk) となります。この方法は、ヒープ内での検索などのさまざまな操作の時間計算量が logk であるという事実を利用します。 " -- 第 III 章の続き、上位 K アルゴリズム問題の実装。もちろん、トライ木を使用することもできます。キーフィールドにはクエリ文字列の出現回数が格納され、出現しない場合は 0 になります。最後に、最小プッシュ 10 要素を使用して出現頻度を並べ替えます。

ケース 3: サイズが 1G のファイルがあり、ファイル内の各行はワードであり、ワードのサイズは 16 バイトを超えず、メモリ制限は 1M です。頻度が最も高い 100 個の単語を返します。

  • 分而治之/hash映射: ファイルを読み取るには、単語 x ごとに hash(x)%5000 を取得し、その値を 5000 個の小さなファイル (x0、x1、...x4999 としてマーク) に保存します。このように、各ファイルは約 200k 程度になります。一部のファイルのサイズが 1M を超える場合は、分解された小さなファイルのサイズが 1M を超えなくなるまで、同様の方法で分割を続けることができます。
  • hash_map统计: 小さなファイルごとに、トライ ツリー/ハッシュマップなどを使用して、各ファイルに出現する単語と対応する頻度をカウントします。
  • 堆/归并排序: 出現頻度が最も高い 100 個の単語を取り出した後 (100 ノードの最小ヒープを使用できます)、100 個の単語とその対応する頻度をファイルに保存し、さらに 5000 個のファイルを取得します。最後に、これら 5000 個のファイルを結合するプロセスです (結合と並べ替えに似ています)。

ケース 4:大量のデータが 100 台のコンピューターに分散されている場合、このバッチのデータの TOP10 を効率的にカウントする方法を見つける

各データ要素が 1 回だけ出現し、特定のマシンにのみ出現する場合、次の手順を実行して、TOP10 出現回数を持つデータ要素をカウントできます。

  • 堆排序: 各コンピューターで TOP10 を見つけるには、10 個の要素を含むヒープを使用して完了できます (TOP10 が小さい場合は最大のヒープを使用し、TOP10 が大きい場合は最小のヒープを使用します。たとえば、TOP10 を見つけるには、最初に最初の 10 個を取得します)要素を取得して最小のヒープに調整し、見つかった場合は、次のデータをスキャンしてヒープの先頭要素と比較し、ヒープの先頭要素より大きい場合は、ヒープの先頭をこれに置き換えます要素を選択し、それを最小ヒープに調整します。ヒープ内の最後の要素は TOP10 の大きさです)。各コンピュータの TOP10 を見つけたら、これら 100 台のコンピュータの TOP10、合計 1000 個のデータを結合し、上記と同様の方法を使用して TOP10 を見つけます。

しかし、次の例に示すように、同じ要素が異なるコンピューターで繰り返し出現する場合はどうなるでしょうか。現時点では、2 つの方法が考えられます。

  • すべてのデータを走査して再ハッシュして、同じ要素が 1 台のコンピューターにのみ出現することを確認し、上記の方法を使用して各コンピューター内の各要素の出現数を数えて TOP10 を見つけます。各コンピューターで 100 個の TOP10 を組み合わせて、最終的な TOP10 を見つけます。
  • または、暴力的な解決策: 各コンピュータでの各要素の出現数を直接数え、次に異なるマシンでの同じ要素の出現数を加算し、最終的にすべてのデータから TOP10 を見つけます。

ケース 5:ファイルが 10 個あり、各ファイルは 1G で、各ファイルの各行にユーザーのクエリが格納され、各ファイルのクエリが繰り返される可能性があります。クエリの頻度で並べ替える必要があります

プラン1:

  • hash映射: 10 個のファイルを順番に読み取り、hash(query)%10 の結果に従って別の 10 個のファイル (a0、a1、..a9 として記録) にクエリを書き込みます。このようにして、新しく生成される各ファイルのサイズは約 1G になります (ハッシュ関数がランダムであると仮定します)。
  • hash_map统计: 約 2G のメモリを備えたマシンを見つけ、hash_map(query, query_count) を順番に使用して、各クエリの発生数をカウントします。注: hash_map(query,query_count) は、値を保存するためではなく、各クエリの出現回数をカウントするために使用されます。1 回出現すると、count+1 になります。ヒープ/クイック/マージ ソート: 高速/ヒープ/マージ ソートを使用して出現数に従ってソートし、ソートされたクエリと対応する query_cout をファイルに出力し、ソートされた 10 個のファイル ( で示されます) を取得します。最後に、これら 10 個のファイルに対してマージ ソート (内部ソートと外部ソートの組み合わせ) を実行します。このスキーム 1 によると、実装は次のとおりです: https://github.com/ooooola/sortquery/blob/master/querysort.py。

解決策 2: 一般に、クエリの総量は制限されていますが、繰り返し回数は比較的多いため、すべてのクエリを一度にメモリに追加できる可能性があります。このようにして、トライ ツリー/ハッシュ マップを使用して各クエリの出現数を直接カウントし、出現数に応じて高速/ヒープ/マージ ソートを実行できます。

解決策 3: 解決策 1 と似ていますが、ハッシュが完了して複数のファイルに分割された後、分散アーキテクチャ (MapReduce など) を使用して処理のために複数のファイルに渡され、最後にマージされます。

ケース 6: 2 つのファイル a と b があり、それぞれに 50 億の URL が保存され、各 URL が 64 バイトを占有し、メモリ制限が 4G であるとします。ファイル a と b の共通 URL を調べてみましょう。

各ファイルのサイズは 5G×64=320G と推定でき、メモリの制限がある 4G よりもはるかに大きくなります。したがって、処理のためにメモリに完全にロードすることはできません。分割統治アプローチを検討する

分而治之/hash映射: ファイル a をスキャンし、各 URL の hash(url)%1000 を計算し、取得した値に従って 1000 個の小さなファイル (a0、a1、...、a999 としてマーク) に URL を保存します。このように、小さなファイルはそれぞれ約 300M です。ファイル b をトラバースし、a と同じ方法で URL を 1000 個の小さなファイル ( として記録) に保存します。このように処理した後、同じである可能性のあるすべての URL が対応する小さなファイル () 内に存在し、対応しない小さなファイルが同じ URL を持つことは不可能になります。そうすれば、1000 組の小さなファイルから同じ URL を見つけるだけで済みます。

hash_set统计: 小さいファイルの各ペアで同じ URL が見つかった場合、小さいファイルの 1 つの URL を hash_set に保存できます。次に、別の小さなファイルの各 URL を走査して、構築したばかりの hash_set に含まれているかどうかを確認します。含まれている場合は、それが共通の URL であるため、ファイルに保存するだけです。

ケース 7: 100w の数値の中から最大の 100 の数値を見つけます。

オプション 1: ローカル消去法を使用します。最初の 100 個の要素を選択して並べ替えると、シーケンス L として記録されます。次に、残りの要素 x を 1 回スキャンし、ソートされた 100 個の要素のうち最小の要素と比較し、最小の要素より大きい場合は、最小の要素を削除し、挿入ソートの考え方を使用してシーケンスに x を挿入します。 Lで。すべての要素がスキャンされるまで順番にループします。複雑さは O(100w*100) です。

解決策 2: クイック並べ替えのアイデアを使用し、各分割後に軸より大きい部分のみを考慮し、軸より大きい部分が 100 を超える場合は、従来の並べ替えアルゴリズムを使用して並べ替えて最初の 100 を取得します。複雑さは O(100w*100) です。

解決策 3: 前の質問で、100 個の要素を含む最小ヒープで完了することをすでに述べました。複雑さは O(100w*lg100) です。

おすすめ

転載: blog.csdn.net/a619602087/article/details/130348569