哈希表
哈希表(Hash Table)是一种用于存储键值对(key-value pair)的数据结构,它通过哈希函数将键映射到一个数组索引,从而实现通过键快速查找对应值。C++ 标准库提供了 unordered_map 和 unordered_set 两种哈希表实现。
哈希表通过 哈希函数 来将键(key)映射到一个固定的数组位置(桶)。通过该索引位置,哈希表可以非常快速地查找、插入、删除元素。哈希表提供了 常数时间复杂度(O(1))的查找操作,因此它对于需要频繁查找操作的数据结构非常高效。
unordered_map:用于存储键值对,类似于 map,但是不保证元素的顺序。
unordered_set:只存储键,不存储值,集合中的元素是唯一的。
使用示例
1、使用 unordered_set 存储节点指针
2、使用 unordered_map 存储指针到字符串的映射
3、使用字符串作为键的 unordered_map
4、自定义类型 Node* 作为键的 unordered_map
#include <iostream>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <functional>
class Node {
public:
int value;
Node* next;
Node(int val) : value(val), next(nullptr) {}
// 相等判断
bool operator==(const Node& other) const {
return value == other.value;
}
};
// 哈希函数
struct NodeHash {
std::size_t operator()(const Node* k) const {
return std::hash<int>()(k->value);
}
};
int main() {
Node* nodeA = new Node(1);
Node* nodeB = new Node(1);
// 使用 unordered_set 存储节点指针
std::unordered_set<Node*, NodeHash> hashSet;
hashSet.insert(nodeA);
std::cout << "Contains nodeA: " << (hashSet.count(nodeA) > 0) << std::endl;
std::cout << "Contains nodeB: " << (hashSet.count(nodeB) > 0) << std::endl; // 应为false
hashSet.erase(nodeA);
std::cout << "Contains nodeA: " << (hashSet.count(nodeA) > 0) << std::endl;
std::cout << "========1=========" << std::endl;
// 使用 unordered_map 存储指针到字符串的映射
std::unordered_map<Node*, std::string, NodeHash> hashMap;
hashMap[nodeA] = "A节点";
std::cout << "Contains nodeA: " << (hashMap.count(nodeA) > 0) << std::endl;
std::cout << "Contains nodeB: " << (hashMap.count(nodeB) > 0) << std::endl; // 应为false
hashMap[nodeB] = "B节点";
std::cout << "Contains nodeB: " << (hashMap.count(nodeB) > 0) << std::endl;
std::cout << "========2=========" << std::endl;
// 使用字符串作为键的 unordered_map
std::unordered_map<std::string, int> hashMap1;
std::string str1 = "key";
std::string str2 = "key";
hashMap1[str1] = 1;
std::cout << "Contains key: " << hashMap1.count(str1) << std::endl;
std::cout << "Value at key: " << hashMap1[str1] << std::endl;
hashMap1[str2] = 2;
std::cout << "Value at key: " << hashMap1[str1] << std::endl;
hashMap1.erase(str1);
std::cout << "Contains key: " << hashMap1.count(str1) << std::endl;
std::cout << "========3=========" << std::endl;
// 自定义类型 Node* 作为键的 unordered_map
std::unordered_map<Node*, std::string, NodeHash> hashMap2;
hashMap2[nodeA] = "A节点";
std::cout << "Contains nodeA: " << (hashMap2.count(nodeA) > 0) << std::endl;
std::cout << "Contains nodeB: " << (hashMap2.count(nodeB) > 0) << std::endl;
hashMap2[nodeB] = "B节点";
std::cout << "Contains nodeA: " << (hashMap2.count(nodeA) > 0) << std::endl;
std::cout << "========4=========" << std::endl;
// 释放动态分配的节点
delete nodeA;
delete nodeB;
return 0;
}
有序表
在 C++ 中,有序表是指能够自动保持元素有序的容器,通常用于按特定顺序存储和访问数据。C++标准库提供了两种常见的有序表容器:std::set 和 std::map,它们基于红黑树实现,具有自动排序、快速查找、插入和删除等特性。
**std::set:**是一个集合(Set),它存储的元素是唯一的,而且会自动根据元素的顺序进行排序。默认情况下,它会根据元素的大小进行升序排序。
**std::map:**是一个映射(Map),它存储的是键值对(key-value pairs)。每个键(key)必须是唯一的,并且会按照键的顺序进行排序。map 会根据键来自动排序,而值(value)可以是任何类型。
使用示例
#include <iostream>
#include <set>
#include <map>
#include <string>
class Node {
public:
int value;
Node* next;
Node(int val) : value(val), next(nullptr) {}
// 确保Node可以被比较
bool operator==(const Node& other) const {
return value == other.value;
}
};
// 比较函数,用于set和map
struct NodeCompare {
bool operator()(const Node* a, const Node* b) const {
return a->value < b->value;
}
};
int main() {
// set with custom comparator
std::set<Node*, NodeCompare> treeSet;
Node* nodeA = new Node(5);
Node* nodeB = new Node(3);
Node* nodeC = new Node(7);
treeSet.insert(nodeA);
treeSet.insert(nodeB);
treeSet.insert(nodeC);
std::cout << "Nodes added successfully." << std::endl;
std::cout << "========1=========" << std::endl;
// TreeMap equivalent in C++
std::map<int, std::string> treeMap1;
treeMap1[7] = "我是7";
treeMap1[5] = "我是5";
treeMap1[4] = "我是4";
treeMap1[3] = "我是3";
treeMap1[9] = "我是9";
treeMap1[2] = "我是2";
std::cout << "Contains 5: " << treeMap1.count(5) << std::endl;
std::cout << "Value at 5: " << treeMap1[5] << std::endl;
std::cout << "First key: " << treeMap1.begin()->first << ", 我最小" << std::endl;//返回 map 中最小的键值对的键(treeMap1 会根据键的顺序排序,最小的键是 2)
std::cout << "Last key: " << (--treeMap1.end())->first << ", 我最大" << std::endl;//返回 map 中最大的键值对的键(treeMap1 会根据键的顺序排序,最大的键是 9)
// floorKey and ceilingKey equivalents in C++
auto it_lower = treeMap1.lower_bound(8); // >=8
auto it_upper = treeMap1.upper_bound(8); // <=8
if (it_lower != treeMap1.end()) {
std::cout << it_lower->first << ", 在表中所有>=8的数中,我离8最近" << std::endl;
}
else {
std::cout << "No element >= 8" << std::endl;
}
// Lower bound for <=8
if (it_upper != treeMap1.begin()) {
std::cout << (--it_upper)->first << ", 在表中所有<=8的数中,我离8最近" << std::endl;
}
else {
std::cout << "No element <= 8" << std::endl;
}
treeMap1.erase(5);
std::cout << "Value at 5: " << (treeMap1.find(5) == treeMap1.end() ? "删了就没有了哦" : treeMap1[5]) << std::endl;
std::cout << "========2=========" << std::endl;
// Clean up nodes
delete nodeA;
delete nodeB;
delete nodeC;
return 0;
}
相关题目
**题目1:**反转链表
#include <iostream>
// 单链表节点定义
struct Node {
int value;
Node* next;
Node(int data) : value(data), next(nullptr) {}
};
// 反转单链表
Node* reverseList(Node* head) {
Node* pre = nullptr; // 初始化指针 pre,指向已反转部分的尾部,初始为空
Node* next = nullptr; // 初始化指针 next,用于暂存当前节点的下一个节点
while (head != nullptr) { // 遍历链表
next = head->next; // 先暂存当前节点的下一个节点
head->next = pre; // 将当前节点的 next 指向已反转部分的尾部(即 pre)
pre = head; // 将 pre 移动到当前节点,即已反转部分的尾部
head = next; // 将 head 移动到下一个节点,继续反转
}
return pre; // 返回反转后的新头节点
}
// 打印单链表
void printLinkedList(Node* head) {
std::cout << "Linked List: ";
while (head != nullptr) {
std::cout << head->value << " ";
head = head->next;
}
std::cout << std::endl;
}
// 双链表节点定义
struct DoubleNode {
int value;
DoubleNode* last;
DoubleNode* next;
DoubleNode(int data) : value(data), last(nullptr), next(nullptr) {}
};
// 反转双链表
DoubleNode* reverseList(DoubleNode* head) {
// 初始化两个指针:pre 和 next,分别用于跟踪已反转部分的尾部和当前节点的下一个节点
DoubleNode* pre = nullptr; // 初始时,pre 为 nullptr,表示没有已反转的部分
DoubleNode* next = nullptr; // next 用于暂存当前节点的下一个节点
// 遍历整个链表,直到 head 为 nullptr,意味着链表已经遍历完
while (head != nullptr) {
next = head->next; // 暂存当前节点的下一个节点(即 head->next),因为后续我们会改变 head->next
// 反转当前节点的指针:
// 将当前节点的 next 指针指向已反转部分的尾部(即 pre)
head->next = pre;
// 将当前节点的 last 指针指向下一个节点(即 next),因为在双链表中,反转时需要保持 last 指针指向原来的下一个节点
head->last = next;
pre = head;// 更新 pre 为当前节点,pre 代表已反转部分的尾部
head = next;// 更新 head 为 next,继续遍历下一个节点
}
// 最后返回 pre,pre 指向反转后的链表头部
return pre;
}
// 打印双链表
void printDoubleLinkedList(DoubleNode* head) {
std::cout << "Double Linked List: ";
DoubleNode* end = nullptr;
while (head != nullptr) {
std::cout << head->value << " ";
end = head;//记录当前节点为 end,目的是在完成正向遍历后,能够使用 end 从链表的尾部开始反向遍历。
head = head->next;
}
std::cout << "| ";
while (end != nullptr) {
std::cout << end->value << " ";
end = end->last;
}
std::cout << std::endl;
}
// 释放单链表
void freeLinkedList(Node* head) {
Node* next;
while (head != nullptr) {
next = head->next;
delete head;
head = next;
}
}
// 释放双链表
void freeDoubleLinkedList(DoubleNode* head) {
DoubleNode* next;
while (head != nullptr) {
next = head->next;
delete head;
head = next;
}
}
int main() {
Node* head1 = new Node(1);
head1->next = new Node(2);
head1->next->next = new Node(3);
printLinkedList(head1);
head1 = reverseList(head1);
printLinkedList(head1);
freeLinkedList(head1);
DoubleNode* head2 = new DoubleNode(1);
head2->next = new DoubleNode(2);
head2->next->last = head2;
head2->next->next = new DoubleNode(3);
head2->next->next->last = head2->next;
head2->next->next->next = new DoubleNode(4);
head2->next->next->next->last = head2->next->next;
printDoubleLinkedList(head2);
head2 = reverseList(head2);
printDoubleLinkedList(head2);
freeDoubleLinkedList(head2);
return 0;
}
**题目2:**打印链表公共部分
#include <iostream>
// 单链表节点定义
struct Node {
int value;
Node* next;
Node(int data) : value(data), next(nullptr) {}
};
// 打印两个有序链表的公共部分
void printCommonPart(Node* head1, Node* head2) {
std::cout << "Common Part: ";
while (head1 != nullptr && head2 != nullptr) {
if (head1->value < head2->value) {
head1 = head1->next;
}
else if (head1->value > head2->value) {
head2 = head2->next;
}
else {
std::cout << head1->value << " ";
head1 = head1->next;
head2 = head2->next;
}
}
std::cout << std::endl;
}
// 打印单链表
void printLinkedList(Node* node) {
std::cout << "Linked List: ";
while (node != nullptr) {
std::cout << node->value << " ";
node = node->next;
}
std::cout << std::endl;
}
int main() {
Node* node1 = new Node(2);
node1->next = new Node(3);
node1->next->next = new Node(5);
node1->next->next->next = new Node(6);
Node* node2 = new Node(1);
node2->next = new Node(2);
node2->next->next = new Node(5);
node2->next->next->next = new Node(7);
node2->next->next->next->next = new Node(8);
printLinkedList(node1);
printLinkedList(node2);
printCommonPart(node1, node2);
// Clean up dynamic memory
Node* temp;
while (node1 != nullptr) {
temp = node1->next;
delete node1;
node1 = temp;
}
while (node2 != nullptr) {
temp = node2->next;
delete node2;
node2 = temp;
}
return 0;
}
**题目3:**判断一个链表是否为回文结构
【例子】1->2->1,返回true; 1->2->2->1,返回true;15->6->15,返回true;1->2->3,返回false。
【例子】如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。
三种方法:
1、使用完整栈来检查是否为回文
2、使用半栈来检查是否为回文
3、使用原地反转来检查是否为回文
#include <iostream>
#include <stack>
// 单链表节点定义
struct Node {
int value;
Node* next;
Node(int data) : value(data), next(nullptr) {}
};
// 使用完整栈来检查是否为回文
bool isPalindrome1(Node* head) {
std::stack<Node*> stack;
Node* cur = head;
while (cur != nullptr) {
stack.push(cur);
cur = cur->next;
}
while (head != nullptr) {
if (head->value != stack.top()->value) {
return false;
}
stack.pop();
head = head->next;
}
return true;
}
// 使用半栈来检查是否为回文
bool isPalindrome2(Node* head) {
// 1. 如果链表为空或只有一个节点,直接返回 true,表示是回文链表
if (head == nullptr || head->next == nullptr) {
return true;
}
//注意快慢指针的定制问题,根据题目要求来,当快指针到头,慢指针正好走一半还是一半前一个?或者后一个?
Node* slow = head;
Node* fast = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
std::stack<Node*> stack;
Node* cur = slow->next;
while (cur != nullptr) {
stack.push(cur);
cur = cur->next;
}
while (!stack.empty()) {
if (head->value != stack.top()->value) {
return false;
}
stack.pop();
head = head->next;
}
return true;
}
// 使用原地反转来检查是否为回文
bool isPalindrome3(Node* head) {
if (head == nullptr || head->next == nullptr) {
return true;
}
Node* slow = head;
Node* fast = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
Node* prev = nullptr;
Node* cur = slow->next;
while (cur != nullptr) {
Node* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
Node* tail = prev;
bool result = true;
while (tail != nullptr) {
if (tail->value != head->value) {
result = false;
break;
}
tail = tail->next;
head = head->next;
}
// Recover the list
prev = nullptr;
cur = tail;
while (cur != nullptr) {
Node* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return result;
}
// 打印单链表
void printLinkedList(Node* node) {
std::cout << "Linked List: ";
while (node != nullptr) {
std::cout << node->value << " ";
node = node->next;
}
std::cout << std::endl;
}
int main() {
// 创建并测试链表
Node* head1 = new Node(1);
head1->next = new Node(2);
head1->next->next = new Node(3);
head1->next->next->next = new Node(2);
head1->next->next->next->next = new Node(1);
printLinkedList(head1);
std::cout << "Is Palindrome (Full Stack): " << isPalindrome1(head1) << std::endl;
std::cout << "Is Palindrome (Half Stack): " << isPalindrome2(head1) << std::endl;
std::cout << "Is Palindrome (In-place): " << isPalindrome3(head1) << std::endl;
// 释放链表内存
Node* current = head1;
Node* next = nullptr;
while (current != nullptr) {
next = current->next;
delete current;
current = next;
}
return 0;
}
题目4:
将单向链表按某值划分成左边小、中间相等、右边大的形式
【题目】给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。
【进阶】在实现原问题功能的基础上增加如下的要求
【要求】调整后所有小于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有等于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有大于pivot的节点之间的相对顺序和调整前一样
【要求】时间复杂度请达到O(N),额外空间复杂度请达到O(1)。
两个方法:
1、使用数组来分区
2、使用链表来分区
#include <iostream>
#include <vector>
// 单链表节点定义
struct Node {
int value;
Node* next;
Node(int data) : value(data), next(nullptr) {}
};
// 数组分区
void arrPartition(std::vector<Node*>& nodeArr, int pivot) {
int small = -1;
int big = nodeArr.size();
int index = 0;
while (index != big) {
if (nodeArr[index]->value < pivot) {
std::swap(nodeArr[++small], nodeArr[index++]);
}
else if (nodeArr[index]->value == pivot) {
index++;
}
else {
std::swap(nodeArr[--big], nodeArr[index]);
}
}
}
// 使用数组来分区
Node* listPartition1(Node* head, int pivot) {
if (head == nullptr) {
return head;
}
std::vector<Node*> nodeArr;
Node* cur = head;
while (cur != nullptr) {
nodeArr.push_back(cur);
cur = cur->next;
}
arrPartition(nodeArr, pivot);
for (size_t i = 1; i < nodeArr.size(); i++) {
nodeArr[i - 1]->next = nodeArr[i];
}
nodeArr.back()->next = nullptr;
return nodeArr.front();
}
// 直接使用链表节点来分区
Node* listPartition2(Node* head, int pivot) {
Node* sH = nullptr; // small head
Node* sT = nullptr; // small tail
Node* eH = nullptr; // equal head
Node* eT = nullptr; // equal tail
Node* bH = nullptr; // big head
Node* bT = nullptr; // big tail
Node* next = nullptr; // save next node
while (head != nullptr) {
next = head->next;
head->next = nullptr;
if (head->value < pivot) {
if (sH == nullptr) {
sH = sT = head;
}
else {
sT = sT->next = head;//先让sT的next指向head,再让head变为此时的尾巴
}
}
else if (head->value == pivot) {
if (eH == nullptr) {
eH = eT = head;
}
else {
eT = eT->next = head;
}
}
else {
if (bH == nullptr) {
bH = bT = head;
}
else {
bT = bT->next = head;
}
}
head = next;
}
// 连接三部分
if (sT != nullptr) {
sT->next = eH; // 如果 small 链表不为空,将 small 链表的尾部连接到 equal 链表的头部
eT = (eT == nullptr) ? sT : eT; // 如果 equal 链表为空,equal 链表的尾部是 small 链表的尾部
}
if (eT != nullptr) {
eT->next = bH; // 如果 equal 链表不为空,将 equal 链表的尾部连接到 big 链表的头部
}
return (sH != nullptr) ? sH : (eH != nullptr) ? eH : bH; // 返回分区后的链表头
//首先检查 sH != nullptr(small 链表是否为空):
//如果 sH 不为空,说明 small 链表有节点,并且返回 sH 作为新链表的头节点。
//如果 sH 为空,则检查 eH != nullptr(equal 链表是否为空):
//如果 eH 不为空,说明 equal 链表有节点,返回 eH 作为新链表的头节点。
//如果 sH 和 eH 都为空,则返回 bH(big 链表的头节点)
}
// 打印链表
void printLinkedList(Node* node) {
std::cout << "Linked List: ";
while (node != nullptr) {
std::cout << node->value << " ";
node = node->next;
}
std::cout << std::endl;
}
int main() {
Node* head1 = new Node(7);
head1->next = new Node(9);
head1->next->next = new Node(1);
head1->next->next->next = new Node(8);
head1->next->next->next->next = new Node(5);
head1->next->next->next->next->next = new Node(2);
head1->next->next->next->next->next->next = new Node(5);
printLinkedList(head1);
head1 = listPartition2(head1, 5);
printLinkedList(head1);
// 释放链表内存
Node* temp;
while (head1 != nullptr) {
temp = head1->next;
delete head1;
head1 = temp;
}
return 0;
}
题目5:
【题目】一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(int val) {
value = val;
}
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】时间复杂度O(N),额外空间复杂度O(1)
两种方法:
1、使用哈希表复制链表
2、不使用额外空间(哈希表)复制链表
#include <iostream>
#include <unordered_map>
#include <sstream>
// 链表节点定义,包含next和rand指针
struct Node {
int value;
Node* next;
Node* rand;
Node(int data) : value(data), next(nullptr), rand(nullptr) {}
};
// 使用哈希表复制链表
Node* copyListWithRand1(Node* head) {
std::unordered_map<Node*, Node*> map;//创建一个哈希表,key为原链表节点,value为复制的节点
Node* cur = head;//遍历原链表的指针
while (cur != nullptr) {
map[cur] = new Node(cur->value);//把原节点 cur 和它对应的复制节点 new Node(cur->value) 存入 map。
cur = cur->next;
}
cur = head;
while (cur != nullptr) {
map[cur]->next = map[cur->next];//将新节点的 next 指针设置为原链表节点 cur 的下一个节点对应的复制节点
map[cur]->rand = map[cur->rand];//将新节点的 rand 指针设置为原链表节点 cur 的随机指针对应的复制节点
cur = cur->next;
}
return map[head];
}
// 不使用额外空间复制链表
Node* copyListWithRand2(Node* head) {
if (head == nullptr) {
return nullptr;
}
Node* cur = head;
Node* next = nullptr;
// 第一步:复制每个节点,并将复制的节点插入到原节点的后面
while (cur != nullptr) {
next = cur->next;//保存下一个原链表节点的位置
Node* copy = new Node(cur->value);//复制当前节点
cur->next = copy;//将复制节点插入到原节点的后面
copy->next = next;//将复制节点的next指向下一个原链表节点
cur = next;//移动到下一个原链表节点
}
cur = head;
// 第二步:设置复制节点的rand指针
while (cur != nullptr) {
if (cur->rand != nullptr) {
cur->next->rand = cur->rand->next;// ① ①’
/* |rand | rand
③---->③’
next */
}
cur = cur->next->next;//从①跳②
}
// 第三步:分离原链表和复制链表
Node* res = head->next;
cur = head;
Node* copy = nullptr;
while (cur != nullptr) {
next = cur->next->next;//保存下一个原链表节点的位置
copy = cur->next;//获取当前节点的复制节点
cur->next = next; //将原链表的连接修复
if (next != nullptr) {
copy->next = next->next;//将复制节点的连接修复
}
cur = next;//移动到下一个原链表节点
}
return res;
}
std::string toString(int value) {
std::ostringstream oss;//创建一个 ostringstream 对象oss
oss << value;//将整数 value 通过 << 操作符输出到 oss 流中
return oss.str();//通过 oss.str() 获取流中存储的字符串,即将 value 转换成的字符串
}
// 打印链表和rand指针
void printRandLinkedList(Node* head) {
Node* cur = head;
std::cout << "order: ";
while (cur != nullptr) {
std::cout << cur->value << " ";
cur = cur->next;
}
std::cout << std::endl;
cur = head;
std::cout << "rand: ";
while (cur != nullptr) {
std::cout << (cur->rand == nullptr ? "- " : toString(cur->rand->value) + " ");//如果rand为空,打印“-”,否则打印rand的值
cur = cur->next;
}
std::cout << std::endl;
}
void freeList(Node* head) {
Node* next;
while (head != nullptr) {
next = head->next;
delete head;
head = next;
}
}
int main() {
Node* head = new Node(1);
head->next = new Node(2);
head->next->next = new Node(3);
head->next->next->next = new Node(4);
head->next->next->next->next = new Node(5);
head->next->next->next->next->next = new Node(6);
head->rand = head->next->next->next->next->next; // 1 -> 6
head->next->rand = head->next->next->next->next->next; // 2 -> 6
head->next->next->rand = head->next->next->next->next; // 3 -> 5
head->next->next->next->rand = head->next->next; // 4 -> 3
head->next->next->next->next->rand = nullptr; // 5 -> null
head->next->next->next->next->next->rand = head->next->next->next; // 6 -> 4
printRandLinkedList(head);
Node* res1 = copyListWithRand1(head);
printRandLinkedList(res1);
Node* res2 = copyListWithRand2(head);
printRandLinkedList(res2);
printRandLinkedList(head);
freeList(head); // 释放原始链表
freeList(res1); // 释放第一种方法复制的链表
freeList(res2); // 释放第二种方法复制的链表
return 0;
}