序章
これが可能な解決策です。それはかなり不自然で実用的ではありませんが、問題もそうです。分析に穴があればコメントをいただければ幸いです。これが「公式」ソリューションの宿題や課題の問題である場合は、質問されてから1か月以上経過していることを考えると、元のポスターがまだ残っているかどうかも確認したいと思います。
まず、問題の詳細が特定されていないことをいくつか具体化する必要があります。必要な時間計算量はですがO(N)
、何N
ですか?ほとんどのコメンテーターN
は、配列内の要素の数を想定しているようです。配列内の数値が固定の最大サイズである場合、これは問題ありません。その場合、基数ソートのMichaelGのソリューションで問題が解決されます。しかし、私は制約#1を、元の投稿者による明確化がない限り、最大桁数を固定する必要はないと言っていると解釈します。したがって、n
(小文字)が配列内の要素の数であり、要素m
の平均の長さである場合、競合する入力の合計サイズはですmn
。解決時間の下限は次のとおりです。O(mn)
これは、ソリューションを検証するために必要な入力の読み取り時間であるためです。したがって、合計入力サイズに関して線形であるソリューションが必要ですN = nm
。
たとえば、平均的な長さの要素でn = m
あるがあります。比較ソートには操作が必要ですが、操作自体には平均して時間がかかるため、これは勝利ではありません。sqrt(N)
sqrt(N)
O( log(N) sqrt(N) ) < O(N)
O(m) = O(sqrt(N))
O( N log(N) )
また、基数ソートは、平均の長さではなく最大O(mn) = O(N)
の長さである場合m
に使用されます。数値が一定の範囲内にあると仮定した場合、最大長と平均長は同じオーダーになりますが、そうでない場合は、桁数が大きく可変の場合はパーセンテージが小さく、桁数が少ない場合はパーセンテージが大きくなる可能性があります。 。たとえば、数値の10%が長さで、90%が長さである可能性があります。平均の長さはですが、最大の長さなので、基数ソートはになります。m^1.1
m*(1-10%*m^0.1)/90%
m
m^1.1
O(m^1.1 n) > O(N)
問題の定義を大幅に変更したという懸念がないように、私の目標は、要素の数に比例する時間計算量を持つアルゴリズムを記述することですO(n)
。ただし、各要素の長さに対して線形時間計算量の操作を実行する必要もあります。これにより、すべての要素の平均でこれらの操作はになりますO(m)
。これらの演算は、要素のハッシュ関数と比較を計算するために必要な乗算と加算になります。そして、実際にこのソリューションがの問題を解決する場合O(N) = O(nm)
、答えを検証するのに同じ時間がかかるため、これは最適な複雑さであるはずです。
問題の定義から省略されているもう1つの詳細は、データの処理中にデータを破棄できるかどうかです。わかりやすくするためにそうしますが、細心の注意を払えば回避できると思います。
考えられる解決策
まず、負の数が存在する可能性があるという制約は空です。データを1回通過するだけで、最小要素z
数と要素数を記録しn
ます。2回目のパスでは、(3-z)
各要素に追加するため、最小の要素は3になります(結果として一定数の数値がオーバーフローする可能性があるため、最初にデータを一定数追加パスしてテストする必要があります)これらはソリューション用です。)ソリューションができたら、単に減算(3-z)
して元の形式に戻します。これで、それ自体は要素ではない3つの特別なマーカー値、、、およびが使用可能0
に1
なりました。2
ステップ1
中央値の中央値選択アルゴリズムを使用してp
、配列の90パーセンタイル要素を決定しA
、配列を2つのセットのセットに分割しますS
。T
ここで、S
は10% of n
より大きい要素p
をT
持ち、はより小さい要素を持ちp
ます。これにはO(n)
、ステップ(合計O(m)
で平均してステップがかかる)時間がかかります。O(N)
一致する要素は、またはのp
いずれかに配置できますが、簡単にするために、配列を1回実行し、テストして、。に置き換えて削除します。セットは元々インデックスにまたがっており、ここで、は約であり、セットS
T
p
0
S
0..s
s
10%
n
T
インデックスの残りの90%にまたがりますs+1..n
。
ステップ2
次に、ループしてi in 0..s
、要素ごとにハッシュ関数をe_i
計算します。一様分布を得るためにユニバーサルハッシュを使用します。したがって、ハッシュ関数は乗算と加算を実行し、各要素の長さに関して線形時間を要します。h(e_i)
s+1..n
衝突には、修正された線形プロービング戦略を使用します。
h(e_i)
のメンバーによって占有されているT
(つまり、マーカーまたはA[ h(e_i) ] < p
ではありません)またはです。これはハッシュテーブルのミスです。スロットとから要素を交換して挿入します。1
2
0
e_i
i
h(e_i)
h(e_i)
S
(を意味するA[ h(e_i) ] > p
)またはマーカー1
またはのメンバーによって占められてい2
ます。これはハッシュテーブルの衝突です。またはの重複またはe_i
メンバーに遭遇するまで、線形プロービングを実行します。T
0
のメンバーの場合T
、これもハッシュテーブルの欠落であるため、スロットにスワップしてのようe_i
に挿入します。(1.)
i
の重複の場合e_i
、これはハッシュテーブルヒットです。次の要素を調べます。その要素が1
またはである場合は、すでに何度も2
見てきましたが、 sをsに変更し、その逆を行って、パリティの変更を追跡します。次の要素がまたはでない場合は、前に1回しか見たことがありません。次の要素にを格納して、偶数回見たことを示します。次の「空の」スロットを探します。これは、スロットまたは0に移動するメンバーによって占有されているスロットであり、要素を上にシフトしてインデックスを下に移動し、次にパリティ情報を格納するためのスペースを確保します。 。保存する必要がないことに注意してくださいe_i
1
2
1
2
e_i
2
e_i
T
i
h(e_i)+1
h(e_i)
e_i
再びそれ自体なので、余分なスペースを使い果たしていません。
つまり、基本的に、ハッシュしたい要素の9倍のスロット数を持つ機能的なハッシュテーブルがあります。ヒットを取得し始めると、パリティ情報の保存も開始するため、スロット数が4.5倍になり、負荷率が非常に低くなる可能性があります。ここで機能する可能性のある衝突戦略はいくつかありますが、負荷率が低いため、衝突の平均数も少なく、線形プロービングは平均して適切な時間計算量でそれらを解決する必要があります。
ステップ3
0..s
intoの要素のハッシュが終了したらs+1..n
、をトラバースしs+1..n
ます。Sの要素の後にaが続く場合2
、それが目標要素であり、完了です。の要素e
のS
後に別のS
指示要素が続く場合は、e
一度だけ検出され、ゼロにすることができます。同様に、奇数回見たe
という意味が続き、とマーカーをゼロにすることができます。1
e
e
1
必要に応じてすすぎ、繰り返します
目標要素が見つからない場合は、プロセスを繰り返します。90パーセンタイルパーティションは、n
残りの最大要素の10%を最初に移動A
し、残りの要素(空の0
マーカースロットを含む)を最後に移動します。以前と同じようにハッシュを続行します。毎回10%を処理するため、これを最大10回実行する必要がありますn
。
結論分析
中央値の中央値アルゴリズムを介したパーティショニングの時間計算量はO(N)
、10回ですが、それでもO(N)
です。O(1)
ハッシュテーブルの負荷が低く、合計O(n)
でハッシュ操作が実行されるため、各ハッシュ操作は平均して実行されます(10回の繰り返しごとにnの約10%)。各要素には、それらに対して計算されたハッシュ関数があり、時間計算量はそれらの長さに線形であるため、平均してすべての要素にわたってです。したがって、集約されたハッシュ操作はです。したがって、これを適切に分析した場合、全体としてこのアルゴリズムはです。(これは、加算、乗算、比較、およびスワッピングの演算が入力に対して一定時間であると想定される場合も同様です。)n
O(m)
O(mn) = O(N)
O(N)+O(N)=O(N)
O(n)
このアルゴリズムは、1つの要素のみが偶数回発生するという問題定義の特殊な性質を利用していないことに注意してください。問題定義のこの特別な性質を利用しなかったということは、より良い(より賢い)アルゴリズムが存在する可能性を残しますが、最終的にはO(N)でなければなりません。