C++高效集合數據結構設計

緒論

在復雜算法實現過程中我們經常會需要一個高效的集合數據結構,支持常數級別的增、刪、查,以及隨機返回、遍歷,最好還能夠支持交集、并集、子集操作

哈希集合實現

大家可能很快想到unordered_setunordered_set由于底層是哈希表,所以自身就支持常數級別的增、刪、查,雖然不支持常數級別的隨機返回,但是可以很簡單地實現一個O(n)的隨機返回。于是我們可以很快實現一個好用的Set數據結構:


頭文件Set.h

// Copyright(C), Edward-Elric233
// Author: Edward-Elric233
// Version: 1.0
// Date: 2022/6/27
// Description: 封裝unordered_set實現支持交集、并集的set
#ifndef P_CENTER_SET_H
#define P_CENTER_SET_H#include <unordered_set>
#include <vector>
#include <iostream>namespace edward {class Set {std::unordered_set<int> set_;
public:Set() = default;~Set() = default;explicit Set(const std::vector<int> &arr):set_(arr.begin(), arr.end()) {}void insert(int x) {set_.insert(x);}void erase(int x) {set_.erase(x);}int size() const {return set_.size(); //size_t -> int}bool empty() const {return set_.empty();}bool exist(int x) const {return set_.count(x) > 0;}const std::unordered_set<int>& getSet() const {return set_;}int getRandom() const;const Set& operator&= (const Set& rhs);const Set& operator|= (const Set& rhs);bool operator<= (const Set& rhs) const;   //check if it's a subset of the right-hand side.friend Set operator& (const Set& lhs, const Set& rhs);friend Set operator| (const Set& lhs, const Set& rhs);friend std::ostream& operator<< (std::ostream &os, const Set& rhs);
};Set operator& (const Set& lhs, const Set& rhs);
Set operator| (const Set& lhs, const Set& rhs);
std::ostream& operator<< (std::ostream &os, const Set& rhs);}#endif //P_CENTER_SET_H

實現文件Set.cpp

// Copyright(C), Edward-Elric233
// Author: Edward-Elric233
// Version: 1.0
// Date: 2022/6/27
// Description: 
#include "Set.h"
#include "utils.h"namespace edward {const Set& Set::operator&=(const Set &rhs) {for (auto iter = set_.begin(); iter != set_.end(); ) {if (rhs.set_.count(*iter) == 0) {set_.erase(iter++);} else {++iter;}}return *this;
}const Set& Set::operator|=(const Set &rhs) {for (auto x : rhs.set_) {insert(x);}return *this;
}bool Set::operator<=(const Set &rhs) const {//check if it's a subset of the right-hand side.for (auto x : set_) {if (!rhs.exist(x)) return false;}return true;
}Set operator& (const Set& lhs, const Set& rhs) {if (lhs.size() > rhs.size()) {return operator&(rhs, lhs);} else {//lhs.size() <= rhs.size()Set ans;for (auto x : lhs.set_) {if (rhs.set_.count(x) > 0) {ans.insert(x);}}return ans;}
}
Set operator| (const Set& lhs, const Set& rhs) {//按秩合并if (lhs.size() > rhs.size()) {return operator|(rhs, lhs);} else {//lhs.size() <= rhs.size()Set ans = rhs;return ans |= lhs;}
}std::ostream& operator<< (std::ostream &os, const Set& rhs) {for (auto x : rhs.set_) {os << x << " ";}return os;
}int Set::getRandom() const {int idx = Random::rand(size());auto iter = set_.begin();while (idx--) ++iter;return *iter;
}}

標記數組實現

使用哈希表其實幫助我們解決了常數插入刪除的問題,但是當我們尤其要求集合的高效時使用哈希表仍然有比較高的復雜度常數。這就要求我們使用空間換時間的思想:直接用數組進行哈希,每個元素值本身就是自己在數組中的下標(值到下標的哈希映射為:x?xx\longrightarrow xx?x)。這種哈希毋庸置疑是最快的,因為根本不需要進行運算,但是我們存在無法遍歷和隨機返回的問題,為了解決這個問題,我們再用一個輔助的數組存儲值,而標記數組中則存儲的是值在輔助數組中的下標,只要我們能夠保證元素在輔助數組中是緊湊的,那么就可以實現遍歷和隨機返回,并且隨機返回是O(1)的。
這種實現能夠滿足我們絕大多數需求,但是缺點也是顯而易見的:空間復雜度太高,每個集合的空間復雜度固定地為集合元素的值域的大小,當我們元素的值域不是太大的時候,我們就可以使用這種集合實現,否則就只能使用上面的哈希集合。


頭文件RandomSet.h

// Copyright(C), Edward-Elric233
// Author: Edward-Elric233
// Version: 1.0
// Date: 2022/7/4
// Description: 
#ifndef P_CENTER_RANDOMSET_H
#define P_CENTER_RANDOMSET_H#include <vector>
#include "utils.h"namespace edward {class RandomSet {std::vector<int> pos_, nums_;
public:explicit RandomSet(int n): pos_(n, -1) {nums_.reserve(n);}void insert(int x) {pos_[x] = nums_.size();nums_.push_back(x);}void erase(int x) {nums_[pos_[x]] = nums_.back();pos_[nums_.back()] = pos_[x];pos_[x] = -1;nums_.pop_back();}int size() const {return nums_.size(); //size_t -> int}bool empty() const {return nums_.empty();}bool exist(int x) const {return pos_[x] != -1;}const std::vector<int>& getSet() const {return nums_;}int getRandom() const {return nums_[Random::rand(nums_.size())];}friend std::ostream& operator<< (std::ostream& os, const RandomSet& randomSet);
};std::ostream& operator<< (std::ostream& os, const RandomSet& randomSet);}#endif //P_CENTER_RANDOMSET_H

實現文件RandomSet.cpp

// Copyright(C), Edward-Elric233
// Author: Edward-Elric233
// Version: 1.0
// Date: 2022/7/4
// Description: 
#include "RandomSet.h"namespace edward {std::ostream& operator<< (std::ostream& os, const RandomSet& randomSet) {for (auto x : randomSet.nums_) {os << x << " ";}return os;
}}

測試文件test.cpp

// Copyright(C), Edward-Elric233
// Author: Edward-Elric233
// Version: 1.0
// Date: 2022/6/27
// Description: 
#include "test.h"
#include "Set.h"
#include "utils.h"
#include "RandomSet.h"namespace edward {void test_Set() {/*edward::Set a({1,2}), b({3,4,5});//a.insert(4);print("a.size():", a.size());print("a & b:", a & b);print("a | b:", a | b);//print("a &= b:", a &= b);print("a |= b:", a |= b);*/
}void test_RandomSet() {RandomSet randomSet(10);randomSet.insert(0);randomSet.insert(1);randomSet.insert(2);print(randomSet);randomSet.erase(1);print(randomSet);print(randomSet.size());randomSet.erase(2);randomSet.erase(0);randomSet.insert(9);print(randomSet);
}void test_Set_efficiency() {constexpr int MAXN = 1000000;Timer timer_Set;edward::Set set;for (int i = 0; i < MAXN; ++i) {set.insert(i);}for (int i = 0; i < MAXN; i += 2) {set.erase(i);}for (int i = 0; i < MAXN; i += 2) {set.insert(i);}print("Set.size() =", set.size());timer_Set("Set:");Timer timer_RandomSet;edward::RandomSet randomSet(MAXN);for (int i = 0; i < MAXN; ++i) {randomSet.insert(i);}for (int i = 0; i < MAXN; i += 2) {randomSet.erase(i);}for (int i = 0; i < MAXN; i += 2) {randomSet.insert(i);}print("RandomSet.size() =", randomSet.size());timer_RandomSet("RandomSet:");
}}

其中print是我自己實現的打印可變參模板函數,Timer類是計時器,實現文件放在文章末尾。
測試結果如下:

Set.size() = 1000000
Set: 0.522428 s
RandomSet.size() = 1000000
RandomSet: 0.0637485 s

我們可以看出,使用數組實現的集合雖然存在大小的限制,但是操作平均快一個量級。對于我們需要設計高效算法的場合,我們可以使用后者。大家可能注意到我沒有在第二種實現RandomSet中重載集合操作,這是因為如果已經需要考慮優化常數的話,那么往往也不允許實現一個完整的集合操作(交集、并集、子集),而是要求用戶具體地手動實現,并根據實際情況進行優化。

代碼中的utils頭文件實現了printTimer等工具函數和類,詳見博客:C++ 工具函數庫

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383510.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383510.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383510.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++ 工具函數庫

在寫一些大型項目的過程中經常需要一些工具函數&#xff0c;例如獲取隨機數、計時器、打印函數、重要常量&#xff08;如最大值&#xff09;、信號與槽等&#xff0c;由于每一個工程都自己手動實現一個實在是太傻&#xff0c;我將其總結放入一個文件中。 utils.h // Copyright…

muduo網絡庫使用入門

muduo網絡庫介紹 muduo網絡庫是陳碩大神開發的基于主從Reactor模式的&#xff0c;事件驅動的高性能網絡庫。 網絡編程中有很多是事務性的工作&#xff0c;使用muduo網絡庫&#xff0c;用戶只需要填上關鍵的業務邏輯代碼&#xff0c;并將回調注冊到框架中&#xff0c;就可以實…

C++ map/unordered_map元素類型std::pair<const key_type, mapped_type>陷阱

在開發的過程中需要遍歷一個unordered_map然后把他的迭代器傳給另一個對象&#xff1a; class A; class B { public:void deal(const std::pair<int, A>& item); }; std::unordered_map<int, A> mp; B b; for (auto &pr : mp) {b.deal(pr); }在我的項目中…

Ubuntu install ‘Bash to dock‘

緒論 在Ubuntu環境搭建這篇博客中記錄了使用Dash To Dock來配置Ubuntu的菜單項&#xff0c;使得實現macOS一樣的效果。為了配置新電腦的環境&#xff0c;我還是想安裝這個軟件。但是如今在Ubuntu Software中已經找不到這個軟件了&#xff0c;我在網上借鑒了一些博客的經驗才得…

Leetcode第309場周賽

Date: September 4, 2022 Difficulty: medium Rate by others: ???? Time consuming: 1h30min 題目鏈接 競賽 - 力扣 (LeetCode) 題目解析 2399. 檢查相同字母間的距離 class Solution {public:bool checkDistances(string s, vector<int>& distance) {vec…

C++ 模板函數、模板類:如果沒有被使用就不會被實例化

C中如果一個模板函數沒有使用過&#xff0c;那么其局部靜態變量都不會被實例化&#xff1a; class A { public:A() {edward::print("A ctor");} };template<typename T> void test() {static A a; }int main() {test<int>(); //如果注釋掉則不會有輸出r…

C++ 條件變量的使用

緒論 并發編程紛繁復雜&#xff0c;其中用于線程同步的主要工具——條件變量&#xff0c;雖然精悍&#xff0c;但是要想正確靈活的運用卻并不容易。 對于條件變量的理解有三個難點&#xff1a; 為什么wait函數需要將解鎖和阻塞、喚醒和上鎖這兩對操作編程原子的&#xff1f;為…

C++Primer學習筆記:第1章 開始

本博客為閱讀《C Primer》&#xff08;第5版&#xff09;的讀書筆記 ps:剛開始的時候我將所有的筆記都放在一篇博客中&#xff0c;等看到第六章的時候發現實在是太多了&#xff0c;導致我自己都不想看&#xff0c;為了日后回顧&#xff08;不那么有心理壓力&#xff09;&#…

【ubuntu】ubuntu14.04上安裝搜狗輸入法

** 在ubuntu14.04.4 desktop 64amd版本上安裝sogou輸入法 ** 0.換安裝源為中國源&#xff08;可選&#xff0c;下載會快些&#xff09; 1.搭fcitx環境 2.安裝sogou for linux 詳細步驟&#xff1a; 因為sogou中文輸入法基于fcitx(Free Chinese Input Toy for X),需要先搭環境…

【ubuntu】ubuntu下用make編譯程序報錯找不到openssl/conf.h

ubuntu下用make編譯程序報錯找不到openssl/conf.h 安裝libssl-dev:i386&#xff0c;sudo apt-get install libssl-dev:i386 看好版本&#xff0c;如果不加i386默認下載的是32位&#xff0c;用ln命令連接過去也還是用不了的!libssl.dev安裝好后&#xff0c;用find / -name libs…

【ubuntu】ubuntu如何改變系統用戶名

ubuntu如何改變系統用戶名 方法1&#xff1a;修改現有用戶名 方法2&#xff1a;創建新用戶&#xff0c;刪掉舊用戶 方法1&#xff1a; * *—&#xff01;&#xff01;&#xff01;有博客說要先改密碼&#xff0c;再改用戶名&#xff0c;否則會出現無法登陸狀況&#xff01;&…

什么是signal(SIGCHLD, SIG_IGN)函數

什么是signal(SIGCHLD, SIG_IGN)函數 在進行網絡編程時候遇到這個函數的使用&#xff0c;自己學習結果如下&#xff0c;有不對請幫忙指正:) signal(SIGCHLD, SIG_IGN)打開manpage康一康~ sighandler_t signal ( int signum, sighandler_t handler );參數1 int signum: 就是…

ssh連接不上linux虛擬機

ssh連接不上linux虛擬機 1.開啟ssh服務 linux虛擬機下命令行輸入&#xff1a; start service ssh如果顯示沒有ssh&#xff0c;就下面兩個試一試哪一個ok&#xff0c;安裝一下ssh&#xff1a; sudo apt-get install openssh-server sudo apt-get install sshd2.還有人說可能是…

沒寫client,想先測試server端怎么辦?

沒寫client&#xff0c;想先測試server端怎么辦&#xff1f; 辦法&#xff1a; 1.先打開終端./server&#xff0c;運行起來server 2.再開一個終端&#xff0c; 輸入nc 127.0.0.1 8888 回車&#xff08;這里port號要和server里邊設置的一致&#xff0c;127.0.0.1是和本機的測試…

【報錯解決】linux網絡編程報錯storage size of ‘serv_addr’ isn’t known解決辦法

linux網絡編程報錯storage size of ‘serv_addr’ isn’t known解決辦法 報錯如下&#xff1a; server.c:18:21: error: storage size of ‘serv_addr’ isn’t known struct sockaddr_in serv_addr, clit_addr; ^server.c:18:32: error: storage size of ‘clit_addr’ isn’…

【c】寫頭文件要加#ifndef,#define, #endif

頭文件首位 編寫.h時&#xff0c; 最好加上如下&#xff0c;用來防止重復包含頭文件&#xff1a; 例如&#xff1a; 要編寫頭文件test.h 在頭文件開頭寫上兩行&#xff1a;#ifndef _TEST_H#define _TEST_H// 文件名的大寫#endif頭文件結尾寫上一行&#xff1a;#endif這樣做是為…

【c】【報錯解決】incompatible implicit declaration

【報錯解決】incompatible implicit declaration 背景; 1.自己封裝的函數wrap.c包含&#xff1a; #include "wrap.h"2.主函數調用如下&#xff1a; #include <stdio.h> #include <stdlib.h> ... #include <errno.h> #include "wrap.h"…

【ubuntu】vim語法高亮設置無效

如果你的.vimrc配置了語法高亮&#xff0c;但是你的vim沒實現&#xff0c;可能你的vim是vim-tiny的黑白版本&#xff0c;你需要vim-gnome這個帶GUI的彩色版本。 apt-get update apt-get upgrade apt-get install vim-gnome reboot打開vi就能看到彩色啦

__attribute__機制介紹

1. __attribute__ GNU C的一大特色&#xff08;卻不被初學者所知&#xff09;就是__attribute__機制。 __attribute__可以設置函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute) __attribute__前后都有兩個下劃線&#xff0c;并且后面會緊…

【git】git基本操作命令

1.建立本地倉庫 git config --global user.name "lora" git config --global user.email "xxxgmail.com"2.建立目錄 mkdir xxx3.初始化 cd xxx //進入目錄 git init //初始化4.將代碼上傳至本地緩存區 git add . //上傳全部 git add 文件名 //…