【OJ | 力扣347】输出前 K 个高频元素
题目
- 描述:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
- 条件:元素为整数,取值范围为 int 整形。1 ≤ k ≤ 数组中不相同的元素的个数,元素出现频率不同。
- 要求:算法的时间复杂度为 O(n log n) , n 是数组的大小。
- 输入数据格式:输入内容中分号前为整数数组,分号后为 k。
示例
输入:
1,1,1,2,2,3;2
输出:
1
2
解读
整道题分为两个部分:
- 遍历给定的整数数组并统计元素出现频率。遍历过程采用顺序遍历即可;统计元素出现频率要求我们维护一个保存有 <整数, 频率> 键值对的表格,对于给定的每一个输入的整数,均需要在维护的表格中查找其对应的键值对,可以利用二叉搜索树进行二分查找。因此,第一部分的时间复杂度为 O(nlogn)。
- 在所有的频率中选择出前 k 大的所对应的元素。可以维护一个最大堆,以 <整数, 频率> 键值对的“频率”值作为键值对大小比较的依据。将所有元素加入堆后,在从堆顶弹出 k 个键值对即可。维护堆的时间复杂度为 O(nlogn),弹出 k 个键值对的时间复杂度为 O(klogn),因此,第二部分的时间复杂度为 O(nlogn)。
编程实现(使用 C++ 语言)
程序的层次从顶层到底层分为了 3 个部分:利用二叉搜索树和最大堆实现程序逻辑、通用二叉搜索树和堆的实现、处理的基本单位 <整数, 频率> 键值对的实现。
较低的层次封装了属于该层次的技术细节,为较高层次提供了其所需要的抽象接口。具体而言,在实现程序逻辑时,需要将元素插入二叉搜索树,但不关心元素是如何插入二叉搜索树的;在实现二叉搜索树时,需要知道其节点元素的大小关系,但不关心这个大小关系是如何比较出来的。
因此,编程实现以下三个部分:
- 在 main 函数中实现程序逻辑
- 实现一个通用二叉搜索树类和一个通用堆类,提供插入、弹出等基本功能接口。
- 实现 <整数, 频率> 键值对数据结构,提供大小比较重载运算符。(对于二叉搜索树和堆可能需要提供不同的数据结构,可以利用类的继承精简代码)
源代码
- main 函数
1 int main() 2 { 3 BST<Elem<int, unsigned>> tree; // 二叉搜索树 4 while (true) // 遍历整数数组并存入二叉搜索树 5 { 6 int num; 7 cin >> num; 8 tree.insert(Elem<int, unsigned>(num)); 9 if (getchar() == ';') 10 break; 11 } 12 13 Heap<Elem_Heap<int, unsigned>> heap(MAX); // 最大堆 14 while (tree.isEmpty() == false) // 将二叉搜索树中的统计数据存入最大堆 15 heap.insert(tree.popMinimal()); 16 unsigned k; 17 cin >> k; 18 for (unsigned i = 0; i < k; i++) // 在最大堆中弹出 k 个堆顶键值对 19 { 20 cout << heap.pop().key << endl; // 打印键值对中的整数 21 } 22 23 24 return 0; 25 }
- 二叉搜索树的实现
1 template<class E> 2 class Node // 二叉搜索树的节点 3 { 4 public: 5 E elem; 6 Node<E>* lchild; 7 Node<E>* rchild; 8 9 Node(E e) :elem(e), lchild(NULL), rchild(NULL) {} 10 }; 11 12 template<class E> 13 class BST // 二叉搜索树 14 { 15 private: 16 Node<E>* root; 17 18 public: 19 BST() :root(NULL) {} 20 21 void insert(E n_elem) // 插入元素 22 { 23 if (root == NULL) 24 root = new Node<E>(n_elem); 25 else 26 { 27 Node<E>* p = root; 28 while (true) 29 { 30 if (n_elem < p->elem) 31 { 32 if (p->lchild == NULL) 33 { 34 p->lchild = new Node<E>(n_elem); 35 break; 36 } 37 else 38 p = p->lchild; 39 } 40 else if (n_elem > p->elem) 41 { 42 if (p->rchild == NULL) 43 { 44 p->rchild = new Node<E>(n_elem); 45 break; 46 } 47 else 48 p = p->rchild; 49 } 50 else 51 { 52 p->elem.CountInc(); 53 break; 54 } 55 } 56 } 57 } 58 59 E popMinimal() // 弹出最小元素 60 { 61 if (root->lchild == NULL) 62 { 63 E ret(root->elem); 64 Node<E>* to_del = root; 65 root = root->rchild; 66 delete to_del; 67 return ret; 68 } 69 else 70 { 71 Node<E>* p = root; 72 Node<E>* cur = p->lchild; 73 while (cur->lchild != NULL) 74 { 75 p = cur; 76 cur = cur->lchild; 77 } 78 E ret(cur->elem); 79 p->lchild = cur->rchild; 80 delete cur; 81 return ret; 82 } 83 } 84 85 bool isEmpty() // 判断树是否为空 86 { 87 if (root == NULL) 88 return true; 89 else 90 return false; 91 } 92 };
- 堆的实现
1 enum heaptype_t 2 { 3 MAX, 4 MIN 5 }; 6 7 template <class E> 8 class Heap // 堆 9 { 10 vector<E> buffer; 11 heaptype_t type; // 标识堆是最大堆还是最小堆 12 13 public: 14 Heap(heaptype_t t) 15 { 16 type = t; 17 } 18 19 void insert(E elem) // 插入元素 20 { 21 buffer.push_back(elem); 22 int i = buffer.size() - 1; 23 while (_parent(i) >= 0 && !_in_order(_parent(i), i)) 24 { 25 _exchange(_parent(i), i); 26 i = _parent(i); 27 } 28 } 29 30 E pop() // 弹出堆顶元素 31 { 32 E ret = top(); 33 if (size() > 0) 34 { 35 buffer[0] = buffer[size() - 1]; 36 buffer.pop_back(); 37 int i = 0; 38 while (_lchild(i) < size()) 39 { 40 int c; 41 if (_rchild(i) >= size() || _in_order(_lchild(i), _rchild(i))) 42 c = _lchild(i); 43 else 44 c = _rchild(i); 45 if (!_in_order(i, c)) 46 { 47 _exchange(i, c); 48 i = c; 49 } 50 else 51 break; 52 } 53 } 54 return ret; 55 } 56 57 E top() // 查看堆顶元素 58 { 59 return buffer[0]; 60 } 61 62 int size() // 查询堆的大小 63 { 64 return buffer.size(); 65 } 66 67 private: 68 int _parent(int i) 69 { 70 return (i - 1) / 2; 71 } 72 73 int _lchild(int i) 74 { 75 return i * 2 + 1; 76 } 77 78 int _rchild(int i) 79 { 80 return i * 2 + 2; 81 } 82 83 bool _in_order(int p, int c) 84 { 85 if (type == MAX) 86 return buffer[p] >= buffer[c]; 87 else 88 return buffer[p] <= buffer[c]; 89 } 90 91 void _exchange(int a, int b) 92 { 93 E temp = buffer[a]; 94 buffer[a] = buffer[b]; 95 buffer[b] = temp; 96 } 97 };
- 存于二叉搜索树和最大堆的 <整数, 频率> 键值对数据结构
1 template<class K, class V> 2 class Elem // 存于二叉搜索树的 <整数, 频率> 键值对结构 3 { 4 public: 5 K key; 6 V value; 7 8 9 Elem(K k) :key(k), value(V()) 10 { 11 value++; // 当 value 的类型为 unsigned int 时,value 的初始值为 1 12 } 13 14 15 bool operator== (Elem<K, V> para) // 重载运算符,二叉搜索树中键值对排序的依据是 键 16 { 17 return key == para.key; 18 } 19 20 21 bool operator> (Elem<K, V> para) // 重载运算符,二叉搜索树中键值对排序的依据是 键 22 { 23 return key > para.key; 24 } 25 26 27 bool operator< (Elem<K, V> para) // 重载运算符,二叉搜索树中键值对排序的依据是 键 28 { 29 return key < para.key; 30 } 31 32 33 void CountInc() // 供二叉搜索树在插入数据时发现整数已插入时调用,使 value 加 1 34 { 35 value++; 36 } 37 }; 38 39 template<class K, class V> 40 class Elem_Heap :public Elem<K, V> // 存于最大堆的 <整数, 频率> 键值对数据结构,继承自 Elem<K, V> 41 { 42 public: 43 Elem_Heap(Elem<K, V> e) :Elem<K, V>(e.key) 44 { 45 Elem<K, V>::value = e.value; 46 } 47 48 49 bool operator>= (Elem_Heap<K, V> para) // 重载运算符,最大堆中键值对排序的依据是 值 50 { 51 return Elem<K, V>::value >= para.value; 52 } 53 54 55 bool operator<= (Elem_Heap<K, V> para) // 重载运算符,最大堆中键值对排序的依据是 值 56 { 57 return Elem<K, V>::value <= para.value; 58 } 59 };
(完……