一、引言
谷歌從1998年至今逐漸成爲一家最有創造力、影響力的公司,其中離不開其創始人提出的PageRank算法。
在給出PageRank算法之前,我們先來回顧一下搜索引擎的工作。
PageRank算法的魅力在於提出了“鏈接關係”用來處理網頁之間的關係,而不是早 期搜索引擎的僅利用關鍵字匹配和簡單的布爾運算來給出搜索結果。
PageRank通過網絡浩瀚的超鏈接關係來確定一個頁面的等級。Google把從A頁面到B頁面的鏈接解釋爲A頁面給B頁面投票,Google根據投票來源(甚至來源的來源,即鏈接到A頁面的頁面)和投票目標的等級來決定新的等級。簡單的說,一個高等級的頁面可以使其他低等級頁面的等級提升。----來自維基百科http://zh.wikipedia.org/wiki/PageRank
至於外鏈的關係何以這樣重要也是好理解的,就像就好比一篇論文被諾貝爾獎得主所引用, 顯然要比被普通研究者所引用更說明其價值。
先給大家一個直觀的列子,網頁B,C,D都有外鏈到網頁A,同時網頁B也有外鏈到C, 且網頁D有外鏈到B和C.
這樣我們粗糙的得到A的PageRank的值,PR(A) = PR(B)/2 + PR(C) + PR(D)/3.
二、數學推導
假設一個虛擬用戶在互聯網上的漫遊過程。 假定: 虛擬用戶一旦訪問了一個網頁後, 下一步將有相同的機率訪問被該網頁所鏈接的任何一個其它網頁。 換句話說, 如果網頁 Wi 有 Ni 個對外鏈接, 則虛擬用戶在訪問了 Wi 之後, 下一步點擊這些鏈接中任何一個的機率均爲 1/Ni。 初看起來, 這一假設並不合理, 因爲任何用戶都有偏好, 怎麼可能以相同的機率訪問一個網頁的所有鏈接呢? 但虛擬用戶實際上是對互聯網上全體用戶的一種平均意義上的代表, 這條假設就不象初看起來那麼不合理了。
那麼網頁的排序由什麼來決定呢? 是由該用戶在漫遊了很長時間 (理論上爲無窮長時間) 後訪問各網頁的機率分佈來決定, 訪問機率越大的網頁排序就越靠前。
pi(n) 表示虛擬用戶在進行第 n 次瀏覽時訪問網頁 Wi 的概率,則
其中,Nj表示頁面Wj的所有外鏈數,Pj->i表示若Wj有到Wi的外鏈則爲1,否則爲0.
爲符號簡潔起見, 我們將虛擬用戶第 n 次瀏覽時訪問各網頁的機率合併爲一個列向量 ,並引進一個只與互聯網結構有關的矩陣 H, 它的第 i 行 j 列的矩陣元爲Hij = pj->i / Nj,上面公式,即:
上面的數學表達是個典型的馬爾可夫過程(維基百科上鏈接),即他的條件概率僅同系統的當前狀態相關,而與他的歷史與未來無關,矩陣H代表的概率就是一個轉移概率。
上面的公式還得解決幾個問題:
- 是否能在N足夠大時得到收斂?
- 若能收斂,和給定的初始值是否有關?
還有一種情況是,我們之前假設的是用戶會順着鏈接一路訪問下去,但實際的情況是用戶中途能夠離開等概率的去其他網頁,設不離開的概率是a,把S修正爲:
不難看出,G矩陣一定是正的,也就是個素矩陣,按照馬爾可夫鏈基本定理,馬爾可夫過程中, 如果轉移矩陣是素矩陣,極限 limn→∞pn 存在,與 p0 的選取無關。這樣前面的兩個問題也有了解決。
至於矩陣G裏面a的選取,google給出的值是0.85。因爲a過大不易收斂,過小難以反映“鏈接分析”的本質,這是個精度和效率的折中。
這樣,所有的問題就是求解下面矩陣的解,
在實際的算法裏,當P(N)和P(N+1)的差值在給定的閾值內,就認爲完成了收斂。
三、算法實現
在介紹算法之前,先介紹一個分析網頁信息較好的一個jar包,htmlparser.jar,目前最新版是1.6。
先新建7個.html文件,只需反映其鏈接關係即可,如
<html>
<body>
<h1> Hello world </h1>
To Test2: <a href="test2.html">Test2</a>
<br/>
To Test3: <a href="test3.html">Test3</a>
</body>
</html>
關係用表格展示如下:
載入頁面的函數:其中HtmlBean是筆者寫的一個Html的javaBean,內容很簡單,有該網頁的路徑,外鏈,排名。
public static void loadHtml() throws IOException, ParserException
{
System.out.println("載入頁面...");
File file = new File(HtmlPath);
// 過濾文件,只尋找html文件
File[] htmlsFiles = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.getName().endsWith(".html"))
return true;
return false;
}
});
init = new double[htmlsFiles.length];
for(int i = 0;i<htmlsFiles.length;i++)
{
File f = htmlsFiles[i];
@SuppressWarnings("resource")
BufferedReader bReader = new BufferedReader(new InputStreamReader(
new FileInputStream(f)));
//讀入文件
String line = bReader.readLine();
StringBuffer html = new StringBuffer();
while(line!=null)
{
html.append(line);
line = bReader.readLine();
}
HtmlBean bean = new HtmlBean();
bean.setPath(f.getName());
// System.out.println("filename: "+f.getName());
// System.out.println("absPath: "+f.getAbsolutePath());
bean.setContent(html.toString());
//HtmlPage Parser處理
Parser parser = Parser.createParser(html.toString(), "utf-8");
HtmlPage page = new HtmlPage(parser);
parser.visitAllNodesWith(page);
NodeList nodeList = page.getBody();
//在NodeList中執行過濾器時,第二個參數爲True
nodeList = nodeList.extractAllNodesThatMatch(new TagNameFilter("A"),
true);// 尋找<a>節點
for(int j=0;j<nodeList.size();j++)
{
LinkTag outLinkTag = (LinkTag) nodeList.elementAt(j);
//尋找外鏈的href的值
bean.getOutLinks().add(outLinkTag.getAttribute("href"));
}
map.put(bean.getPath(), bean);
list.add(bean);
// 初始值
init[i] = 0.0;
}
}
執行PageRank的方法:其中DAMP的阻尼係數,0.85,即上文提到的概率a.
private static double[] doPageRank()
{
double[] pr = new double[init.length];
for(int i=0;i<init.length;i++)
{
double temp = 0.0;
HtmlBean htmlBean = list.get(i);
for(int j=0;j<init.length;j++)
{
HtmlBean htmlBean2 = list.get(j);
// 計算對本頁面的鏈接
if(i!=j&&htmlBean2.getOutLinks().size()!=0
&&htmlBean2.getOutLinks().contains(htmlBean.getPath()))
{
temp = temp + init[j]/htmlBean2.getOutLinks().size();
}
}
pr[i] = DAMP + (1-DAMP)* temp;
}
return pr;
}
/**
* 若前後兩次的值相差小於閾值,認爲完成收斂
* @return
*/
private static boolean check()
{
boolean flag = true;
for(int i=0;i<pr.length;i++)
{
if(Math.abs(pr[i]-init[i])>MAX)
{
flag = false;
break;
}
}
return flag;
}
主函數的核心代碼:
loadHtml();
pr = doPageRank();
while(!check())
{
System.arraycopy(pr, 0, init, 0, init.length);
pr = doPageRank();
}
排序過程:
// 比較器的實現
Collections.sort(list,new Comparator() {
@Override
public int compare(Object o1, Object o2) {
HtmlBean h1 = (HtmlBean) o1;
HtmlBean h2 = (HtmlBean) o2;
int em = 0;
if(h1.getPr()>h2.getPr())
{
em = -1;
}
else
{
em = 1;
}
return em;
}
});
結果: