求眾數是乙個古老的問題。眾數:是一組資料中出現次數最多的數值。求眾數的主要演算法有:
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值、、這個沒什麼問題,直接上**
1、treap的左右旋轉這裡的思想和treap有點接近,而且旋轉操作和treap是一樣一樣的。可以參考我的其他文章typedef
struct node;
typedef node *tree;
struct node ;
tree root;
在這裡用一幅**釋左右旋轉(注意具體方法):
**給出乙個例項:
2、左右旋轉在什麼時候做左右旋轉是為了維護性質三中 孩子的times比父母小(即越靠近根出現次數越多)這一性質。那麼什麼時候旋轉最合適呢?顯然如果專門來維護性質很不划算。void liftleftchild(tree &node)
/*左節點上旋*/
void liftrightchild(tree &node)
/*右節點上旋*/
我們考慮什麼能破壞性質:顯然,只有插入資料才能導致乙個節點的times發生改變,從而使它的times大於其父親,破壞性質3。那麼,不妨在插入後檢查是否違反性質,如果違反就進行旋轉。
插入pushkey**如下:
3、剩下的問題void pushkey(tree &n, int key)
else
if (n->key == key) else
if (n->key > key) else
if (n->key < key)
}
4、完整**// 獲取眾數出現次數
int maxtimes()
/*獲取乙個眾數(根節點)
* 如果要獲取所有眾數也很方便
* 直接遞迴向下找所有times = maxtimes()的節點即可
*/int mode()
/*如果認(jiao)真(qing)還可以寫個刪除,和bst完全一樣*/
inline
void del(tree &nd)
}
5、演算法正確性分析演算法的正確性是顯然的。如果pushkey導致乙個節點的孩子的times更大,必定會觸發上旋,一路上旋到需要的地方(即性質3一定不會被破壞)。特別的,這種演算法可以很方便的求出第二眾數、第三眾數等等。#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;}
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 ...