数据结构入门
1, 哈希表的c语言与c++实现
1.1 哈希表介绍
先说数组,我们知道数组的查找时间复杂度是O(1),但是只支持连续整数索引,不支持非连续索引。并且在索引过大时,数组扩容的成本很高。空间利用率很低。
所以就有了哈希表,用于查找非连续索引的数据结构。也就是说,任何唯一的键(可哈希)都可以作为哈希表的索引。
哈希表是一种数据结构,它通过将键映射到存储桶(bucket)的索引来实现快速的查找、插入和删除操作。哈希表通常使用哈希函数来计算键的哈希值,然后将哈希值映射到存储桶的索引。
在哈希表中,每个存储桶都可以存储多个键值对。当需要查找一个键时,哈希表首先计算该键的*哈希值,然后将哈希值映射到存储桶的索引。如果该存储桶中存在该键,则返回对应的值;否则,返回空值。
哈希表的查找、插入和删除操作的时间复杂度通常为O(1),即常数时间。这是因为哈希表的查找、插入和删除操作只需要计算哈希值和访问存储桶,而不需要遍历整个哈希表。(准确来说是O(1)的平均时间复杂度,在最坏情况下是O(n),即当哈希表装载因子过高时,会发生碰撞,导致查找、插入和删除操作的时间复杂度退化为线性时间。也就是我们说的哈希冲突)
哈希表的一个重要特性是哈希冲突。当两个不同的键映射到同一个存储桶时,就会发生哈希冲突。为了解决哈希冲突,可以使用开放地址法和链地址法等技术。
1.2 哈希表的c语言实现
下面各个成员的含义
size->哈希表的大小(存储桶的数量)
data->哈希表的数组(存储桶本身)
get_id()->哈希函数,用于计算键的哈希值 这个是哈希表的核心函数
get()->获取键对应的值
set()->设置键值对
对set()函数的分析
- 首先,我们需要计算键的哈希值。这里使用了一个简单的哈希函数,将键值与哈希表的大小取模,得到存储桶的索引。
- 然后,我们需要遍历存储桶中的链表,查找是否已经存在该键。如果存在,则更新对应的值;如果不存在,则将新的键值对插入到链表的末尾。
对get()函数的分析
- 首先,我们需要计算键的哈希值。这里使用了一个简单的哈希函数,将键值与哈希表的大小取模,得到存储桶的索引。
- 然后,我们需要遍历存储桶中的链表,查找是否存在该键。如果存在,则返回对应的值;如果不存在,则返回空值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| typedef struct MapNode { int key; int val; struct MapNode* next; } MapNode;
typedef struct{ int size; MapNode** data; }Map;
MapNode* buildNode(){ MapNode* obj=(MapNode*)malloc(sizeof(MapNode)); obj->key=-1; obj->val=0; obj->next=NULL; return obj; } MapNode* BuildNode(int key,int val){ MapNode* obj=(MapNode*)malloc(sizeof(MapNode)); obj->key=key; obj->val=val; obj->next=NULL; return obj; }
Map* init(){ Map* obj=(Map*)malloc(sizeof(Map)); obj->size=1000; obj->data=(MapNode**)malloc((obj->size)*sizeof(MapNode*)); for(int i=0;i<obj->size;i++){ obj->data[i]=buildNode(); } return obj; }
unsigned int get_id(int key, int size) { const unsigned int FNV_OFFSET_BASIS = 2166136261U; const unsigned int FNV_PRIME = 16777619U; unsigned int hash = (FNV_OFFSET_BASIS ^ key) * FNV_PRIME; return hash % size; }
void set(Map* obj,int key,int val){ unsigned int id=get_id(key,obj->size); MapNode* node=obj->data[id]; while(node->next){ if(node->key==key){ node->val=val; return; } node=node->next; } node->next=BuildNode(key,val); }
int get(Map* obj,int key){ unsigned int id=get_id(key,obj->size); MapNode* node=obj->data[id]; while(node->next){ if(node->key==key){ return node->val; } node=node->next; } return 0; }
void freeMap(Map* obj){ if (obj == NULL) return; for (int i = 0; i < obj->size; i++) { MapNode* curr = obj->data[i]; while (curr != NULL) { MapNode* temp = curr; curr = curr->next; free(temp); } } free(obj->data); free(obj); }
|
1.3 c++实现
c++语法较为复杂,需要先学习c++基础语法,原理与c语言实现相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
template<typename T> int get_digit(T key) { return key; }
template<> inline int get_digit(char key) { return static_cast<int>(key); }
template<> inline int get_digit(const char *key) { if(key==nullptr) return 0; int hash=0; while(*key) { hash=((hash << 5)+hash)+*key++; } return hash; }
template<> inline int get_digit(std::string key) { return get_digit(key.c_str()); }
template<> inline int get_digit(std::string_view key) { int hash=0; for(char c:key) { hash=((hash << 5)+hash)+c; } return hash; }
template <typename K, typename V> class Unordered_dict { int get_hash(const K& k) const{ return get_digit(k) % _size; }
void build() { for (int i=0;i<_size;i++) { tables[i] = new Node(); } } void clear_nodes() { for (int i = 0; i < _size; ++i) { Node* current = tables[i]; while (current) { Node* next = current->next; delete current; current = next; } } delete[] tables; } struct Node { K key; V value; Node *next; Node() { key = K(); value = V(); next = nullptr; } Node(K k, V v) { key = k; value = v; next = nullptr; } }; int _size; Node **tables; int total_size; public: Unordered_dict() { _size = 1<<16; tables = new Node*[_size]; total_size = 0; build(); } explicit Unordered_dict(int size): _size (size) { tables = new Node*[size]; total_size = 0; build(); }
Unordered_dict(const Unordered_dict<K,V> &other) { _size = other._size; tables = new Node*[_size]; total_size = other.total_size; build(); for(int i=0;i<_size;i++) { Node *p = other.tables[i] -> next; Node *cur = tables[i]; while(p) { cur -> next = new Node(p->key, p->value); p = p->next; cur = cur->next; } } }
~Unordered_dict() { clear_nodes(); }
V &operator[](K k) { int hash = get_hash(k); Node *p = tables[hash]; while(p->next) { if(p->next->key == k) { return p->next->value; } p = p->next; } p->next = new Node(k, V()); total_size ++; return p->next->value; } Unordered_dict<K,V>& operator=(const Unordered_dict<K,V> &other) { if(this == &other) return *this; if (this -> tables) { clear_nodes(); } _size = other._size; tables = new Node*[_size]; total_size = other.total_size; build(); for(int i=0;i<_size;i++) { Node *p = other.tables[i] -> next; Node *cur = tables[i]; while(p) { cur -> next = new Node(p->key, p->value); p = p->next; cur = cur->next; } } return *this; }
Unordered_dict<K,V>& operator=(Unordered_dict<K,V> &&other) noexcept { if (this == &other) return *this; if (this -> tables) { clear_nodes(); } _size = other._size; tables = other.tables; total_size = other.total_size; other.tables = nullptr; return *this; }
friend std::ostream& operator<<(std::ostream &os, Unordered_dict<K,V>& dict) { os << "--------------------------------------------------" << std::endl; for(int i=0;i<dict._size;i++) { Node *p = dict.tables[i] -> next; while(p) { os << p->key << " : " << p->value << std::endl; p = p->next; } } os << "--------------------------------------------------" << std::endl; return os; }
V get(K k) { int hash = get_hash(k); Node *p = tables[hash]->next; while(p) { if(p->key == k) { return p->value; } p = p->next; } throw std::runtime_error("key not found"); }
void insert(K k, V v) { int hash = get_hash(k); Node *p = tables[hash]; while(p->next) { if(p->next->key == k) { p->next->value = v; return; } p = p->next; } p->next = new Node(k, v); total_size ++; }
[[nodiscard]] int size() const { return total_size; }
int count(K k) const{ int hash = get_hash(k); Node *p = tables[hash] -> next; while(p) { if(p->key == k) { return 1; } p = p->next; } return 0; }
int erase(K k) { int hash = get_hash(k); Node *p = tables[hash]; while(p->next) { if(p->next->key == k) { Node *q = p->next; p->next = p->next->next; delete q; total_size --; return 1; } p = p->next; } return 0; } bool empty() const { return total_size==0; } };
|
2,优先队列
原理
优先队列是一种特殊的队列,它的特点是队列中的元素按照一定的优先级进行排序,优先级最高的元素最先出队。优先队列通常使用堆来实现,堆是一种完全二叉树,它的每个节点的值都大于或等于它的左右子节点的值(最大堆),或者小于或等于它的左右子节点的值(最小堆)。
这样,堆顶的元素就是优先级最高的元素。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| template<typename T> class PriorityQueue { vector<T> data; void up(int i) { while(data[i] > data[(i-1)/2]) { swap(data[i], data[(i-1)/2]); i=(i-1)/2; } }
void down(int i) { int j = 2*i+1; while(j<data.size()) { int maxidx = j+1 < data.size() && data[j+1] > data[j] ? j+1 : j; if(data[i] >= data[maxidx]) { break; } swap(data[i], data[maxidx]); i = maxidx; j = 2*i+1; } } public:
PriorityQueue() {}
PriorityQueue(vector<T> v) { for(auto &x: v) { data.push_back(x); up(data.size()-1); } }
void push(T x) { data.push_back(x); up(data.size()-1); }
void pop() { if(data.empty()) return; swap(data[0], data[data.size()-1]); data.pop_back(); down(0); }
T top() { if(data.empty()) return T(); return data[0]; }
bool empty() { return data.empty(); } }
|
注:c语言不好写
3,栈
原理
栈是一种后进先出(LIFO)的数据结构,它只允许在表的一端进行插入和删除操作。栈的实现通常使用数组或链表。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| template<typename T> class Stack { vector<T> data; public: Stack() {}
Stack(const Stack& other) = delete;
void push(T x) { data.push_back(x); }
void pop() { if(data.empty()) return; data.pop_back(); }
T top() { if(data.empty()) return T(); return data[data.size()-1]; }
bool empty() { return data.empty(); } }
|
4,队列
原理
队列是一种先进先出(FIFO)的数据结构,它只允许在表的一端进行插入操作,在另一端进行删除操作。队列的实现通常使用数组或链表。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| template<typename T> class Queue { int* data; int f; int b; public: Queue() { data = new int[10]; f=b=0; }
Queue(int n) { data = new int[n]; f=b=0; }
Queue(const Queue& other) = delete;
~Queue() { delete[] data; }
void push(T x) { data.push_back(x); b++; }
void pop() { if(f == b) return; f++; }
T front() { if(f == b) return T(); return data[f]; }
bool empty() { return f == b; } }
|
5,图
原理
图是一种由节点和边组成的抽象数据结构,它表示了一组对象及其之间的关系。图可以是有向的,也可以是无向的。图可以是有权重的,也可以是无权重的。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template<typename T> class Graph { vector<vector<T>> data; public: Graph() {}
Graph(int n) { data.resize(n); }
Graph(const Graph& other) = delete;
void add_edge(int u, int v) { data[u].push_back(v); }
void add_edge(int u, int v, int w) { data[u].push_back(make_pair(v, w)); }
vector<T> get_neighbors(int u) { return data[u]; } }
|
6,树
原理
树是一种由节点和边组成的抽象数据结构,它表示了一组对象及其之间的关系。树是一种特殊的图,它的特点是每个节点最多只有一个父节点,没有父节点的节点称为根节点。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| template<typename T> class Tree { struct Node { T value; vector<Node*> children; Node(T v) : value(v) {} }; Node* root; public: Tree() {}
Tree(T v) { root = new Node(v); }
Tree(const Tree& other) = delete;
void add_child(Node* parent, Node* child) { parent->children.push_back(child); }
Node* get_root() { return root; } }
|
7,线段树
跳转:线段树
8,双端队列
原理
双端队列是一种允许在两端进行插入和删除操作的数据结构。它既可以像栈一样后进先出,也可以像队列一样先进先出。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| template<typename T> class Deque { vector<T> data; public: Deque() {}
void push_front(T x) { data.insert(data.begin(), x); }
void push_back(T x) { data.push_back(x); }
void pop_front() { if(data.empty()) return; data.erase(data.begin()); }
void pop_back() { if(data.empty()) return; data.pop_back(); }
T front() { if(data.empty()) return T(); return data[0]; }
T back() { if(data.empty()) return T(); return data[data.size()-1]; }
bool empty() { return data.empty(); } }
|
9,链表
原理
链表是一种由节点组成的抽象数据结构,每个节点包含一个或多个元素以及指向下一个节点的指针。与数组相比,链表的优点是可以在常数时间内插入和删除元素,但访问特定元素的效率较低。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| template<typename T> class LinkedList { struct Node { T value; Node* next; Node(T v) : value(v), next(nullptr) {} }; Node* head; public: LinkedList() { head = nullptr; }
void push_back(T x) { if(head == nullptr) { head = new Node(x); return; } Node* p = head; while(p->next != nullptr) { p = p->next; } p->next = new Node(x); }
void push_front(T x) { Node* p = new Node(x); p->next = head; head = p; }
void pop_back() { if(head == nullptr) return; if(head->next == nullptr) { delete head; head = nullptr; return; } Node* p = head; while(p->next->next != nullptr) { p = p->next; } delete p->next; p->next = nullptr; }
void pop_front() { if(head == nullptr) return; Node* p = head; head = head->next; delete p; } T front() { if(head == nullptr) return T(); return head->value; }
T back() { if(head == nullptr) return T(); Node* p = head; while(p->next != nullptr) { p = p->next; } return p->value; }
bool empty() { return head == nullptr; } }
|
10,并查集
原理
并查集是一种用于处理不相交集合的数据结构,它支持两种操作:查找和合并。查找操作用于确定一个元素属于哪个集合,合并操作用于将两个集合合并为一个集合。
方法:路径压缩,合并。
并查思路:是否有共同的祖先。
路径压缩:在查找过程中,将路径上的所有节点都指向根节点,从而减少查找的时间复杂度。
合并:将两个集合合并为一个集合,通常是将一个集合的根节点指向另一个集合的根节点。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class UnionSet{ unordered_map<int,int> father; public: UnionSet(int n) { for (int i=0; i<n; i++) { father[i] = i; } } int find(int x) { if (father[x] == x) { return x; } father[x] = find(father[x]); return father[x]; }
void join(int x, int y) { int fa_x = find(x); int fa_y = find(y); if (fa_x != fa_y) { father[fa_x] = fa_y; } }
bool is_Connect(int x, int y) { int fa_x = find(x); int fa_y = find(y); return fa_x == fa_y; } };
|
