David Lu
Software Developer
Yung-Sheng Lu
FEB 09, 2015
@NCKU-CSIE
Lecture 4
空集合 (Empty set)。
集合中的元素不重複。
1
0
-5
7
8
11
1
0
-5
7
8
7
Set
Not Set
Set
Set
循序存取 (Sequential Access)
索引存取 (Indexed Access)
雜湊表 (Hash Table)
陣列 (Array):
使用一維陣列紀錄集合裡的所有元素。再用一個變數,記錄元素總數。
元素個數:5
/* Using struct */
typedef struct set {
int array[1000];
int num;
} Set;
Set set1, set2;
/* Using vector */
# include <vector>
vector<int> set1(1000);
vector<int> set2(1000);
1
2
-5
7
8
8 | 1 | 2 | 7 | -5 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
陣列 (Array):
如果要做聯集、交集、差集之類的運算,則會相當麻煩。
8 | 1 | 2 | 7 | -5 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
3 | -2 | 7 | 9 |
8 | 1 | 2 | 7 | -5 | 3 | -2 | 9 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
檢查元素是否已經存在?
Union
陣列 (Array):
/* Using vector */
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(void) {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10);
vector<int>::iterator it;
sort(first, first + 5); // 5 10 15 20 25
sort(second, second + 5); // 10 20 30 40 50
...
...
it = set_union(first, first + 5, second, second + 5,
v.begin());
// 5 10 15 20 25 30 40 50 0 0
v.resize(it - v.begin());
// 5 10 15 20 25 30 40 50
// Output result
for (it = v.begin(); it != v.end(); ++it)
cout << ' ' << *it;
cout << endl;
return 0;
}
陣列 (Array):
/* Using vector */
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(void) {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10);
vector<int>::iterator it;
sort(first, first + 5); // 5 10 15 20 25
sort(second, second + 5); // 10 20 30 40 50
...
...
it = set_intersection(first, first + 5, second, second + 5,
v.begin());
// 10 20 0 0 0 0 0 0 0 0
v.resize(it - v.begin());
// 10 20
// Output result
for (it = v.begin(); it != v.end(); ++it)
cout << ' ' << *it;
cout << endl;
return 0;
}
陣列 (Array):
/* Using vector */
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(void) {
int first[] = {5,10,15,20,25};
int second[] = {50,40,30,20,10};
vector<int> v(10);
vector<int>::iterator it;
sort(first, first + 5); // 5 10 15 20 25
sort(second, second + 5); // 10 20 30 40 50
...
...
it = set_difference(first, first + 5, second, second + 5,
v.begin());
// 5 15 25 0 0 0 0 0 0 0
v.resize(it - v.begin());
// 5 10 15 20 25 30 40 50
// Output result
for (it = v.begin(); it != v.end(); ++it)
cout << ' ' << *it;
cout << endl;
return 0;
}
鏈結串列 (Linked List):
原理和陣列完全一樣。
陣列是一個一個數字連著放,鏈結串列則是一個一個數字連成串。
/* Using linked list */
typedef struct node {
int data;
struct node *next;
} Node;
Node *set = new Node();
1
2
-5
7
8
8 |
1 |
2 |
7 |
-5 |
二元搜尋樹 (Binary Search Tree):
使用二元搜尋樹將集合裡的所有元素,概念類似鏈結串列。
/* Using binary search tree */
class BinarySearchTree {
private:
typedef struct treeNode {
struct treeNode *left;
struct treeNode *right;
int key;
} TreeNode;
TreeNode *root;
...
...
public:
BinarySearchTree() {
root = NULL;
}
bool isEmpty() const {
return root == NULL;
}
void inorder(tree_node*);
void preorder(tree_node*);
void postorder(tree_node*);
void insert(int);
void remove(int);
};
1
2
-5
7
8
7
1
8
-5
2
陣列 (Array):使用 bool 陣列。
集合裡若有 x 這個元素,就讓 array[x] 為 true ,否則為 false 。
數值受陣列大小影響。
如果要做聯集、交集、差集之類的運算,會比較快速,
時間複雜度: , 為陣列大小
1
2
5
7
8
0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
陣列 (Array):使用 bool 陣列。
bool set[1000];
// Determine whether the set is empty
bool empty(bool a[1000]) {
for (int i = 0; i < 1000; ++i) {
if (a[i])
return false;
}
return true;
}
// Add element
void add_element(bool a[1000], int element) {
a[element] = true;
}
// Remove element
void remove_element(bool a[1000], int element) {
a[element] = false;
}
...
...
void union(bool a[1000], bool b[1000], bool c[1000]) {
for (int i = 0; i < 1000; ++i)
c[i] = a[i] || b[i];
}
void intersection(bool a[1000], bool b[1000], bool c[1000]) {
for (int i = 0; i < 1000; ++i)
c[i] = a[i] && b[i];
}
void difference(bool a[1000], bool b[1000], bool c[1000]) {
for (int i = 0; i < 1000; ++i)
c[i] = a[i] && !b[i];
}
void complement(bool a[1000], bool b[1000]) {
for (int i = 0; i < 1000; ++i)
b[i] = !a[i];
}
位元陣列 (Bit Array; Bitset):使用 bit 來代替 bool 變數。
每個位元只有 0 和 1 兩種值,可用來表示一個集合元素存不存在。
可以節省儲存空間、運算時間。
一個整數變數所使用的記憶體大小為 32 bit ,可當作是 32 個數字的集合。
1
34
32
7
8
390 |
5 |
0 | 1 |
2
00000000000000000000000110000110 | 00000000000000000000000000000101 |
位元陣列 (Bit Array; Bitset):使用 bit 來代替 bool 變數。
可以直接使用 STL 的 <bitset> 的 bitset 。
// Bitset contains 0 to 3199 elements
typedef int Bitset[100];
// Get element's position
int get_pos(int element) {
return element >> 5;
}
// Get the element's bit to compute
int get_bit(int element) {
return 1 << (element & 31);
}
...
...
// Add element
void add_element(Bitset a, int element) {
a[get_pos(element)] |= get_bit(element);
}
// Remove element
void delete_element(Bitset a, int element) {
a[get_pos(element)] &= ~get_bit(element);
}
位元陣列 (Bit Array; Bitset):使用 bit 來代替 bool 變數。
Example - bitset 。
#include <iostream>
#include <string>
#include <bitset>
using namespace std;
int main(void) {
bitset<16> foo;
bitset<16> bar(0xfa2);
bitset<16> baz(string("0101111001"));
cout << "foo: " << foo << '\n';
cout << "bar: " << bar << '\n';
cout << "baz: " << baz << '\n';
return 0;
}
雜湊函式 (Hash Function):非資料結構。
將一筆資料重新表示成一個數值,該數值稱作雜湊值。
以資料庫的觀點,資料進行索引,以利管理。
以密碼學的觀點,資料進行編碼,以求隱蔽。
理想情況是相同資料有著相同雜湊值、相異資料有著相異雜湊值,如此就能直接使用雜湊值來分辨資料。
no: 1
name: H
m: 1.008
1210493772981
雜湊函式 (Hash Function):非資料結構。
可以直接使用 STL 中 <functional> 的 hash 。
#include <iostream>
#include <functional>
#include <string>
using namespace std;
int main(void) {
char nts1[] = "Test";
char nts2[] = "Test";
string str1(nts1);
string str2(nts2);
hash<char*> ptr_hash;
hash<string> str_hash;
...
...
cout << "same hashes:\n" << boolalpha;
cout << "nts1 and nts2: " << (ptr_hash(nts1) == ptr_hash(nts2)) << endl;
cout << "str1 and str2: " << (str_hash(str1) == str_hash(str2)) << endl;
return 0;
}
雜湊法 (Hashing):非資料結構。
一筆資料套用 hash function 得到雜湊值,作為陣列索引值,用陣列儲存。
設計 hash function 時,必須確保雜湊值不會超出陣列邊界。
no: 1 name: H m: 1.008 |
no: 3 name: Li m: 6.941 |
no: 11 name: Na m: 22.99 |
no: 19 name: K m: 39.1 |
1 |
3 |
1 |
4 |
0 |
1 |
2 |
3 |
4 |
no: 1 name: H m: 1.008 |
no: 11 name: Na m: 22.9 |
no: 3 name: Li m: 6.941 |
no: 19 name: K m: 39.1 |
雜湊法 (Hashing):非資料結構。
相同雜湊值,會儲存到陣列的同一格。此時有三種應對方案:
每個陣列元素皆改為 linked list ,串接資料。
放到下一格;如果下一格已經使用,就再往下一格。
新資料直接覆蓋舊資料。
雜湊法 (Hashing):非資料結構。
雜湊表 (Hash Table):
當元素的數值範圍很大,甚至元素不是整數,可以利用 hash function 得到一個索引值,而不會超出陣列邊界。
當數值範圍小,建議使用索引存取:省時間、費空間。
當數值範圍大,建議使用循序存取:省空間、費時間。
Hash table 兩者兼具,介於中間。
1
2
-5
7
8
1 | 2 | -5 | 7 | 8 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
int hash(int n) { return n % 10; }
雜湊表 (Hash Table):
可以直接使用 STL 的 unordered_set 、 unordered_multiset 。
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;
typedef unordered_set<string> StringSet;
int main(void) {
StringSet myset;
StringSet::hasher hash = myset.hash_function();
cout << "that: " << hash("that") << endl;
cout << "than: " << hash("than") << endl;
return 0;
}
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;
int main(void) {
unordered_set<string> myset;
myset.rehash(12);
myset.insert("office");
myset.insert("gym");
myset.insert("highway");
cout << "current bucket_count: "
<< myset.bucket_count() << endl;
return 0;
}
Cuckoo Filter *
建立多個 hash function 。
當陣列格子已有資料,就換 hash function 、換雜湊值。
1
2
-5
7
8
-5 | 1 | 2 | 8 | 7 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
int hash2(int n) { return (n + 2) % 10; }
int hash1(int n) { return (n * 2) % 10; }
Bloom Filter *
套用多個 hash function ,同時儲存於多個欄位,分散風險。
只要發現對應欄位幾乎都是 1 ,就推定元素存在於集合當中。
可能產生原本不存在的元素。
1
2
-5
7
8
1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
int hash2(int n) { return (n + 2) % 10; }
int hash1(int n) { return n % 10; }
集合之間擁有的元素都不相同,也就是集合之間都沒有交集。
Example -
構成 Disjoint Sets
不構成 Disjoint Sets
(元素重複 )
make_set(x):
以元素 為代表建立新的集合 。
find_set(x):
回傳元素 所在集合的代表。
union_set(x, y):
將元素 與 聯集成新的集合,建立新的代表。
Example -
make_set ( 1 )
make_set ( 2 )
make_set ( 3 )
make_set ( 4 )
find_set ( 3 )
find_set ( 2 )
union_set ( 1, 2 )
union_set ( 3, 4 )
find_set ( 2 )
find_set ( 4 )
1
2
4
3
return 3
return 1
representative 1
representative 3
return 1
return 3
void make_set(int x) {
// Set the parent of the node
parent[x] = x;
// Set height of the tree
rank[x] = 0;
// Set current number of nodes including itself
size[x] = 1;
}
1
3
8
7
rank[1] = 1
parent[8] = 1
1
3
8
7
rank[1] = 1
parent[8] = 1
2
parent[2] = 1
void find_set(int x) {
if (parent[x] == x)
return x;
return find_set(parent[x]);
}
1
3
7
1
3
8
7
void find_set(int x) {
if (parent[x] == x)
return x;
return parent[x] = find_set(parent[x]);
}
8
Very deep
void union_set(int x, int y) {
if (rank[x] > rank[y]) {
parent[y] = x;
rank[parent[x]] += rank[y];
size[parent[x]] += size[y];
}
else {
parent[x] = y;
rank[parent[y]] += rank[x];
size[parent[y]] += size[x];
}
}
1
3
8
7
2
5
4
6
rank[1] = 2
rank[5] = 1
Simple solution
Chose the smallest number at each iteration and delete the multiple of this number.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...
#define MAX 1000000
vector<bool> isPrime(MAX);
void sieve() {
isPrime[0] = false;
isPrime[1] = true;
for (int i = 2; i < MAX; ++i) {
if (isPrime[i]) {
for (int j = i + 1; j < MAX; j += i)
isPrime[j] = false;
}
}
}
#define MAX 1000000
vector<bool> isPrime(MAX);
void sieve() {
isPrime[0] = false;
isPrime[1] = true;
for (int i = 2; i < sqrt(MAX); ++i) {
if (isPrime[i]) {
for (int j = i * i; j < MAX; j += i)
isPrime[j] = false;
}
}
}
Euler's Function (Euler's Totient Function)
The totient of a positive integer is defined to be
The number of positive integers that are co-prime to .
e.g., 1, 2, 4, 5, 7, 8 are co-prime to 9.
To find totient of , we have to factorize first.
e.g.,
How to factorize ?
How to count the number of factor of ?
How to count the ?
Euler's Function (Euler's Totient Function)
int n = 12, pos = 0;
int tmp = n;
for (int i = 2; i <= (int)sqrt(n); ++i) {
if (!isPrime[i] || tmp % i) {
continue;
}
prime[pos] = i; // prime[pos] = prime i
while (tmp % i == 0) {
tmp /= i;
++a[pox]; // Increase the power a[pos]
}
++pos;
}
printf("%d =", n);
for (int i = 0; i < pos; ++i) {
if (i)
printf(" x");
printf(" %d ^ %d", prime[i], a[i])
}
By David Lu
Competitive Programming - Lecture 2