面试题答案
一键面试1. 删除元素操作对内存分配和释放的影响分析
- 内存释放:当从
std::set
中删除元素时,std::set
内部使用的红黑树数据结构会调整节点。被删除节点占用的内存会被释放回操作系统的堆空间。例如,假设std::set
中有一个节点存储了一个整数5
,当删除这个元素时,该节点的内存(包含数据和红黑树节点相关的指针等)会被释放。 - 内存碎片:频繁的插入和删除操作可能导致内存碎片。因为每次分配和释放的内存大小不一定相同,在堆空间中会产生一些小块的空闲内存区域,这些区域由于大小不适合后续的分配请求而被闲置。例如,先分配了一个较大的内存块用于存储多个
std::set
节点,然后删除部分节点释放了部分内存,这些释放的内存可能无法被后续较小的分配请求完全利用,从而形成碎片。
2. 优化方案
2.1 数据结构调整
- 使用
std::unordered_set
:如果元素的顺序不重要,std::unordered_set
基于哈希表实现,其插入和删除操作平均时间复杂度为 $O(1)$,相比std::set
的 $O(\log n)$ 更快。虽然它在查找时可能需要更多的内存来存储哈希表,但在频繁插入和删除场景下性能可能更优。
#include <unordered_set>
#include <iostream>
int main() {
std::unordered_set<int> uset;
uset.insert(1);
uset.insert(2);
uset.erase(2);
for (int num : uset) {
std::cout << num << " ";
}
return 0;
}
- 自定义数据结构:可以考虑实现一个基于链表的集合结构,链表的插入和删除操作在已知位置时时间复杂度为 $O(1)$。同时,可以结合一个哈希表来快速定位要删除的元素。例如:
#include <iostream>
#include <unordered_map>
template <typename T>
struct ListNode {
T data;
ListNode* next;
ListNode(const T& value) : data(value), next(nullptr) {}
};
template <typename T>
class MySet {
private:
ListNode<T>* head;
std::unordered_map<T, ListNode<T>*> nodeMap;
public:
MySet() : head(nullptr) {}
void insert(const T& value) {
if (nodeMap.find(value) != nodeMap.end()) return;
ListNode<T>* newNode = new ListNode<T>(value);
newNode->next = head;
head = newNode;
nodeMap[value] = newNode;
}
void erase(const T& value) {
auto it = nodeMap.find(value);
if (it == nodeMap.end()) return;
ListNode<T>* nodeToDelete = it->second;
if (nodeToDelete == head) {
head = head->next;
} else {
ListNode<T>* current = head;
while (current->next != nodeToDelete) {
current = current->next;
}
current->next = nodeToDelete->next;
}
delete nodeToDelete;
nodeMap.erase(it);
}
~MySet() {
while (head != nullptr) {
ListNode<T>* temp = head;
head = head->next;
delete temp;
}
}
};
int main() {
MySet<int> mySet;
mySet.insert(1);
mySet.insert(2);
mySet.erase(2);
// 这里可以添加遍历输出等操作验证结果
return 0;
}
2.2 内存池技术应用
- 内存池原理:内存池是一种预先分配一定大小内存块的技术,在需要分配内存时从内存池中获取,释放内存时将其归还到内存池而不是直接归还给操作系统。这样可以减少系统调用的开销,并且避免内存碎片。
- 代码示例:
#include <iostream>
#include <vector>
#include <memory>
class MemoryPool {
private:
std::vector<char> pool;
size_t nextFreeIndex;
size_t blockSize;
public:
MemoryPool(size_t numBlocks, size_t blockSize) : blockSize(blockSize), nextFreeIndex(0) {
pool.resize(numBlocks * blockSize);
}
void* allocate() {
if (nextFreeIndex >= pool.size()) return nullptr;
void* result = &pool[nextFreeIndex];
nextFreeIndex += blockSize;
return result;
}
void deallocate(void* ptr) {
char* blockPtr = static_cast<char*>(ptr);
size_t index = blockPtr - &pool[0];
if (index % blockSize != 0 || index >= pool.size()) return;
// 这里可以简单标记为可用,实际可实现更复杂的回收策略
}
};
// 结合自定义数据结构使用内存池
template <typename T>
struct PooledListNode {
T data;
PooledListNode* next;
static MemoryPool* pool;
PooledListNode(const T& value) : data(value), next(nullptr) {}
~PooledListNode() {}
void* operator new(size_t size) {
return pool->allocate();
}
void operator delete(void* ptr) {
pool->deallocate(ptr);
}
};
template <typename T>
MemoryPool* PooledListNode<T>::pool = new MemoryPool(100, sizeof(PooledListNode<T>));
template <typename T>
class PooledMySet {
private:
PooledListNode<T>* head;
std::unordered_map<T, PooledListNode<T>*> nodeMap;
public:
PooledMySet() : head(nullptr) {}
void insert(const T& value) {
if (nodeMap.find(value) != nodeMap.end()) return;
PooledListNode<T>* newNode = new PooledListNode<T>(value);
newNode->next = head;
head = newNode;
nodeMap[value] = newNode;
}
void erase(const T& value) {
auto it = nodeMap.find(value);
if (it == nodeMap.end()) return;
PooledListNode<T>* nodeToDelete = it->second;
if (nodeToDelete == head) {
head = head->next;
} else {
PooledListNode<T>* current = head;
while (current->next != nodeToDelete) {
current = current->next;
}
current->next = nodeToDelete->next;
}
delete nodeToDelete;
nodeMap.erase(it);
}
~PooledMySet() {
while (head != nullptr) {
PooledListNode<T>* temp = head;
head = head->next;
delete temp;
}
delete PooledListNode<T>::pool;
}
};
int main() {
PooledMySet<int> pooledSet;
pooledSet.insert(1);
pooledSet.insert(2);
pooledSet.erase(2);
// 这里可以添加遍历输出等操作验证结果
return 0;
}
通过上述数据结构调整和内存池技术应用,可以有效优化在频繁进行元素插入和删除操作的 C++ set
容器场景下的内存管理和整体性能。