基本数据结构和算法
数据结构是计算机存储、组织数据的方式,它定义了数据的逻辑或物理存储方式,以及如何使用特定的方法来检索或操作这些数据。精心选择的数据结构可以带来更高的运行或存储效率。以下是对几种常见数据结构的详述:
- 链表:
- 链表是一种物理存储单元上非连续、非顺序的线性表,数据元素的逻辑顺序是通过链表中的指针链接次序来实现的。
- 链表由一系列节点(链表中每一个元素称为节点)组成,节点可以在运行时动态生成。
- 每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域。
- 栈:
- 栈(Stack)是一种后进先出(LIFO)的数据结构。
- 栈的主要操作有入栈(将元素添加到栈顶)和出栈(从栈顶移除元素)。
- 栈常用于实现函数调用和递归等。
- 队列:
- 队列是一种先进先出(FIFO)的数据结构。
- 元素被添加到队列的尾部,并从队列的头部被移除。
- 队列常用于实现缓冲区、任务调度等。
- 二叉树:
- 二叉树是每个节点最多有两个子节点的树结构,通常子节点被称为“左子节点”和“右子节点”。
- 二叉树常被用于实现搜索算法和排序算法,如二叉搜索树和二叉堆等。
- 二叉树的遍历方式有前序遍历、中序遍历和后序遍历等。
- 图:
- 图是由顶点和边组成的数据结构,其中顶点表示对象,边表示对象之间的关系。
- 图可以分为有向图和无向图,有向图的边具有方向性。
- 图常用于表示复杂的关系网络,如社交网络、交通网络等。
- 图的遍历算法有深度优先搜索(DFS)和广度优先搜索(BFS)等。
链表
链表是一种常见的数据结构,用于存储一系列的元素。与数组不同,链表中的元素不是连续存储的,而是通过指针或引用相互连接。这使得链表在插入和删除元素时具有很高的灵活性,无需移动大量数据。
链表的基本概念
- 节点(Node):链表由一系列节点组成。每个节点通常包含两部分:数据域和指针域。数据域用于存储元素的值,而指针域用于指向链表中的下一个节点。
- 头节点和尾节点:链表的第一个节点通常被称为头节点,而最后一个节点被称为尾节点。尾节点的指针域通常指向
null
或某个特殊值,表示链表的结束。 - 单向链表和双向链表:根据指针的方向,链表可以分为单向链表和双向链表。单向链表中的节点只有一个指针,指向下一个节点。而双向链表中的节点有两个指针,一个指向前一个节点,另一个指向后一个节点。
链表的操作
- 插入:在链表中插入一个元素通常涉及创建一个新节点,并将其插入到链表中的适当位置。这可以通过修改相邻节点的指针来实现。插入操作的时间复杂度通常为O(1),但如果需要找到插入位置,则可能需要O(n)的时间。
- 删除:从链表中删除一个元素涉及找到要删除的节点,并修改其相邻节点的指针,以便它们绕过要删除的节点。删除操作的时间复杂度也取决于找到要删除节点所需的时间,但实际的删除操作(修改指针)是O(1)的。
- 遍历:遍历链表意味着访问链表中的每个节点。这通常通过从头节点开始,并沿着指针依次访问每个节点来完成。遍历链表的时间复杂度为O(n),其中n是链表中的节点数。
- 搜索:在链表中搜索一个元素涉及遍历链表,直到找到匹配的元素或到达链表的末尾。搜索操作的时间复杂度为O(n)。
链表的优点和缺点
优点:
- 动态大小:链表可以在运行时动态地增长和缩小,无需预先分配固定大小的内存空间。
- 插入和删除效率高:在已知要插入或删除节点的位置时,链表可以在O(1)时间内完成这些操作(不考虑查找时间)。
缺点:
- 随机访问慢:与数组相比,链表不支持快速的随机访问。要访问链表中的特定元素,通常需要从头节点开始遍历链表。
- 额外的内存开销:每个节点都需要额外的空间来存储指针,这增加了内存开销。
链表的应用场景
- 当数据项的数量在运行时可能发生变化时,链表是一个很好的选择。
- 当需要在序列中间频繁地插入和删除元素时,链表比数组更高效。
- 在某些算法中,如链表排序算法或图的遍历算法中,链表也经常被使用。
代码示例
C++
struct ListNode {
int data;
ListNode * next;
ListNode (int x) : data(x), next(nullptr) {}
};
ListNode* insertAtHead(ListNode* head, int value) {
ListNode* newNode = new ListNode;
newNode->data = value;
newNode->next = head;
return newNode;
}
ListNode* insertAtTail(ListNode* head, int value) {
ListNode* newNode = new ListNode;
newNode->data = value;
newNode->next = nullptr;
if (head == nullptr) {
return newNode;
}
ListNode* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
return head;
}
ListNode* deleteNode(ListNode* head, int value) {
if (head == nullptr) {
return nullptr;
}
if (head->data == value) {
ListNode* temp = head;
head = head->next;
delete temp;
return head;
}
ListNode* current = head;
while (current->next != nullptr && current->next->data != value) {
current = current->next;
}
if (current->next != nullptr) {
ListNode* temp = current->next;
current->next = current->next->next;
delete temp;
}
return head;
}
void traverseList(ListNode* head) {
ListNode* current = head;
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;
}
ListNode* search(ListNode* head, int value) {
ListNode* current = head;
while (current != nullptr) {
if (current->data == value) {
return current;
}
current = current->next;
}
return nullptr; // 未找到
}
Haxe
class ListNode {
public var data:Int; // 数据元素
public var next:ListNode; // 指向下一个节点的指针
public function new(data:Int) {
this.data = data;
this.next = null;
}
}
function insertAtHead(head:ListNode, value:Int):ListNode {
var newNode:ListNode = new ListNode(value);
newNode.next = head;
return newNode;
}
function insertAtTail(head:ListNode, value:Int):ListNode {
var newNode:ListNode = new ListNode(value);
newNode.next = null;
if (head == null) {
return newNode;
}
var current:ListNode = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
return head;
}
function deleteNode(head:ListNode, value:Int):ListNode {
if (head == null) {
return null;
}
if (head.data == value) {
var temp:ListNode = head;
head = head.next;
temp = null;
return head;
}
var current:ListNode = head;
while (current.next != null && current.next.data != value) {
current = current.next;
}
if (current.next != null) {
var temp:ListNode = current.next;
current.next = current.next.next;
temp = null;
}
return head;
}
function traverseList(head:ListNode):Void {
var current:ListNode = head;
while (current != null) {
trace(current.data);
current = current.next;
}
}
function search(head:ListNode, value:Int):ListNode {
var current:ListNode = head;
while (current != null) {
if (current.data == value) {
return current;
}
current = current.next;
}
return null; // 未找到
}
Java
class ListNode {
int data; // 数据元素
ListNode next; // 指向下一个节点的指针
public ListNode(int data) {
this.data = data;
this.next = null;
}
}
public ListNode insertAtHead(ListNode head, int value) {
ListNode newNode = new ListNode(value);
newNode.next = head;
return newNode;
}
public ListNode insertAtTail(ListNode head, int value) {
ListNode newNode = new ListNode(value);
newNode.next = null;
if (head == null) {
return newNode;
}
ListNode current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
return head;
}
public ListNode deleteNode(ListNode head, int value) {
if (head == null) {
return null;
}
if (head.data == value) {
ListNode temp = head;
head = head.next;
temp = null;
return head;
}
ListNode current = head;
while (current.next != null && current.next.data != value) {
current = current.next;
}
if (current.next != null) {
ListNode temp = current.next;
current.next = current.next.next;
temp = null;
}
return head;
}
public void traverseList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.data + " ");
current = current.next;
}
System.out.println();
}
public ListNode search(ListNode head, int value) {
ListNode current = head;
while (current != null) {
if (current.data == value) {
return current;
}
current = current.next;
}
return null; // 未找到
}
二叉树
二叉树是一种常见的数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。这种树形结构在计算机科学中应用广泛,特别是在搜索、排序和构建高效的查找数据结构方面。
二叉树的基本概念
- 节点(Node):二叉树的每个元素称为节点,它包含数据域、左指针和右指针。数据域用于存储节点的值,而左指针和右指针分别指向节点的左子节点和右子节点。
- 根节点:二叉树的起始节点称为根节点,它没有父节点。
- 叶子节点:没有子节点的节点称为叶子节点。
- 深度(Depth):从根节点到最远叶子节点的最长路径上的节点数称为二叉树的深度。
- 满二叉树:如果一棵二叉树的每一层都有节点,并且所有叶子节点都在同一层,这样的二叉树称为满二叉树。
- 完全二叉树:若二叉树中除去最后一层节点为满二叉树,且最后一层的节点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。
二叉树的遍历
遍历二叉树是指按照某种规则访问树中的所有节点,每个节点仅被访问一次。常见的遍历方式有三种:
- 前序遍历:首先访问根节点,然后遍历左子树,最后遍历右子树。
- 中序遍历:首先遍历左子树,然后访问根节点,最后遍历右子树。对于二叉搜索树,中序遍历可以得到一个有序的节点序列。
- 后序遍历:首先遍历左子树,然后遍历右子树,最后访问根节点。
二叉树的应用
- 二叉搜索树(BST):在二叉搜索树中,所有左子节点的值都小于其父节点,而所有右子节点的值都大于其父节点。这种特性使得二叉搜索树在搜索操作上非常高效。
- 堆:堆是一种特殊的完全二叉树,它满足父节点的值总是大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。堆常用于实现优先队列。
- 哈夫曼树:哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。它常用于数据压缩和编码。
代码示例
struct TreeNode {
int data; // 数据元素
TreeNode* left; // 指向左子树的指针
TreeNode* right; // 指向右子树的指针
};
void preorderRecursive(TreeNode* root) {
if (root == nullptr) {
return;
}
cout << root->data << " "; // 访问根节点
preorderRecursive(root->left); // 遍历左子树
preorderRecursive(root->right); // 遍历右子树
}
void preorderIterative(TreeNode* root) {
if (root == nullptr) {
return;
}
stack<TreeNode*> s;
s.push(root);
while (!s.empty()) {
TreeNode* current = s.top();
s.pop();
cout << current->data << " "; // 访问当前节点
if (current->right) {
s.push(current->right); // 先将右子树入栈
}
if (current->left) {
s.push(current->left); // 再将左子树入栈
}
}
}
void inorderRecursive(TreeNode* root) {
if (root == nullptr) {
return;
}
inorderRecursive(root->left); // 遍历左子树
cout << root->data << " "; // 访问根节点
inorderRecursive(root->right); // 遍历右子树
}
void inorderIterative(TreeNode* root) {
stack<TreeNode*> s;
TreeNode* current = root;
while (current || !s.empty()) {
while (current) {
s.push(current);
current = current->left; // 先将左子树入栈
}
current = s.top();
s.pop();
cout << current->data << " "; // 访问当前节点
current = current->right; // 遍历右子树
}
}
void postorderRecursive(TreeNode* root) {
if (root == nullptr) {
return;
}
postorderRecursive(root->left); // 遍历左子树
postorderRecursive(root->right); // 遍历右子树
cout << root->data << " "; // 访问根节点
}
void postorderIterative(TreeNode* root) {
if (root == nullptr) {
return;
}
stack<TreeNode*> s1, s2;
s1.push(root);
while (!s1.empty()) {
TreeNode* current = s1.top();
s1.pop();
s2.push(current); // 将节点压入第二个栈
if (current->left) {
s1.push(current->left);
}
if (current->right) {
s1.push(current->right);
}
}
while (!s2.empty()) {
cout << s2.top()->data << " "; // 访问当前节点
s2.pop();
}
}
STL heap用法
//
// 堆创建
//
#include <iostream>
#include <vector>
#include <algorithm> // 包含 heap 相关算法
using namespace std;
int main() {
vector<int> heap = {6, 1, 2, 5, 3, 4}; // 示例数据
// 创建大顶堆(默认)
make_heap(heap.begin(), heap.end());
cout << "大顶堆: ";
for (int num : heap) {
cout << num << " ";
}
cout << endl;
// 创建小顶堆
make_heap(heap.begin(), heap.end(), greater<int>());
cout << "小顶堆: ";
for (int num : heap) {
cout << num << " ";
}
cout << endl;
return 0;
}
//
// 堆插入和删除
//
vector<int> heap = {6, 1, 2, 5, 3, 4}; // 示例数据
// 创建大顶堆
make_heap(heap.begin(), heap.end());
// 插入元素 200
heap.push_back(200);
push_heap(heap.begin(), heap.end());
cout << "插入元素后的堆: ";
for (int num : heap) {
cout << num << " ";
}
cout << endl;
// 弹出堆顶元素
pop_heap(heap.begin(), heap.end());
heap.pop_back();
cout << "弹出堆顶元素后的堆: ";
for (int num : heap) {
cout << num << " ";
}
cout << endl;
//
// 堆排序
//
vector<int> heap = {6, 1, 2, 5, 3, 4}; // 示例数据
// 创建大顶堆
make_heap(heap.begin(), heap.end());
// 堆排序
sort_heap(heap.begin(), heap.end());
cout << "堆排序结果: ";
for (int num : heap) {
cout << num << " ";
}
cout << endl;
实现最大堆和最小堆
下面的代码使用vector实现,并没有考虑将大小判断策略化,或通用化。
///
// 最大堆
///
#include <iostream>
#include <vector>
class MaxHeap {
private:
std::vector<int> heap;
int parent(int i) { return (i - 1) / 2; }
int leftChild(int i) { return (2 * i + 1); }
int rightChild(int i) { return (2 * i + 2); }
void siftUp(int i) {
while (i != 0 && heap[parent(i)] < heap[i]) {
std::swap(heap[i], heap[parent(i)]);
i = parent(i);
}
}
void siftDown(int i) {
int maxIndex = i;
int l = leftChild(i);
if (l < heap.size() && heap[l] > heap[maxIndex])
maxIndex = l;
int r = rightChild(i);
if (r < heap.size() && heap[r] > heap[maxIndex])
maxIndex = r;
if (i != maxIndex) {
std::swap(heap[i], heap[maxIndex]);
siftDown(maxIndex);
}
}
public:
void insert(int p) {
heap.push_back(p);
siftUp(heap.size() - 1);
}
int extractMax() {
if (heap.size() == 0)
throw std::runtime_error("Heap is empty");
int result = heap[0];
heap[0] = heap.back();
heap.pop_back();
if (!heap.empty())
siftDown(0);
return result;
}
int getMax() {
if (heap.size() == 0)
throw std::runtime_error("Heap is empty");
return heap[0];
}
void displayHeap() {
for (int i : heap)
std::cout << i << " ";
std::cout << std::endl;
}
};
int main() {
MaxHeap maxHeap;
maxHeap.insert(3);
maxHeap.insert(2);
maxHeap.insert(15);
maxHeap.insert(5);
maxHeap.insert(4);
maxHeap.insert(45);
std::cout << "Heap elements: ";
maxHeap.displayHeap();
std::cout << "Extracted max element: " << maxHeap.extractMax() << std::endl;
std::cout << "Heap elements after extraction: ";
maxHeap.displayHeap();
return 0;
}
///
// 最小堆
///
#include <iostream>
#include <vector>
class MinHeap {
private:
std::vector<int> heap;
int parent(int i) { return (i - 1) / 2; }
int leftChild(int i) { return (2 * i + 1); }
int rightChild(int i) { return (2 * i + 2); }
void siftUp(int i) {
while (i != 0 && heap[parent(i)] > heap[i]) {
std::swap(heap[i], heap[parent(i)]);
i = parent(i);
}
}
void siftDown(int i) {
int minIndex = i;
int l = leftChild(i);
if (l < heap.size() && heap[l] < heap[minIndex])
minIndex = l;
int r = rightChild(i);
if (r < heap.size() && heap[r] < heap[minIndex])
minIndex = r;
if (i != minIndex) {
std::swap(heap[i], heap[minIndex]);
siftDown(minIndex);
}
}
public:
void insert(int p) {
heap.push_back(p);
siftUp(heap.size() - 1);
}
int extractMin() {
if (heap.size() == 0)
throw std::runtime_error("Heap is empty");
int result = heap[0];
heap[0] = heap.back();
heap.pop_back();
if (!heap.empty())
siftDown(0);
return result;
}
int getMin() {
if (heap.size() == 0)
throw std::runtime_error("Heap is empty");
return heap[0];
}
void displayHeap() {
for (int i : heap)
std::cout << i << " ";
std::cout << std::endl;
}
};
int main() {
MinHeap minHeap;
minHeap.insert(3);
minHeap.insert(2);
minHeap.insert(15);
minHeap.insert(5);
minHeap.insert(4);
minHeap.insert(45);
std::cout << "Heap elements: ";
minHeap.displayHeap();
std::cout << "Extracted min element: " << minHeap.extractMin() << std::endl;
std::cout << "Heap elements after extraction: ";
minHeap.displayHeap();
return 0;
}
霍夫曼树
霍夫曼树(Huffman Tree)是一种用于数据压缩的二叉树。下面是使用 C++ 实现霍夫曼树的完整代码,包括生成霍夫曼树、编码和解码的功能。
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
// 定义霍夫曼树节点
struct HuffmanNode {
char data;
int freq;
HuffmanNode *left, *right;
HuffmanNode(char data, int freq) {
left = right = nullptr;
this->data = data;
this->freq = freq;
}
};
// 比较两个霍夫曼树节点的优先级(频率),频率越小优先级越高
struct compare {
bool operator()(HuffmanNode* l, HuffmanNode* r) {
return l->freq > r->freq;
}
};
// 创建霍夫曼树并返回根节点
HuffmanNode* buildHuffmanTree(const std::unordered_map<char, int>& freq) {
std::priority_queue<HuffmanNode*, std::vector<HuffmanNode*>, compare> minHeap;
// 创建一个叶子节点并加入优先队列
for (auto pair : freq) {
minHeap.push(new HuffmanNode(pair.first, pair.second));
}
while (minHeap.size() != 1) {
// 从队列中取出两个频率最小的节点
HuffmanNode *left = minHeap.top();
minHeap.pop();
HuffmanNode *right = minHeap.top();
minHeap.pop();
// 创建一个新的内部节点,其频率是两个子节点频率之和
HuffmanNode *top = new HuffmanNode('$', left->freq + right->freq);
top->left = left;
top->right = right;
// 将新的内部节点插入优先队列
minHeap.push(top);
}
// 优先队列中剩下的唯一节点是霍夫曼树的根节点
return minHeap.top();
}
// 递归存储霍夫曼编码
void storeCodes(HuffmanNode* root, std::string str, std::unordered_map<char, std::string>& huffmanCode) {
if (root == nullptr)
return;
if (root->data != '$')
huffmanCode[root->data] = str;
storeCodes(root->left, str + "0", huffmanCode);
storeCodes(root->right, str + "1", huffmanCode);
}
// 霍夫曼编码函数
std::unordered_map<char, std::string> HuffmanCodes(const std::unordered_map<char, int>& freq) {
HuffmanNode* root = buildHuffmanTree(freq);
std::unordered_map<char, std::string> huffmanCode;
storeCodes(root, "", huffmanCode);
return huffmanCode;
}
// 编码字符串
std::string encode(const std::string& text, const std::unordered_map<char, std::string>& huffmanCode) {
std::string encodedString = "";
for (char ch : text) {
encodedString += huffmanCode.at(ch);
}
return encodedString;
}
// 解码字符串
std::string decode(const std::string& encodedString, HuffmanNode* root) {
std::string decodedString = "";
HuffmanNode* currentNode = root;
for (char bit : encodedString) {
if (bit == '0')
currentNode = currentNode->left;
else
currentNode = currentNode->right;
if (currentNode->left == nullptr && currentNode->right == nullptr) {
decodedString += currentNode->data;
currentNode = root;
}
}
return decodedString;
}
// 主函数
int main() {
std::string text = "huffman coding algorithm";
// 计算每个字符的频率
std::unordered_map<char, int> freq;
for (char ch : text) {
freq[ch]++;
}
// 生成霍夫曼编码
std::unordered_map<char, std::string> huffmanCode = HuffmanCodes(freq);
// 显示每个字符的霍夫曼编码
std::cout << "Huffman Codes:\n";
for (auto pair : huffmanCode) {
std::cout << pair.first << ": " << pair.second << "\n";
}
// 编码字符串
std::string encodedString = encode(text, huffmanCode);
std::cout << "\nEncoded string:\n" << encodedString << "\n";
// 构建霍夫曼树以进行解码
HuffmanNode* root = buildHuffmanTree(freq);
// 解码字符串
std::string decodedString = decode(encodedString, root);
std::cout << "\nDecoded string:\n" << decodedString << "\n";
return 0;
}
代码说明
- 定义霍夫曼树节点结构:节点包含字符数据、频率以及左右子节点指针。
- 优先队列比较函数:用于优先队列比较节点的频率。
- 构建霍夫曼树:通过频率信息构建霍夫曼树。
- 存储霍夫曼编码:递归地存储每个字符的霍夫曼编码。
- 霍夫曼编码函数:生成霍夫曼编码。
- 编码函数:将输入字符串编码为霍夫曼编码。
- 解码函数:将霍夫曼编码解码为原始字符串。
- 主函数:测试编码和解码功能。
运行结果
运行该代码后,你将看到霍夫曼编码,每个字符的霍夫曼编码,以及编码后的字符串和解码后的字符串。这种实现展示了霍夫曼编码算法在数据压缩中的应用。
图
图(Graph)是一种复杂的数据结构,用于表示对象及其相互关系。它由节点(或顶点)和边组成,可以用来描述现实世界中各种复杂的关系网络。以下是对图的详细描述:
1. 图的基本概念
- 节点(Vertex):图的基本单元,代表某个具体对象或实体。
- 边(Edge):连接两个节点的线段,表示节点之间的关系。
- 有向图与无向图:根据边是否有方向,图可分为有向图和无向图。有向图的边具有明确的方向性,而无向图的边则没有。
- 权重图:在图中,每条边可以赋予一个数值,表示该边的权重。这在许多算法中,如最短路径算法,是非常重要的。
2. 图的表示方法
图通常可以用邻接矩阵或邻接表来表示:
- 邻接矩阵:一个二维数组,其中每个元素表示对应两个节点之间是否存在边以及边的权重(对于权重图)。
- 邻接表:一种链表结构,每个节点对应一个链表,链表中存储与该节点直接相连的节点信息。
3. 图的应用场景
图在多个领域都有广泛应用,包括但不限于:
- 社交网络:用户作为节点,用户之间的关系作为边,用于分析社交网络中的各种关系。
- 万维网:网页作为节点,超链接作为边,用于搜索引擎的网页排序和导航优化。
- 推荐系统:商品和用户作为节点,购买行为等作为边,用于为用户推荐相关商品。
- 知识图谱:实体和实体之间的关系构成图,用于智能搜索和问答系统。
- 路由和路径规划:网络设备或地点作为节点,连接关系作为边,用于优化网络路由和地图导航。
- 生物信息学:基因、蛋白质等生物实体作为节点,它们之间的关系作为边,用于研究生物过程和发现新的生物标志物。
4. 图的遍历算法
图的遍历算法主要有深度优先搜索(DFS)和广度优先搜索(BFS):
- 深度优先搜索:从某个节点出发,尽可能深地搜索图的分支,直到当前分支搜索完毕后再回溯搜索其他分支。
- 广度优先搜索:从某个节点出发,首先搜索其所有相邻节点,然后对每个相邻节点进行相同的搜索过程,直到遍历完整个图。
5. 图的常见算法及应用案例
- 最短路径算法(如Dijkstra算法、Floyd算法):用于寻找两个节点之间的最短路径。例如,在交通网络中规划最佳路线。
- 最小生成树算法(如Prim算法、Kruskal算法):用于找出一棵包含所有节点的树,且树上边的权重之和最小。这在电力网络布局优化中有重要应用。
- 社交网络分析算法(如PageRank算法):用于研究和分析社交网络中的节点间关系,如确定网页的重要性排序。
- 流网络算法(如最大流算法):用于研究图中节点之间的流量分配和优化问题,如在管道网络中确定最大流量。
代码示例
图的定义
#include <iostream>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <limits.h>
#include <algorithm>
class Graph {
private:
int V; // 顶点数量
std::vector<std::list<std::pair<int, int>>> adj; // 邻接表:每个节点的列表,其中包含邻接节点和权重
public:
Graph(int V); // 构造函数
void addEdge(int v, int w, int weight = 1); // 添加边
void BFS(int s); // 广度优先搜索
void DFS(int s); // 深度优先搜索
void DFSUtil(int v, std::vector<bool>& visited); // DFS的辅助函数
void Dijkstra(int src); // Dijkstra算法
void Kruskal(); // Kruskal算法
// 辅助函数
int find(std::vector<int>& parent, int i);
void Union(std::vector<int>& parent, std::vector<int>& rank, int x, int y);
};
Graph::Graph(int V) {
this->V = V;
adj.resize(V);
}
void Graph::addEdge(int v, int w, int weight) {
adj[v].emplace_back(w, weight);
adj[w].emplace_back(v, weight); // 无向图
}
广度优先搜索(BFS)
void Graph::BFS(int s) {
std::vector<bool> visited(V, false);
std::queue<int> queue;
visited[s] = true;
queue.push(s);
while (!queue.empty()) {
s = queue.front();
std::cout << s << " ";
queue.pop();
for (auto& adjNode : adj[s]) {
if (!visited[adjNode.first]) {
visited[adjNode.first] = true;
queue.push(adjNode.first);
}
}
}
}
深度优先搜索(DFS)
void Graph::DFSUtil(int v, std::vector<bool>& visited) {
visited[v] = true;
std::cout << v << " ";
for (auto& adjNode : adj[v]) {
if (!visited[adjNode.first]) {
DFSUtil(adjNode.first, visited);
}
}
}
void Graph::DFS(int s) {
std::vector<bool> visited(V, false);
DFSUtil(s, visited);
}
Dijkstra算法
void Graph::Dijkstra(int src) {
std::vector<int> dist(V, INT_MAX);
dist[src] = 0;
std::vector<bool> sptSet(V, false);
for (int count = 0; count < V - 1; count++) {
int u = -1;
for (int i = 0; i < V; i++) {
if (!sptSet[i] && (u == -1 || dist[i] < dist[u])) {
u = i;
}
}
sptSet[u] = true;
for (auto& adjNode : adj[u]) {
int v = adjNode.first;
int weight = adjNode.second;
if (!sptSet[v] && dist[u] != INT_MAX && dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
}
}
}
std::cout << "Vertex \t Distance from Source" << std::endl;
for (int i = 0; i < V; i++) {
std::cout << i << " \t\t " << dist[i] << std::endl;
}
}
Kruskal算法
struct Edge {
int src, dest, weight;
};
bool compareEdge(const Edge& e1, const Edge& e2) {
return e1.weight < e2.weight;
}
int Graph::find(std::vector<int>& parent, int i) {
if (parent[i] != i)
parent[i] = find(parent, parent[i]);
return parent[i];
}
void Graph::Union(std::vector<int>& parent, std::vector<int>& rank, int x, int y) {
int rootX = find(parent, x);
int rootY = find(parent, y);
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
void Graph::Kruskal() {
std::vector<Edge> edges;
for (int u = 0; u < V; u++) {
for (auto& adjNode : adj[u]) {
int v = adjNode.first;
int weight = adjNode.second;
if (u < v) {
edges.push_back({u, v, weight});
}
}
}
std::sort(edges.begin(), edges.end(), compareEdge);
std::vector<int> parent(V);
std::vector<int> rank(V, 0);
for (int i = 0; i < V; i++) {
parent[i] = i;
}
std::vector<Edge> result;
for (auto& edge : edges) {
int x = find(parent, edge.src);
int y = find(parent, edge.dest);
if (x != y) {
result.push_back(edge);
Union(parent, rank, x, y);
}
}
std::cout << "Following are the edges in the constructed MST" << std::endl;
for (auto& edge : result) {
std::cout << edge.src << " -- " << edge.dest << " == " << edge.weight << std::endl;
}
}
主函数
下面是一个简单的主函数,演示如何使用上述实现的图算法:
int main() {
Graph g(9);
g.addEdge(0, 1, 4);
g.addEdge(0, 7, 8);
g.addEdge(1, 2, 8);
g.addEdge(1, 7, 11);
g.addEdge(2, 3, 7);
g.addEdge(2, 8, 2);
g.addEdge(2, 5, 4);
g.addEdge(3, 4, 9);
g.addEdge(3, 5, 14);
g.addEdge(4, 5, 10);
g.addEdge(5, 6, 2);
g.addEdge(6, 7, 1);
g.addEdge(6, 8, 6);
g.addEdge(7, 8, 7);
std::cout << "Breadth First Traversal starting from vertex 0:\n";
g.BFS(0);
std::cout << "\n";
std::cout << "Depth First Traversal starting from vertex 0:\n";
g.DFS(0);
std::cout << "\n";
std::cout << "Dijkstra's shortest path algorithm starting from vertex 0:\n";
g.Dijkstra(0);
std::cout << "\n";
std::cout << "Kruskal's Minimum Spanning Tree algorithm:\n";
g.Kruskal();
return 0;
}
代码说明
- 图的定义:定义了图的构造函数和添加边的函数,使用邻接表存储图。
- 广度优先搜索(BFS):使用队列实现,逐层遍历图。
- 深度优先搜索(DFS):使用递归实现,深入遍历每一个分支。
- Dijkstra算法:找到从源节点到所有其他节点的最短路径。
- Kruskal算法:使用边列表和并查集构建最小生成树(MST)。
这些代码展示了图的常用算法,适用于各种图论问题。
游戏开发领域的数据结构和算法
在大型多人在线角色扮演游戏(MMORPG)中,为了管理复杂的游戏世界、处理海量的数据以及提高性能,使用了许多高级的数据结构。以下是一些常用的数据结构及其用途的详细介绍。
客户端
1. 四叉树(Quadtree)
用途:四叉树主要用于管理二维空间,例如地图、场景管理、碰撞检测和视野检测。
结构:四叉树是一种树形数据结构,每个节点最多有四个子节点。它通过将空间递归地细分成四个象限来组织数据。
实现示例
struct Point {
int x, y;
};
struct Quadtree {
int MAX_CAPACITY = 4;
std::vector<Point> points;
bool divided = false;
Quadtree *northwest, *northeast, *southwest, *southeast; // 可以用数组,加枚举的方式比这样好些
int x, y, width, height;
Quadtree(int x, int y, int width, int height)
: x(x), y(y), width(width), height(height) {
northwest = northeast = southwest = southeast = nullptr;
}
bool insert(Point point) {
if (!contains(point)) return false;
if (points.size() < MAX_CAPACITY) {
points.push_back(point);
return true;
}
if (!divided) subdivide();
if (northwest->insert(point)) return true;
if (northeast->insert(point)) return true;
if (southwest->insert(point)) return true;
if (southeast->insert(point)) return true;
return false;
}
bool contains(Point point) {
return point.x >= x - width && point.x < x + width &&
point.y >= y - height && point.y < y + height;
}
void subdivide() {
int newWidth = width / 2;
int newHeight = height / 2;
northwest = new Quadtree(x - newWidth, y - newHeight, newWidth, newHeight);
northeast = new Quadtree(x + newWidth, y - newHeight, newWidth, newHeight);
southwest = new Quadtree(x - newWidth, y + newHeight, newWidth, newHeight);
southeast = new Quadtree(x + newWidth, y + newHeight, newWidth, newHeight);
divided = true;
}
};
2. 八叉树(Octree)
用途:八叉树主要用于管理三维空间,例如3D场景管理、碰撞检测和视野检测。
结构:八叉树是一种树形数据结构,每个节点最多有八个子节点。它通过将三维空间递归地细分成八个象限来组织数据。
实现示例
struct Point3D {
int x, y, z;
};
struct Octree {
int MAX_CAPACITY = 4;
std::vector<Point3D> points;
bool divided = false;
Octree *children[8];
int x, y, z, size;
Octree(int x, int y, int z, int size)
: x(x), y(y), z(z), size(size) {
std::fill_n(children, 8, nullptr);
}
bool insert(Point3D point) {
if (!contains(point)) return false;
if (points.size() < MAX_CAPACITY) {
points.push_back(point);
return true;
}
if (!divided) subdivide();
for (int i = 0; i < 8; ++i) {
if (children[i]->insert(point)) return true;
}
return false;
}
bool contains(Point3D point) {
return point.x >= x - size && point.x < x + size &&
point.y >= y - size && point.y < y + size &&
point.z >= z - size && point.z < z + size;
}
void subdivide() {
int newSize = size / 2;
children[0] = new Octree(x - newSize, y - newSize, z - newSize, newSize);
children[1] = new Octree(x + newSize, y - newSize, z - newSize, newSize);
children[2] = new Octree(x - newSize, y + newSize, z - newSize, newSize);
children[3] = new Octree(x + newSize, y + newSize, z - newSize, newSize);
children[4] = new Octree(x - newSize, y - newSize, z + newSize, newSize);
children[5] = new Octree(x + newSize, y - newSize, z + newSize, newSize);
children[6] = new Octree(x - newSize, y + newSize, z + newSize, newSize);
children[7] = new Octree(x + newSize, y + newSize, z + newSize, newSize);
divided = true;
}
};
3. 网格(Grid)
用途:网格用于将游戏世界划分成固定大小的单元格,每个单元格用于管理其中的对象,例如单位、建筑物等。这种结构常用于路径查找(如A*算法)和碰撞检测。
结构:网格是二维数组,其中每个元素代表一个单元格。
实现示例
struct Grid {
int width, height;
std::vector<std::vector<int>> cells;
Grid(int width, int height) : width(width), height(height) {
cells.resize(height, std::vector<int>(width, 0));
}
void set(int x, int y, int value) {
cells[y][x] = value;
}
int get(int x, int y) {
return cells[y][x];
}
};
4. 邻接表(Adjacency List)
用途:邻接表用于存储图结构,例如地图、任务系统中的依赖关系等。
结构:邻接表是一个数组或哈希表,其中每个元素是一个列表,列表中存储与该顶点相邻的顶点及权重。
实现示例
class Graph {
private:
int V;
std::vector<std::list<int>> adj;
public:
Graph(int V) {
this->V = V;
adj.resize(V);
}
void addEdge(int v, int w) {
adj[v].push_back(w);
}
void BFS(int s) {
std::vector<bool> visited(V, false);
std::queue<int> queue;
visited[s] = true;
queue.push(s);
while (!queue.empty()) {
s = queue.front();
std::cout << s << " ";
queue.pop();
for (int adjNode : adj[s]) {
if (!visited[adjNode]) {
visited[adjNode] = true;
queue.push(adjNode);
}
}
}
}
};
5. 路径查找(Pathfinding)
A*算法
用途:A*算法用于在网格或图中查找从起点到终点的最短路径。
实现示例
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_map>
struct Node {
int x, y;
int g, h; // g: 从起点到当前节点的代价, h: 从当前节点到终点的估计代价
Node(int x, int y, int g, int h) : x(x), y(y), g(g), h(h) {}
int f() const {
return g + h;
}
bool operator<(const Node& other) const {
return f() > other.f();
}
};
int heuristic(int x1, int y1, int x2, int y2) {
return std::abs(x1 - x2) + std::abs(y1 - y2);
}
std::vector<std::pair<int, int>> AStar(std::vector<std::vector<int>>& grid, std::pair<int, int> start, std::pair<int, int> goal) {
int rows = grid.size();
int cols = grid[0].size();
std::priority_queue<Node> openSet;
std::unordered_map<int, std::unordered_map<int, std::pair<int, int>>> cameFrom;
std::vector<std::vector<int>> gScore(rows, std::vector<int>(cols, INT_MAX));
gScore[start.first][start.second] = 0;
openSet.emplace(start.first, start.second, 0, heuristic(start.first, start.second, goal.first, goal.second));
std::vector<std::pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (!openSet.empty()) {
Node current = openSet.top();
openSet.pop();
if (current.x == goal.first && current.y == goal.second) {
std::vector<std::pair<int, int>> path;
while (cameFrom.count(current.x) && cameFrom[current.x].count(current.y)) {
path.push_back({current.x, current.y});
std::pair<int, int> previous = cameFrom[current.x][current.y];
current.x = previous.first;
current.y = previous.second;
}
std::reverse(path.begin(), path.end());
return path;
}
for (auto& direction : directions) {
int neighborX = current.x + direction.first;
int neighborY = current.y + direction.second;
if (neighborX >= 0 && neighborX < rows && neighborY >= 0 && neighborY < cols && grid[neighborY][neighborX] == 0) {
int tentativeGScore = gScore[current.x][current.y] + 1;
if (tentativeGScore < gScore[neighborX][neighborY]) {
cameFrom[neighborX][neighborY] = {current.x, current.y};
gScore[neighborX][neighborY] = tentativeGScore;
int hScore = heuristic(neighborX, neighborY, goal.first, goal.second);
openSet.emplace(neighborX, neighborY, tentativeGScore, hScore);
}
}
}
}
return {};
}
服务器
在MMORPG(大型多人在线角色扮演游戏)服务端中,为了管理和处理大量玩家和对象,确保游戏的流畅运行,需要使用多种高级的数据结构和算法。以下是一些常用的数据结构和算法的详细介绍:
1. AOI (Area of Interest)
用途:AOI用于管理玩家和对象的可视区域,以减少不必要的数据传输和计算负载。当玩家或对象在游戏世界中移动时,服务器只需要更新它们的可视范围内的对象。
常见实现
- 网格法(Grid-based AOI)
- 圆形区域(Circular AOI)
- 四叉树(Quadtree)
实现示例(网格法)
#include <iostream>
#include <vector>
#include <unordered_set>
struct Object {
int id;
int x, y;
};
class AOIGrid {
private:
int width, height;
int cellSize;
int gridWidth, gridHeight;
std::vector<std::unordered_set<int>> cells;
int getCellIndex(int x, int y) {
int cellX = x / cellSize;
int cellY = y / cellSize;
return cellY * gridWidth + cellX;
}
public:
AOIGrid(int width, int height, int cellSize)
: width(width), height(height), cellSize(cellSize) {
gridWidth = (width + cellSize - 1) / cellSize;
gridHeight = (height + cellSize - 1) / cellSize;
cells.resize(gridWidth * gridHeight);
}
void addObject(Object obj) {
int index = getCellIndex(obj.x, obj.y);
cells[index].insert(obj.id);
}
void removeObject(Object obj) {
int index = getCellIndex(obj.x, obj.y);
cells[index].erase(obj.id);
}
std::unordered_set<int> getNearbyObjects(int x, int y) {
int index = getCellIndex(x, y);
return cells[index];
}
};
int main() {
AOIGrid grid(1000, 1000, 100);
Object player1 = {1, 100, 100};
Object player2 = {2, 150, 150};
grid.addObject(player1);
grid.addObject(player2);
auto nearby = grid.getNearbyObjects(100, 100);
for (int id : nearby) {
std::cout << "Nearby object ID: " << id << std::endl;
}
return 0;
}
十字链表(Cross Linked List)
十字链表算法是一种高效的AOI实现,用于管理游戏中对象的可视区域。它通过维护对象在x轴和y轴上的有序链表,快速确定哪些对象在特定对象的可视范围内。这种方法特别适用于MMORPG等需要管理大量动态对象的游戏。适用于对象密集且动态变化的场景,如MMORPG中的玩家和NPC管理。
对象节点(Object Node)
对象节点包含对象的位置、ID和两个指向其他节点的指针(x轴和y轴上的邻居)。
struct Object {
int id;
int x, y;
Object* xPrev;
Object* xNext;
Object* yPrev;
Object* yNext;
Object(int id, int x, int y) : id(id), x(x), y(y), xPrev(nullptr), xNext(nullptr), yPrev(nullptr), yNext(nullptr) {}
};
十字链表(Cross Linked List)
十字链表维护两个有序链表,分别按x轴和y轴排序。
class AOI {
private:
Object* xHead;
Object* yHead;
public:
AOI() : xHead(nullptr), yHead(nullptr) {}
void insertObject(Object* obj) {
insertIntoXAxis(obj);
insertIntoYAxis(obj);
}
void removeObject(Object* obj) {
removeFromXAxis(obj);
removeFromYAxis(obj);
}
std::vector<Object*> getNearbyObjects(Object* obj, int range) {
std::vector<Object*> nearbyObjects;
getObjectsInRangeX(obj, range, nearbyObjects);
getObjectsInRangeY(obj, range, nearbyObjects);
return nearbyObjects;
}
private:
void insertIntoXAxis(Object* obj) {
if (!xHead) {
xHead = obj;
return;
}
Object* current = xHead;
Object* prev = nullptr;
while (current && current->x < obj->x) {
prev = current;
current = current->xNext;
}
obj->xNext = current;
obj->xPrev = prev;
if (prev) prev->xNext = obj;
else xHead = obj;
if (current) current->xPrev = obj;
}
void insertIntoYAxis(Object* obj) {
if (!yHead) {
yHead = obj;
return;
}
Object* current = yHead;
Object* prev = nullptr;
while (current && current->y < obj->y) {
prev = current;
current = current->yNext;
}
obj->yNext = current;
obj->yPrev = prev;
if (prev) prev->yNext = obj;
else yHead = obj;
if (current) current->yPrev = obj;
}
void removeFromXAxis(Object* obj) {
if (obj->xPrev) obj->xPrev->xNext = obj->xNext;
else xHead = obj->xNext;
if (obj->xNext) obj->xNext->xPrev = obj->xPrev;
}
void removeFromYAxis(Object* obj) {
if (obj->yPrev) obj->yPrev->yNext = obj->yNext;
else yHead = obj->yNext;
if (obj->yNext) obj->yNext->yPrev = obj->yPrev;
}
void getObjectsInRangeX(Object* obj, int range, std::vector<Object*>& nearbyObjects) {
int minX = obj->x - range;
int maxX = obj->x + range;
Object* current = obj;
while (current && current->x >= minX) {
nearbyObjects.push_back(current);
current = current->xPrev;
}
current = obj->xNext;
while (current && current->x <= maxX) {
nearbyObjects.push_back(current);
current = current->xNext;
}
}
void getObjectsInRangeY(Object* obj, int range, std::vector<Object*>& nearbyObjects) {
int minY = obj->y - range;
int maxY = obj->y + range;
Object* current = obj;
while (current && current->y >= minY) {
nearbyObjects.push_back(current);
current = current->yPrev;
}
current = obj->yNext;
while (current && current->y <= maxY) {
nearbyObjects.push_back(current);
current = current->yNext;
}
}
};
使用示例
int main() {
AOI aoi;
Object* obj1 = new Object(1, 100, 100);
Object* obj2 = new Object(2, 150, 150);
Object* obj3 = new Object(3, 200, 200);
aoi.insertObject(obj1);
aoi.insertObject(obj2);
aoi.insertObject(obj3);
std::vector<Object*> nearbyObjects = aoi.getNearbyObjects(obj1, 100);
for (Object* obj : nearbyObjects) {
std::cout << "Nearby object ID: " << obj->id << std::endl;
}
aoi.removeObject(obj2);
nearbyObjects = aoi.getNearbyObjects(obj1, 100);
for (Object* obj : nearbyObjects) {
std::cout << "Nearby object ID: " << obj->id << std::endl;
}
delete obj1;
delete obj2;
delete obj3;
return 0;
}
2. 空间分区(Spatial Partitioning)
用途:空间分区用于组织游戏世界中的对象,以便快速查询和更新对象的位置。常见的空间分区方法包括四叉树、八叉树和KD树。
四叉树实现示例
struct Point {
int x, y;
};
struct Quadtree {
int MAX_CAPACITY = 4;
std::vector<Point> points;
bool divided = false;
Quadtree *northwest, *northeast, *southwest, *southeast;
int x, y, width, height;
Quadtree(int x, int y, int width, int height)
: x(x), y(y), width(width), height(height) {
northwest = northeast = southwest = southeast = nullptr;
}
bool insert(Point point) {
if (!contains(point)) return false;
if (points.size() < MAX_CAPACITY) {
points.push_back(point);
return true;
}
if (!divided) subdivide();
if (northwest->insert(point)) return true;
if (northeast->insert(point)) return true;
if (southwest->insert(point)) return true;
if (southeast->insert(point)) return true;
return false;
}
bool contains(Point point) {
return point.x >= x - width && point.x < x + width &&
point.y >= y - height && point.y < y + height;
}
void subdivide() {
int newWidth = width / 2;
int newHeight = height / 2;
northwest = new Quadtree(x - newWidth, y - newHeight, newWidth, newHeight);
northeast = new Quadtree(x + newWidth, y - newHeight, newWidth, newHeight);
southwest = new Quadtree(x - newWidth, y + newHeight, newWidth, newHeight);
southeast = new Quadtree(x + newWidth, y + newHeight, newWidth, newHeight);
divided = true;
}
};
3. 事件队列(Event Queue)
用途:事件队列用于处理异步事件,例如玩家动作、NPC行为、任务触发等。它保证事件按顺序处理,避免并发问题。
实现示例
#include <queue>
#include <functional>
class EventQueue {
private:
std::queue<std::function<void()>> events;
public:
void pushEvent(std::function<void()> event) {
events.push(event);
}
void processEvents() {
while (!events.empty()) {
events.front()();
events.pop();
}
}
};
int main() {
EventQueue eq;
eq.pushEvent([]() { std::cout << "Player moved" << std::endl; });
eq.pushEvent([]() { std::cout << "NPC attacked" << std::endl; });
eq.processEvents();
return 0;
}
4. 碰撞检测(Collision Detection)
用途:碰撞检测用于检测和处理对象之间的碰撞。例如,玩家与NPC的碰撞,子弹与墙壁的碰撞等。常用的方法有AABB(Axis-Aligned Bounding Box)、圆形碰撞检测等。
实现示例(AABB)
struct AABB {
int x, y, width, height;
bool intersects(const AABB& other) const {
return x < other.x + other.width && x + width > other.x &&
y < other.y + other.height && y + height > other.y;
}
};
int main() {
AABB box1 = {10, 10, 50, 50};
AABB box2 = {30, 30, 50, 50};
if (box1.intersects(box2)) {
std::cout << "Collision detected" << std::endl;
} else {
std::cout << "No collision" << std::endl;
}
return 0;
}
5. 路径查找(Pathfinding)
A*算法
用途:A*算法用于在网格或图中查找从起点到终点的最短路径。
实现示例
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_map>
struct Node {
int x, y;
int g, h; // g: 从起点到当前节点的代价, h: 从当前节点到终点的估计代价
Node(int x, int y, int g, int h) : x(x), y(y), g(g), h(h) {}
int f() const {
return g + h;
}
bool operator<(const Node& other) const {
return f() > other.f();
}
};
int heuristic(int x1, int y1, int x2, int y2) {
return std::abs(x1 - x2) + std::abs(y1 - y2);
}
std::vector<std::pair<int, int>> AStar(std::vector<std::vector<int>>& grid, std::pair<int, int> start, std::pair<int, int> goal) {
int rows = grid.size();
int cols = grid[0].size();
std::priority_queue<Node> openSet;
std::unordered_map<int, std::unordered_map<int, std::pair<int, int>>> cameFrom;
std::vector<std::vector<int>> gScore(rows, std::vector<int>(cols, INT_MAX));
gScore[start.first][start.second] = 0;
openSet.emplace(start.first, start.second, 0, heuristic(start.first, start.second, goal.first, goal.second));
std::vector<std::pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (!openSet.empty()) {
Node current = openSet.top();
openSet.pop();
if (current.x == goal.first && current.y == goal.second) {
std::vector<std::pair<int, int>> path;
while (cameFrom.count(current.x) && cameFrom[current.x].count(current.y)) {
path.push_back({current.x, current.y});
std::pair<int, int> previous = cameFrom[current.x][current.y];
current.x = previous.first;
current.y = previous.second;
}
std::reverse(path.begin(), path.end());
return path;
}
for (auto& direction : directions) {
int neighborX = current.x + direction.first;
int neighborY = current.y + direction.second;
if (neighborX >= 0 && neighborX < rows && neighborY >= 0 && neighborY < cols && grid[neighborY][neighborX] == 0) {
int tentativeGScore = gScore[current.x][current.y] + 1;
if (tentativeGScore < gScore[neighborX][neighborY]) {
cameFrom[neighborX][neighborY] = {current.x, current.y};
gScore[neighborX][neighborY] = tentativeGScore;
int hScore = heuristic(neighborX, neighborY, goal.first, goal.second);
openSet.emplace(neighborX, neighborY, tentativeGScore, hScore);
}
}
}
}
return {};
}
6. 负载均衡
负载均衡(Load Balancing)是一种将请求分配到多个服务器上,以确保系统资源得到充分利用,提高系统的性能、可用性和可伸缩性的技术。负载均衡算法决定了如何分配请求到服务器上,以尽量避免某些服务器过载而导致性能下降,同时保证各服务器的负载相对均衡。
以下是几种常见的负载均衡算法:
1. 轮询(Round Robin)
轮询算法将请求依次分配给服务器列表中的每个服务器,直到所有服务器都被轮询过一次,然后重新开始。这种方法简单且公平,适用于服务器性能相似的场景。
优点:
- 简单易实现。
- 公平地分配请求。
缺点:
- 无法考虑服务器的实际负载情况。
- 若服务器性能差异较大,可能会导致性能不佳。
代码示例:
#include <iostream>
#include <vector>
class RoundRobinBalancer {
private:
std::vector<std::string> servers;
size_t currentIndex;
public:
RoundRobinBalancer(const std::vector<std::string>& serverList) : servers(serverList), currentIndex(0) {}
std::string getNextServer() {
if (servers.empty())
return "";
std::string server = servers[currentIndex];
currentIndex = (currentIndex + 1) % servers.size();
return server;
}
};
int main() {
std::vector<std::string> serverList = {"Server1", "Server2", "Server3"};
RoundRobinBalancer balancer(serverList);
for (int i = 0; i < 5; ++i) {
std::cout << "Request " << i + 1 << " handled by: " << balancer.getNextServer() << std::endl;
}
return 0;
}
2. 加权轮询(Weighted Round Robin)
加权轮询算法在轮询的基础上,为每个服务器分配一个权重值,根据权重值决定每个服务器被轮询的次数。权重值可以根据服务器的性能、带宽、负载等情况进行设置,以更合理地分配请求。
优点:
- 能够根据服务器性能进行动态调整。
- 相对公平地分配请求。
缺点:
- 仍然无法根据实时负载情况进行调整。
代码示例:
#include <iostream>
#include <vector>
#include <random>
class WeightedRoundRobinBalancer {
private:
std::vector<std::pair<std::string, int>> servers;
std::vector<std::string> weightedList;
size_t currentIndex;
public:
WeightedRoundRobinBalancer(const std::vector<std::pair<std::string, int>>& serverList) : servers(serverList), currentIndex(0) {
// 构建加权列表
for (const auto& server : servers) {
for (int i = 0; i < server.second; ++i) {
weightedList.push_back(server.first);
}
}
}
std::string getNextServer() {
if (servers.empty())
return "";
std::string server = weightedList[currentIndex];
currentIndex = (currentIndex + 1) % weightedList.size();
return server;
}
};
int main() {
std::vector<std::pair<std::string, int>> serverList = {{"Server1", 1}, {"Server2", 2}, {"Server3", 1}};
WeightedRoundRobinBalancer balancer(serverList);
for (int i = 0; i < 5; ++i) {
std::cout << "Request " << i + 1 << " handled by: " << balancer.getNextServer() << std::endl;
}
return 0;
}
3. 最少连接(Least Connection)
最少连接算法将请求分配给当前连接数最少的服务器。这种方法能够有效地将请求分配到负载较低的服务器上,以达到负载均衡的目的。
优点:
- 能够考虑到服务器的实时负载情况。
- 能够更有效地利用服务器资源。
缺点:
- 实现相对复杂。
- 如果负载均衡器无法获取到服务器的实时连接数,可能导致分配不均。
代码实现:
#include <iostream>
#include <vector>
class LeastConnectionBalancer {
private:
std::vector<std::string> servers; // 当数据量大,又要求较高性能的时候,可考虑使用最小堆实现。
public:
LeastConnectionBalancer(const std::vector<std::string>& serverList) : servers(serverList) {}
std::string getServerWithLeastConnection() {
if (servers.empty())
return "";
// 假设服务器连接数存储在另外的数据结构中,这里简化为返回第一个服务器
return servers[0];
}
};
int main() {
std::vector<std::string> serverList = {"Server1", "Server2", "Server3"};
LeastConnectionBalancer balancer(serverList);
std::cout << "Request handled by: " << balancer.getServerWithLeastConnection() << std::endl;
return 0;
}
4. IP哈希(IP Hashing)
IP哈希算法根据客户端的IP地址将请求分配给特定的服务器。这种方法保证了相同IP的请求总是被分配到同一台服务器上,适用于需要保持会话一致性的场景,如Web应用的会话管理。
优点:
- 保持会话一致性。
- 对服务器的负载无需关心。
缺点:
- 当某个IP的请求量过大时,可能会导致单个服务器负载过高。
代码实现:
#include <iostream>
#include <string>
#include <unordered_map>
class IPHashBalancer {
private:
std::unordered_map<std::string, std::string> ipServerMap;
public:
IPHashBalancer(const std::unordered_map<std::string, std::string>& ipServerMapping) : ipServerMap(ipServerMapping) {}
std::string getServerForIP(const std::string& ipAddress) {
auto it = ipServerMap.find(ipAddress);
if (it != ipServerMap.end()) {
return it->second;
}
return "";
}
};
int main() {
std::unordered_map<std::string, std::string> ipServerMapping = {{"192.168.0.1", "Server1"}, {"192.168.0.2", "Server2"}, {"192.168.0.3", "Server3"}};
IPHashBalancer balancer(ipServerMapping);
std::cout << "Request from IP 192.168.0.2 handled by: " << balancer.getServerForIP("192.168.0.2") << std::endl;
return 0;
}
5. 响应时间加权(Response Time Weighted)
响应时间加权算法根据服务器的响应时间动态调整权重值,将请求分配给响应时间较短的服务器。这种方法能够自适应地调整负载分配,使得服务器的负载更加均衡。
优点:
- 根据实际响应时间调整负载分配。
- 能够更合理地利用服务器资源。
缺点:
- 实现较为复杂。
- 需要收集和分析服务器的响应时间数据。
代码实现:
#include <iostream>
#include <vector>
#include <random>
#include <chrono>
#include <thread>
// 模拟服务器
class Server {
private:
std::string name;
int initialWeight;
int currentWeight;
public:
Server(const std::string& name, int weight) : name(name), initialWeight(weight), currentWeight(weight) {}
const std::string& getName() const {
return name;
}
int getWeight() const {
return currentWeight;
}
void updateWeight(int newWeight) {
currentWeight = newWeight;
}
// 模拟服务器处理请求的时间
void processRequest() {
// 随机休眠一段时间模拟处理请求
std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 1000));
}
};
// 负载均衡器
class ResponseTimeWeightedBalancer {
private:
std::vector<Server> servers;
public:
ResponseTimeWeightedBalancer(const std::vector<std::string>& serverNames, const std::vector<int>& weights) {
if (serverNames.size() != weights.size()) {
std::cerr << "Error: Number of server names and weights must match." << std::endl;
return;
}
for (size_t i = 0; i < serverNames.size(); ++i) {
servers.emplace_back(serverNames[i], weights[i]);
}
}
// 模拟请求处理,并更新服务器权重
void handleRequest() {
// 模拟请求处理
for (Server& server : servers) {
server.processRequest();
}
// 更新服务器权重
updateWeights();
}
// 根据服务器响应时间动态调整权重
void updateWeights() {
// 这里简化为将服务器的权重设置为其初始权重,实际场景中可以根据服务器的实际响应时间来调整权重
for (Server& server : servers) {
server.updateWeight(server.getWeight());
}
}
// 选择具有最低权重的服务器
Server& selectServer() {
Server* selectedServer = &servers[0];
for (Server& server : servers) {
if (server.getWeight() < selectedServer->getWeight()) {
selectedServer = &server;
}
}
return *selectedServer;
}
};
int main() {
std::vector<std::string> serverNames = {"Server1", "Server2", "Server3"};
std::vector<int> weights = {10, 20, 15};
ResponseTimeWeightedBalancer balancer(serverNames, weights);
// 模拟处理请求
for (int i = 0; i < 5; ++i) {
std::cout << "Handling request " << i + 1 << std::endl;
balancer.handleRequest();
Server& selectedServer = balancer.selectServer();
std::cout << "Request handled by: " << selectedServer.getName() << std::endl;
std::cout << std::endl;
}
return 0;
}
6. 动态权重调整(Dynamic Weight Adjustment)
动态权重调整算法通过实时监测服务器的负载情况,动态地调整服务器的权重值,以确保负载均衡器能够更加准确地分配请求。这种方法结合了最少连接和响应时间加权等算法的优点,适用于需要实时调整负载分配的场景。
优点:
- 根据实时负载情况动态调整负载分配。
- 能够更灵活地应对服务器负载变化。
缺点:
- 实现复杂度较高。
- 需要实时监测服务器负载情况,并进行动态调整。
代码实现:
#include <iostream>
#include <vector>
#include <random>
#include <chrono>
#include <thread>
class Server {
private:
std::string name;
int initialWeight;
int currentWeight;
public:
Server(const std::string& name, int weight) : name(name), initialWeight(weight), currentWeight(weight) {}
const std::string& getName() const {
return name;
}
int getWeight() const {
return currentWeight;
}
void updateWeight(int newWeight) {
currentWeight = newWeight;
}
// 模拟服务器处理请求的时间
void processRequest() {
// 随机休眠一段时间模拟处理请求
std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 1000));
}
};
class DynamicWeightAdjustmentBalancer {
private:
std::vector<Server> servers;
public:
DynamicWeightAdjustmentBalancer(const std::vector<std::string>& serverNames, const std::vector<int>& weights) {
if (serverNames.size() != weights.size()) {
std::cerr << "Error: Number of server names and weights must match." << std::endl;
return;
}
for (size_t i = 0; i < serverNames.size(); ++i) {
servers.emplace_back(serverNames[i], weights[i]);
}
}
// 模拟请求处理,并更新服务器权重
void handleRequest() {
// 模拟请求处理
for (Server& server : servers) {
server.processRequest();
}
// 更新服务器权重
updateWeights();
}
// 根据服务器响应时间动态调整权重
void updateWeights() {
for (Server& server : servers) {
// 模拟服务器响应时间,这里简化为随机生成一个响应时间
int responseTime = rand() % 1000;
// 根据实际响应时间调整权重
if (responseTime < 500) { // 如果响应时间较短,则增加权重
server.updateWeight(server.getWeight() + 1);
} else if (responseTime > 800) { // 如果响应时间较长,则降低权重
server.updateWeight(server.getWeight() - 1);
}
}
}
// 选择具有最低权重的服务器
Server& selectServer() {
Server* selectedServer = &servers[0];
for (Server& server : servers) {
if (server.getWeight() < selectedServer->getWeight()) {
selectedServer = &server;
}
}
return *selectedServer;
}
};
int main() {
std::vector<std::string> serverNames = {"Server1", "Server2", "Server3"};
std::vector<int> weights = {10, 20, 15};
DynamicWeightAdjustmentBalancer balancer(serverNames, weights);
// 模拟处理请求
for (int i = 0; i < 5; ++i) {
std::cout << "Handling request " << i + 1 << std::endl;
balancer.handleRequest();
Server& selectedServer = balancer.selectServer();
std::cout << "Request handled by: " << selectedServer.getName() << ", Weight: " << selectedServer.getWeight() << std::endl;
std::cout << std::endl;
}
return 0;
}