跳到主要内容

東京大学 情報理工学系研究科 電子情報学専攻 2016年8月実施 専門 第3問

Author​

adj-matrix, 祭音Myyura

Description​

ある Web サービスに対し、異常に高頻度でアクセスするユーザをアクセスログから見つけたい。各ユーザには一意なユーザ ID が割り当てられており、アクセスログにはアクセスしたユーザの ID が時系列順で記録されている。このとき、以下の問いに答えよ。

(1) アクセスログにおいて、全体の過半数のアクセスを行っているユーザがいるかどうかを、全ユーザに対する頻度ヒストグラム等を生成することなく検証したい。そこで、以下のようなアルゴリズムを考えた。

  • i. 空リスト {}\{\} で初期化されたリスト LL を用意する。
  • ii. 配列 AA の要素に先頭からアクセスし、ii 番目の要素 A[i]A[i] について、そのときの LL の値に応じて以下のいずれかの処理を行う。
    • ii-(a). LL が空である場合、A[i]A[i] を LL に追加する。
    • ii-(b). LL が空でない場合、A[i]A[i] が LL に含まれていれば LL に追加し、含まれていなければ LL から任意の要素を1つ取り除く。
  • iii. LL を出力する。

このアルゴリズムを用いて以下のユーザ ID の系列を先頭の要素から処理し、ii で各要素を処理した後のリスト LL の値を順に示せ。

11,10,11,11,7,11,11,3,811,10,11,11,7,11,11,3,8

(2) (1) のアルゴリズムにおいて、LL に含まれる要素の種類数は高々一つであることを証明せよ。

(3) アクセスログにおいて過半数を占めるユーザ uMAJORITYu_{\mathrm{MAJORITY}} が存在するとき、uMAJORITYu_{\mathrm{MAJORITY}} は (1) のアルゴリズムが出力するリスト LL に含まれる唯一のユーザであることを証明せよ。

(4) (1) のアルゴリズムでは、配列 AA のサイズが非常に大きい場合にリスト LL のサイズが問題となる場合がある。この点に注意し、LL をリストとして陽に表現しないことで空間効率を改善した上で、アクセスログからユーザ ID を1つずつ順に読み込む関数 read_log() を用いて (1) のアルゴリズムを擬似コードで示せ。

なお、read_log() は最初に呼び出されるとアクセスログの先頭のユーザ ID を返し、以降、呼び出すたびに続くユーザ ID を返す。また、アクセスログの末尾に到達すると −1-1 を返すものとする。

题目描述​

希望从 Web 服务的访问日志中找出访问次数极多的用户。每位用户有唯一 ID,日志按时间顺序记录每次访问者的 ID。回答下列问题。

(1) 为判断日志中是否存在占全部访问次数过半的用户,同时不为所有用户建立频数直方图,考虑以下算法:

  1. 准备列表 LL,初始为空列表 {}\{\}。
  2. 从头依次访问数组 AA 的元素。处理第 ii 个元素 A[i]A[i] 时:
    • 若 LL 为空,将 A[i]A[i] 加入 LL;
    • 若 LL 非空且 A[i]A[i] 已包含在 LL 中,再加入一个 A[i]A[i];否则从 LL 中任意删除一个元素。
  3. 输出 LL。

按顺序用该算法处理用户 ID 序列

11,10,11,11,7,11,11,3,8,11,10,11,11,7,11,11,3,8,

依次写出步骤 2 每次处理 A[i]A[i] 后 LL 的值。

(2) 证明在 (1) 的算法中,LL 所含用户 ID 的种类数至多为 11。

(3) 若日志中确实存在多数用户 uMAJORITYu_{\mathrm{MAJORITY}},证明它是 (1) 的算法最终输出列表 LL 中唯一种类的元素。

(4) 当数组 AA 很大时,显式保存列表 LL 会造成空间问题。使用每次从日志读取一个用户 ID 的函数 read_log(),写出不显式表示 LL、空间效率更高但实现同一算法的伪代码。read_log() 第一次调用返回日志中的第一个用户 ID,随后逐次返回后续 ID,到达日志末尾时返回 −1-1。

Kai​

(1)​

処理した ID処理後の LL
11{11}\{11\}
10{}\{\}
11{11}\{11\}
11{11,11}\{11,11\}
7{11}\{11\}
11{11,11}\{11,11\}
11{11,11,11}\{11,11,11\}
3{11,11}\{11,11\}
8{11}\{11\}

(2)​

帰納法で示す。初期状態では LL は空である。処理前の LL が空なら1要素を追加するので種類数は1となる。空でなければ、その唯一の種類と同じ ID を追加するか、既存要素を1つ削除するだけなので、種類数は高々1に保たれる。

(3)​

削除操作は、異なる ID の2要素(入力要素と LL 内の要素)を組にして除く操作とみなせる。過半数ユーザを uu とすると、uu の出現数は他の全ユーザの出現数の和より多い。従って、異なる ID を何組取り除いても uu は少なくとも1個残る。(2) より最終的な LL の種類は高々1なので、その唯一の種類は uu である。

(4)​

LL を「候補 ID とその個数」で表す。

FindCandidate():
candidate = NONE
count = 0
while true:
id = read_log()
if id == -1:
break
if count == 0:
candidate = id
count = 1
else if id == candidate:
count = count + 1
else:
count = count - 1
if count == 0:
return (NONE, 0)
return (candidate, count)

この組は元の出力リストを圧縮して表し、追加空間は O(1)O(1)、時間はログ長に対して O(N)O(N) である。過半数ユーザの存在そのものを判定するには、候補の実際の出現数を再走査で数え、N/2N/2 を超えることを確認する。