求眾數的演算法研究

2021-07-10 01:53:14 字數 3527 閱讀 3798

求眾數是乙個古老的問題。眾數:是一組資料中出現次數最多的數值。求眾數的主要演算法有:

1,hash表 時間複雜度為o(n),但空間極大,通常讓人難以承受

2,排序 對元素表進行排序,然後統計元素出現的個數,得出眾數。時間複雜度為o(nlgn),空間複雜度為o(n)

3,二叉搜尋樹 用rbtree之類的樹來實現。如果實現的好,複雜度和排序接近。

這三種方法各有所長,但是都有一些問題。所以最近我腦洞大開,想擴張二叉搜尋樹以實現更簡單、更高效的眾數演算法。這個演算法的複雜度約為o(nlgn),但是實際來看,效率比普通的二叉樹實現效率高得多。看一下簡單的效能測試(隨機資料):

資料量/n

mytreetimes/ms

std::maptimes/ms

pbds::rbtreetimes/ms

std::sorttimes/ms

100000

1578

9332

1000000

143811

936343

10000000

1435

8253

9397

4040

可見,這種方法比現有的樹演算法優勢明顯,對於排序方法也有一定優勢(當然這也有手寫快於封裝的因素)。但這還只是隨機資料的測試,如果資料是特殊的(眾數出現次數很多),效率會更高。

好了關子賣完了,這種演算法的思路很簡(ju)單(ruo):對於乙個bst,每乙個node記錄兩個值:key和times(數字和出現的次數)。bst基於key構建,而每次插入時,一旦當前節點子樹的times大於當前節點的times,就把子樹上旋。經過多次插入後,根的key即是乙個眾數,times即是它出現的次數。

對於這種樹(下面稱modetree)的定義是:

空樹是modetree

乙個對稱為乙個節點

如果對於任意乙個節點n,n的左孩子是空樹或者左孩子的key小於n的key,n的右節點是空樹或者右孩子的key大於n的key,且n的times大於或等於它的孩子的times,則n是modetree

如果還是不太懂,我們就把需要的知識複習一下。

0、資料結構

普通二叉樹多乙個times值、、這個沒什麼問題,直接上**

typedef

struct node;

typedef node *tree;

struct node ;

tree root;

1、treap的左右旋轉這裡的思想和treap有點接近,而且旋轉操作和treap是一樣一樣的。可以參考我的其他文章

在這裡用一幅**釋左右旋轉(注意具體方法):

**給出乙個例項:

void liftleftchild(tree &node)

/*左節點上旋*/

void liftrightchild(tree &node)

/*右節點上旋*/

2、左右旋轉在什麼時候做左右旋轉是為了維護性質三中 孩子的times比父母小(即越靠近根出現次數越多)這一性質。那麼什麼時候旋轉最合適呢?顯然如果專門來維護性質很不划算。

我們考慮什麼能破壞性質:顯然,只有插入資料才能導致乙個節點的times發生改變,從而使它的times大於其父親,破壞性質3。那麼,不妨在插入後檢查是否違反性質,如果違反就進行旋轉。

插入pushkey**如下:

void pushkey(tree &n, int key)

else

if (n->key == key) else

if (n->key > key) else

if (n->key < key)

}

3、剩下的問題

// 獲取眾數出現次數

int maxtimes()

/*獲取乙個眾數(根節點)

* 如果要獲取所有眾數也很方便

* 直接遞迴向下找所有times = maxtimes()的節點即可

*/int mode()

/*如果認(jiao)真(qing)還可以寫個刪除,和bst完全一樣*/

inline

void del(tree &nd)

}

4、完整**

#include

#include

#include

using namespace std;

typedef struct node;

typedef node *tree;

struct node ;

tree root;

inline

void liftleftchild(tree &node)

inline

void liftrightchild(tree &node)

inline

void pushkey(tree &n, int key)

else

if (n->key == key) else

if (n->key > key) else

if (n->key < key)

}inline int maxtimes()

inline int mode()

inline int times(tree nd, int key)

int main()

printf("%d\n", mode());

return

0;}

5、演算法正確性分析演算法的正確性是顯然的。如果pushkey導致乙個節點的孩子的times更大,必定會觸發上旋,一路上旋到需要的地方(即性質3一定不會被破壞)。特別的,這種演算法可以很方便的求出第二眾數、第三眾數等等。

6、演算法複雜度分析

由測試來看,這種方法的效率是很高的。因為出現頻率更高的數更有可能是眾數,上旋可以使下次操作更快。如果眾數出現次數特別多,許多操作都只需要常數時間。

顯然在最壞情況下,複雜度為o(n^2),即數字都不重複而且是單調的。對於這種情況,可以使用隨機化上旋出現次數相同(times相同)的節點的方法來解決,實現非常簡單。(這樣實現的二叉樹有高概率是良好的平衡樹?請大神解釋)

在最好情況下,即眾數出現次數充分多時,複雜度為o(n),幾乎和hash媲美。

在通常情況下,由於這樣的方法類似於treap,複雜度應該為o(nlgn)。

從總體上看,這種方法是很優秀的(特別是便於實現)。

7、總結

這是對二叉排序樹(更準確地是對treap)的一種擴張,事實證明這種方法很成功。看來,擴張已有的資料結構是一種很好的學習方法。

演算法 求眾數

給定乙個大小為 n 的陣列,找到其中的眾數。眾數是指在陣列 現次數大於 n 2 的元素。你可以假設陣列是非空的,並且給定的陣列總是存在眾數。示例 1 輸入 3,2,3 輸出 3 示例 2 輸入 2,2,1,1,1,2,2 輸出 2class solution else return curnum c...

leetcode 演算法 求眾數 169

leetcode 傳送門 給定乙個大小為 n 的陣列,找到其中的眾數。眾數是指在陣列 現次數大於 n 2 的元素。你可以假設陣列是非空的,並且給定的陣列總是存在眾數。示例 1 輸入 3,2,3 輸出 3 示例 2 輸入 2,2,1,1,1,2,2 輸出 2 本題是求陣列 現次數大於一半的元素。乙個基...

169 求眾數 229 求眾數 II

不限定時間複雜度的話,很多人會先排序,再遍歷的方法來做。不限定空間複雜度的話,很多人會用hash表來做。那麼,有了這兩個限定,就只能用摩爾投票演算法了。主元素問題典型解法。摩爾投票演算法 時間複雜度o n 空間複雜度o 1 class solution else if nums i ans cnt ...