C++常见面试题-5.数据结构

五、数据结构

5.1 线性数据结构

  1. 数组和链表的区别?

    • 数组(Array)

      • 存储方式:连续的内存空间;
      • 访问方式:支持随机访问,通过索引直接访问元素,时间复杂度为O(1);
      • 插入/删除:在中间位置插入或删除元素时,需要移动其他元素,时间复杂度为O(n);
      • 空间利用率:内存利用率高,不需要额外的指针开销;
      • 适用场景:需要频繁随机访问元素的场景。
    • 链表(Linked List)

      • 存储方式:不连续的内存空间,每个节点包含数据和指向下一个(或前一个)节点的指针;
      • 访问方式:不支持随机访问,只能从头开始遍历,时间复杂度为O(n);
      • 插入/删除:在任意位置插入或删除元素时,只需要修改指针,时间复杂度为O(1);
      • 空间利用率:内存利用率较低,每个节点需要额外的指针开销;
      • 适用场景:需要频繁在任意位置插入或删除元素的场景。
      // 数组示例
      void test_array() {int arr[5] = {1, 2, 3, 4, 5};  // 静态数组std::vector<int> vec = {1, 2, 3, 4, 5};  // 动态数组// 随机访问,O(1)时间复杂度std::cout << arr[2] << std::endl;  // 输出3std::cout << vec[3] << std::endl;  // 输出4// 插入元素,需要移动后面的元素,O(n)时间复杂度vec.insert(vec.begin() + 2, 10);  // 在索引2处插入10
      }// 链表示例(自定义简单链表)
      struct Node {int data;Node* next;Node(int val) : data(val), next(nullptr) {}
      };void test_linked_list() {// 创建链表:1 -> 2 -> 3Node* head = new Node(1);head->next = new Node(2);head->next->next = new Node(3);// 访问元素,需要遍历,O(n)时间复杂度Node* current = head;while (current) {std::cout << current->data << " ";  // 输出1 2 3current = current->next;}// 在中间插入元素,O(1)时间复杂度(假设已经找到插入位置)Node* new_node = new Node(10);new_node->next = head->next;  // 10的next指向2head->next = new_node;        // 1的next指向10,现在链表变为1->10->2->3// 释放内存current = head;while (current) {Node* temp = current;current = current->next;delete temp;}
      }
      
  2. 栈和队列的区别?它们的应用场景有哪些?

    • 栈(Stack)

      • 特点:后进先出(LIFO, Last In First Out),只能在一端(栈顶)进行插入和删除操作;
      • 基本操作:push(入栈)、pop(出栈)、top(查看栈顶元素)、empty(判断是否为空);
      • 实现方式:可以用数组或链表实现;
      • 应用场景:函数调用、表达式求值、括号匹配、深度优先搜索(DFS)等。
    • 队列(Queue)

      • 特点:先进先出(FIFO, First In First Out),只能在一端(队尾)插入,另一端(队头)删除;
      • 基本操作:enqueue(入队)、dequeue(出队)、front(查看队头元素)、empty(判断是否为空);
      • 实现方式:可以用数组或链表实现;
      • 应用场景:任务调度、缓冲处理、广度优先搜索(BFS)等。
      // 栈示例
      void test_stack() {std::stack<int> st;// 入栈st.push(1);st.push(2);st.push(3);// 访问栈顶元素std::cout << "Top: " << st.top() << std::endl;  // 输出3// 出栈st.pop();std::cout << "After pop, top: " << st.top() << std::endl;  // 输出2// 判断是否为空while (!st.empty()) {std::cout << st.top() << " ";  // 输出2 1st.pop();}
      }// 队列示例
      void test_queue() {std::queue<int> q;// 入队q.push(1);q.push(2);q.push(3);// 访问队头元素std::cout << "Front: " << q.front() << std::endl;  // 输出1// 出队q.pop();std::cout << "After pop, front: " << q.front() << std::endl;  // 输出2// 判断是否为空while (!q.empty()) {std::cout << q.front() << " ";  // 输出2 3q.pop();}
      }// 栈的应用:括号匹配
      bool is_valid_parentheses(const std::string& s) {std::stack<char> st;for (char c : s) {// 左括号入栈if (c == '(' || c == '[' || c == '{') {st.push(c);} else {// 右括号,检查是否匹配if (st.empty()) return false;char top = st.top();st.pop();if ((c == ')' && top != '(') ||(c == ']' && top != '[') ||(c == '}' && top != '{')) {return false;}}}return st.empty();  // 所有括号都应匹配
      }// 队列的应用:简单的任务调度
      void task_scheduler() {std::queue<std::string> tasks;// 添加任务tasks.push("Task 1");tasks.push("Task 2");tasks.push("Task 3");// 处理任务(先进先出)while (!tasks.empty()) {std::string current = tasks.front();tasks.pop();std::cout << "Processing " << current << std::endl;}
      }
      
  3. 什么是双端队列(Deque)?它与普通队列的区别?

    • 双端队列(Double-Ended Queue,Deque)

      • 是一种允许在两端(队首和队尾)进行插入和删除操作的数据结构;
      • 结合了栈和队列的特点,可以在两端高效地进行操作;
      • 在STL中,deque通常实现为分段连续的内存块,通过中央控制结构(如指针数组)管理这些内存块;
      • 基本操作:push_front(队首入队)、push_back(队尾入队)、pop_front(队首出队)、pop_back(队尾出队)、front(查看队首元素)、back(查看队尾元素)、empty(判断是否为空)。
    • 与普通队列的区别

      • 操作灵活性:普通队列只允许在队尾插入、队首删除;而双端队列允许在两端进行插入和删除;
      • 实现方式:普通队列通常基于链表或循环数组实现;双端队列通常基于分段连续内存实现;
      • 应用场景:双端队列适用于需要在两端频繁操作的场景,如滑动窗口算法、双端栈等。
      // 双端队列示例
      void test_deque() {std::deque<int> dq;// 队尾入队dq.push_back(1);dq.push_back(2);// 队首入队dq.push_front(0);// 遍历双端队列std::cout << "Deque elements: ";for (int val : dq) {std::cout << val << " ";  // 输出0 1 2}std::cout << std::endl;// 访问队首和队尾元素std::cout << "Front: " << dq.front() << std::endl;  // 输出0std::cout << "Back: " << dq.back() << std::endl;    // 输出2// 队首出队dq.pop_front();std::cout << "After pop_front, front: " << dq.front() << std::endl;  // 输出1// 队尾出队dq.pop_back();std::cout << "After pop_back, back: " << dq.back() << std::endl;    // 输出1
      }// 双端队列的应用:滑动窗口最大值
      std::vector<int> max_sliding_window(const std::vector<int>& nums, int k) {std::vector<int> result;std::deque<int> dq;  // 存储索引,保持队首为当前窗口最大值的索引for (int i = 0; i < nums.size(); ++i) {// 移除超出窗口范围的元素(队首)if (!dq.empty() && dq.front() == i - k) {dq.pop_front();}// 移除所有小于当前元素的值(队尾),保持队列单调递减while (!dq.empty() && nums[dq.back()] < nums[i]) {dq.pop_back();}// 添加当前元素的索引dq.push_back(i);// 当窗口形成时,记录最大值(队首元素)if (i >= k - 1) {result.push_back(nums[dq.front()]);}}return result;
      }
      

5.2 非线性数据结构

  1. 树的基本概念?二叉树有哪些遍历方式?

    • 树的基本概念

      • 树是一种非线性的层次结构,由节点组成,没有环路;
      • 根节点:没有父节点的节点;
      • 叶子节点:没有子节点的节点;
      • 父节点/子节点:一个节点的直接前驱/后继节点;
      • 兄弟节点:具有相同父节点的节点;
      • 深度:从根节点到当前节点的路径长度;
      • 高度:从当前节点到最远叶子节点的路径长度;
      • 子树:以某节点为根的子结构。
    • 二叉树(Binary Tree):每个节点最多有两个子节点(左子节点和右子节点)。

    • 二叉树的遍历方式

      • 前序遍历(Pre-order Traversal):根->左->右;
      • 中序遍历(In-order Traversal):左->根->右;
      • 后序遍历(Post-order Traversal):左->右->根;
      • 层序遍历(Level-order Traversal):按层从左到右遍历。
      // 二叉树节点定义
      struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
      };// 前序遍历(递归实现)
      void preorder_traversal(TreeNode* root) {if (root == nullptr) return;std::cout << root->val << " ";  // 根preorder_traversal(root->left);  // 左preorder_traversal(root->right); // 右
      }// 中序遍历(递归实现)
      void inorder_traversal(TreeNode* root) {if (root == nullptr) return;inorder_traversal(root->left);   // 左std::cout << root->val << " ";  // 根inorder_traversal(root->right);  // 右
      }// 后序遍历(递归实现)
      void postorder_traversal(TreeNode* root) {if (root == nullptr) return;postorder_traversal(root->left);  // 左postorder_traversal(root->right); // 右std::cout << root->val << " ";   // 根
      }// 层序遍历(非递归实现,使用队列)
      void level_order_traversal(TreeNode* root) {if (root == nullptr) return;std::queue<TreeNode*> q;q.push(root);while (!q.empty()) {int level_size = q.size();// 处理当前层的所有节点for (int i = 0; i < level_size; ++i) {TreeNode* current = q.front();q.pop();std::cout << current->val << " ";// 将子节点入队if (current->left) q.push(current->left);if (current->right) q.push(current->right);}std::cout << std::endl;  // 一层结束,换行}
      }// 测试用例:创建一个二叉树
      //      1
      //     / \
      //    2   3
      //   / \   \
      //  4   5   6
      void test_tree_traversal() {TreeNode* root = new TreeNode(1);root->left = new TreeNode(2);root->right = new TreeNode(3);root->left->left = new TreeNode(4);root->left->right = new TreeNode(5);root->right->right = new TreeNode(6);std::cout << "Pre-order traversal: ";preorder_traversal(root);  // 输出:1 2 4 5 3 6std::cout << std::endl;std::cout << "In-order traversal: ";inorder_traversal(root);   // 输出:4 2 5 1 3 6std::cout << std::endl;std::cout << "Post-order traversal: ";postorder_traversal(root); // 输出:4 5 2 6 3 1std::cout << std::endl;std::cout << "Level-order traversal: " << std::endl;level_order_traversal(root);  // 按层输出:1//           2 3//           4 5 6// 释放内存(实际应用中应使用智能指针或递归释放)// 这里简化处理
      }
      
  2. 什么是二叉搜索树(BST)?它有什么特点?

    • 二叉搜索树(Binary Search Tree,BST)

      • 是一种特殊的二叉树,具有以下性质:
        • 左子树上所有节点的值均小于根节点的值;
        • 右子树上所有节点的值均大于根节点的值;
        • 左子树和右子树也分别是二叉搜索树(递归定义);
        • (可选)不允许有重复的值(某些实现允许重复值)。
      • 特点
        • 排序特性:中序遍历BST可以得到一个有序的序列;
        • 查找效率:在平衡的BST中,查找、插入、删除的时间复杂度均为O(log n);
        • 不平衡风险:如果插入的是有序数据,BST可能退化为链表,时间复杂度变为O(n)。
      • 常见操作:查找、插入、删除、求最大值/最小值、求前驱/后继等。
      // 二叉搜索树的实现(简化版)
      class BST {
      private:TreeNode* root;// 递归插入TreeNode* insert_recursive(TreeNode* node, int val) {if (node == nullptr) {return new TreeNode(val);}if (val < node->val) {node->left = insert_recursive(node->left, val);} else if (val > node->val) {node->right = insert_recursive(node->right, val);} // 如果val已存在,可以选择不插入或插入到右子树return node;}// 递归查找TreeNode* search_recursive(TreeNode* node, int val) {if (node == nullptr || node->val == val) {return node;}if (val < node->val) {return search_recursive(node->left, val);} else {return search_recursive(node->right, val);}}// 找到最小值节点TreeNode* find_min(TreeNode* node) {while (node->left != nullptr) {node = node->left;}return node;}// 递归删除TreeNode* delete_recursive(TreeNode* node, int val) {if (node == nullptr) return nullptr;if (val < node->val) {node->left = delete_recursive(node->left, val);} else if (val > node->val) {node->right = delete_recursive(node->right, val);} else {// 节点有0或1个子节点if (node->left == nullptr) {TreeNode* temp = node->right;delete node;return temp;} else if (node->right == nullptr) {TreeNode* temp = node->left;delete node;return temp;}// 节点有2个子节点,找到右子树中的最小值节点(后继)TreeNode* successor = find_min(node->right);node->val = successor->val;  // 替换值node->right = delete_recursive(node->right, successor->val);  // 删除后继节点}return node;}// 递归释放内存void destroy_tree(TreeNode* node) {if (node) {destroy_tree(node->left);destroy_tree(node->right);delete node;}}// 递归中序遍历void inorder_recursive(TreeNode* node) {if (node) {inorder_recursive(node->left);std::cout << node->val << " ";inorder_recursive(node->right);}}public:BST() : root(nullptr) {}~BST() {destroy_tree(root);}void insert(int val) {root = insert_recursive(root, val);}bool search(int val) {return search_recursive(root, val) != nullptr;}void remove(int val) {root = delete_recursive(root, val);}void inorder_traversal() {inorder_recursive(root);std::cout << std::endl;}
      };void test_bst() {BST bst;// 插入元素bst.insert(50);bst.insert(30);bst.insert(70);bst.insert(20);bst.insert(40);bst.insert(60);bst.insert(80);// 中序遍历(应输出有序序列)std::cout << "In-order traversal: ";bst.inorder_traversal();  // 输出:20 30 40 50 60 70 80// 查找元素std::cout << "Search 40: " << (bst.search(40) ? "Found" : "Not found") << std::endl;  // Foundstd::cout << "Search 45: " << (bst.search(45) ? "Found" : "Not found") << std::endl;  // Not found// 删除元素bst.remove(20);  // 删除叶子节点std::cout << "After removing 20: ";bst.inorder_traversal();  // 输出:30 40 50 60 70 80bst.remove(30);  // 删除有一个子节点的节点std::cout << "After removing 30: ";bst.inorder_traversal();  // 输出:40 50 60 70 80bst.remove(50);  // 删除有两个子节点的节点std::cout << "After removing 50: ";bst.inorder_traversal();  // 输出:40 60 70 80
      }
      
  3. 什么是平衡二叉树?常见的平衡二叉树有哪些?

    • 平衡二叉树(Balanced Binary Tree)

      • 是一种特殊的二叉搜索树,通过一定的机制保证树的高度平衡,从而保证基本操作(查找、插入、删除)的时间复杂度为O(log n);
      • 平衡条件:任意节点的左右子树高度差不超过某个常数(通常为1)。
    • 常见的平衡二叉树

      • AVL树(Adelson-Velsky and Landis Tree)
        • 左右子树高度差不超过1;
        • 通过旋转操作(左旋、右旋、左右旋、右左旋)来保持平衡;
        • 适用于查找频繁、插入删除相对较少的场景。
      • 红黑树(Red-Black Tree)
        • 是一种自平衡的二叉搜索树,每个节点要么是红色,要么是黑色;
        • 通过颜色规则和旋转操作来保持平衡;
        • 相比AVL树,红黑树在插入删除时的旋转操作更少,因此在频繁修改的场景下性能更好;
        • STL中的map、set、multimap、multiset等关联容器底层就是基于红黑树实现的。
      • B树(B-Tree)和B+树(B+Tree)
        • 是多路平衡查找树,每个节点可以有多个子节点;
        • B树的所有节点都存储数据;B+树只有叶子节点存储数据,非叶子节点仅用于索引;
        • 广泛应用于文件系统和数据库索引。
      • Splay树(伸展树)
        • 通过频繁访问的节点向上移动(伸展)来保持树的平衡;
        • 不严格保证树的高度平衡,但在实际应用中表现良好。
      // 红黑树的特性(在STL中的应用)
      void test_rb_tree_in_stl() {// std::map底层基于红黑树实现std::map<int, std::string> my_map;// 插入元素(自动排序,保持平衡)my_map[3] = "three";my_map[1] = "one";my_map[4] = "four";my_map[2] = "two";my_map[5] = "five";// 遍历(按键排序)std::cout << "Map elements (ordered):" << std::endl;for (const auto& pair : my_map) {std::cout << pair.first << ": " << pair.second << std::endl;}// 输出顺序:1: one, 2: two, 3: three, 4: four, 5: five// std::set底层也基于红黑树实现std::set<int> my_set = {5, 2, 8, 1, 3};// 遍历(有序)std::cout << "Set elements (ordered):" << std::endl;for (int val : my_set) {std::cout << val << " ";  // 输出:1 2 3 5 8}std::cout << std::endl;
      }
      
  4. 什么是哈希表?如何处理哈希冲突?

    • 哈希表(Hash Table)

      • 是一种根据键(key)直接访问值(value)的数据结构;
      • 通过哈希函数将键映射到表中的一个位置,从而实现快速查找;
      • 理想情况下,查找、插入、删除的时间复杂度为O(1)。
    • 哈希函数(Hash Function)

      • 用于将键转换为表索引的函数;
      • 好的哈希函数应具有:计算简单、分布均匀(减少冲突)、不可逆性等特性。
    • 哈希冲突(Hash Collision)

      • 不同的键通过哈希函数计算后得到相同的表索引;
      • 任何哈希表都无法完全避免哈希冲突。
    • 处理哈希冲突的方法

      • 链地址法(Separate Chaining)
        • 每个哈希表位置维护一个链表,存储哈希到该位置的所有键值对;
        • 查找时,先通过哈希函数找到对应链表,然后在链表中线性查找;
        • 优点:实现简单,处理冲突灵活;
        • 缺点:需要额外的内存空间存储链表指针。
      • 开放地址法(Open Addressing)
        • 当发生冲突时,寻找表中的其他空闲位置存储键值对;
        • 常见的探测方法:线性探测(Linear Probing)、二次探测(Quadratic Probing)、双重哈希(Double Hashing);
        • 优点:不需要额外的指针空间;
        • 缺点:容易出现聚类问题,删除操作复杂。
      • 再哈希法(Rehashing)
        • 当哈希表负载因子(键值对数量/表大小)超过阈值时,重新分配更大的内存空间,并重新计算所有键的哈希值;
        • 常用于动态调整哈希表大小,保证性能。
      • 建立公共溢出区
        • 建立一个公共的溢出表,将所有冲突的键值对都存储在溢出表中;
        • 查找时,先在主表中查找,若找不到则到溢出表中查找。
      // 简单的哈希表实现(使用链地址法处理冲突)
      template <typename K, typename V>
      class HashTable {
      private:struct Node {K key;V value;Node* next;Node(const K& k, const V& v) : key(k), value(v), next(nullptr) {}};std::vector<Node*> table;  // 哈希表数组size_t capacity;           // 表容量size_t size;               // 当前元素数量// 简单的哈希函数size_t hash(const K& key) const {// 这里使用std::hash,实际应用中可能需要更复杂的哈希函数return std::hash<K>()(key) % capacity;}// 扩容(再哈希)void rehash() {std::vector<Node*> old_table = table;// 创建新的更大的表capacity *= 2;table.assign(capacity, nullptr);size = 0;// 重新插入所有元素for (Node* head : old_table) {while (head) {Node* temp = head;insert(head->key, head->value);  // 重新插入head = head->next;delete temp;}}}public:HashTable(size_t initial_capacity = 16) : capacity(initial_capacity), size(0) {table.resize(capacity, nullptr);}~HashTable() {// 释放所有内存for (Node* head : table) {while (head) {Node* temp = head;head = head->next;delete temp;}}}void insert(const K& key, const V& value) {// 检查是否需要扩容(负载因子阈值设为0.75)if (size >= capacity * 0.75) {rehash();}size_t index = hash(key);// 检查key是否已存在Node* current = table[index];while (current) {if (current->key == key) {current->value = value;  // 更新已存在的键的值return;}current = current->next;}// 插入新节点(头插法)Node* new_node = new Node(key, value);new_node->next = table[index];table[index] = new_node;size++;}bool find(const K& key, V& value) const {size_t index = hash(key);Node* current = table[index];while (current) {if (current->key == key) {value = current->value;return true;}current = current->next;}return false;  // 未找到}bool remove(const K& key) {size_t index = hash(key);Node* current = table[index];Node* prev = nullptr;while (current) {if (current->key == key) {if (prev) {prev->next = current->next;} else {table[index] = current->next;}delete current;size--;return true;}prev = current;current = current->next;}return false;  // 未找到要删除的键}size_t get_size() const {return size;}
      };void test_hash_table() {HashTable<std::string, int> ht;// 插入元素ht.insert("apple", 5);ht.insert("banana", 7);ht.insert("orange", 3);ht.insert("grape", 10);// 查找元素int value;if (ht.find("banana", value)) {std::cout << "banana: " << value << std::endl;  // 输出:banana: 7}// 测试哈希冲突(假设两个键哈希到同一个索引)// 这里为了演示,我们使用两个可能产生冲突的键ht.insert("test1", 100);ht.insert("test2", 200);  // 假设与test1冲突if (ht.find("test1", value)) {std::cout << "test1: " << value << std::endl;  // 应该能找到test1}if (ht.find("test2", value)) {std::cout << "test2: " << value << std::endl;  // 应该能找到test2}// 删除元素ht.remove("orange");if (!ht.find("orange", value)) {std::cout << "orange has been removed." << std::endl;}// 检查大小std::cout << "Hash table size: " << ht.get_size() << std::endl;  // 输出:4
      }
      
  5. 图的基本概念?图的存储方式有哪些?

    • 图的基本概念

      • 图是由顶点(Vertex)和边(Edge)组成的一种数据结构;
      • 有向图:边有方向,从一个顶点指向另一个顶点;
      • 无向图:边没有方向,连接的两个顶点是相互可达的;
      • 顶点的度:与该顶点相连的边的数量(有向图分为入度和出度);
      • 路径:从一个顶点到另一个顶点的边的序列;
      • :起点和终点相同的路径。
    • 图的存储方式

      • 邻接矩阵(Adjacency Matrix)
        • 使用二维数组表示图,matrix[i][j]表示顶点i到顶点j是否有边;
        • 优点:判断两个顶点是否相邻的时间复杂度为O(1),适合稠密图;
        • 缺点:空间复杂度为O(n²),不适合稀疏图。
      • 邻接表(Adjacency List)
        • 每个顶点对应一个链表,链表中存储该顶点的所有邻接顶点;
        • 优点:空间复杂度为O(n + m)(n为顶点数,m为边数),适合稀疏图;
        • 缺点:判断两个顶点是否相邻的时间复杂度为O(d)(d为顶点的度)。
      • 十字链表(Orthogonal List)
        • 有向图的一种存储方式,每条边用一个节点表示,同时存储入边和出边信息;
        • 适合处理有向图的复杂操作。
      • 邻接多重表(Adjacency Multilist)
        • 无向图的一种存储方式,每条边用一个节点表示,避免重复存储;
        • 适合处理无向图的边操作。
      // 图的表示方法示例// 邻接矩阵表示
      class GraphMatrix {
      private:int** matrix;  // 邻接矩阵int num_vertices;  // 顶点数bool directed;  // 是否为有向图public:GraphMatrix(int n, bool is_directed = true) : num_vertices(n), directed(is_directed) {// 初始化邻接矩阵matrix = new int*[num_vertices];for (int i = 0; i < num_vertices; ++i) {matrix[i] = new int[num_vertices]();  // 初始化为0}}~GraphMatrix() {// 释放内存for (int i = 0; i < num_vertices; ++i) {delete[] matrix[i];}delete[] matrix;}void add_edge(int from, int to, int weight = 1) {if (from >= 0 && from < num_vertices && to >= 0 && to < num_vertices) {matrix[from][to] = weight;  // 添加边if (!directed) {matrix[to][from] = weight;  // 无向图需要添加反向边}}}void remove_edge(int from, int to) {if (from >= 0 && from < num_vertices && to >= 0 && to < num_vertices) {matrix[from][to] = 0;  // 移除边if (!directed) {matrix[to][from] = 0;  // 无向图需要移除反向边}}}bool has_edge(int from, int to) const {if (from >= 0 && from < num_vertices && to >= 0 && to < num_vertices) {return matrix[from][to] != 0;}return false;}void print() const {for (int i = 0; i < num_vertices; ++i) {for (int j = 0; j < num_vertices; ++j) {std::cout << matrix[i][j] << " ";}std::cout << std::endl;}}
      };// 邻接表表示
      class GraphList {
      private:std::vector<std::vector<std::pair<int, int>>> adj_list;  // 邻接表,存储邻接顶点和权重int num_vertices;  // 顶点数bool directed;  // 是否为有向图public:GraphList(int n, bool is_directed = true) : num_vertices(n), directed(is_directed) {adj_list.resize(num_vertices);}void add_edge(int from, int to, int weight = 1) {if (from >= 0 && from < num_vertices && to >= 0 && to < num_vertices) {adj_list[from].emplace_back(to, weight);  // 添加边if (!directed) {adj_list[to].emplace_back(from, weight);  // 无向图需要添加反向边}}}void remove_edge(int from, int to) {if (from >= 0 && from < num_vertices && to >= 0 && to < num_vertices) {// 移除from到to的边auto& list = adj_list[from];list.erase(std::remove_if(list.begin(), list.end(),[to](const std::pair<int, int>& p) { return p.first == to; }),list.end());if (!directed) {// 移除to到from的边auto& reverse_list = adj_list[to];reverse_list.erase(std::remove_if(reverse_list.begin(), reverse_list.end(),[from](const std::pair<int, int>& p) { return p.first == from; }),reverse_list.end());}}}bool has_edge(int from, int to) const {if (from >= 0 && from < num_vertices && to >= 0 && to < num_vertices) {const auto& list = adj_list[from];return std::any_of(list.begin(), list.end(),[to](const std::pair<int, int>& p) { return p.first == to; });}return false;}void print() const {for (int i = 0; i < num_vertices; ++i) {std::cout << "Vertex " << i << ": ";for (const auto& edge : adj_list[i]) {std::cout << "(" << edge.first << ", " << edge.second << ") ";}std::cout << std::endl;}}
      };void test_graph_representations() {// 测试邻接矩阵std::cout << "Testing GraphMatrix (directed):" << std::endl;GraphMatrix gm(5, true);  // 有向图,5个顶点gm.add_edge(0, 1);gm.add_edge(0, 2);gm.add_edge(1, 3);gm.add_edge(2, 3);gm.add_edge(3, 4);gm.print();std::cout << "Has edge 0->2: " << (gm.has_edge(0, 2) ? "Yes" : "No") << std::endl;// 测试邻接表std::cout << "\nTesting GraphList (undirected):" << std::endl;GraphList gl(5, false);  // 无向图,5个顶点gl.add_edge(0, 1, 2);gl.add_edge(0, 2, 3);gl.add_edge(1, 3, 4);gl.add_edge(2, 3, 1);gl.add_edge(3, 4, 5);gl.print();std::cout << "Has edge 2->0: " << (gl.has_edge(2, 0) ? "Yes" : "No") << std::endl;// 测试删除边gl.remove_edge(1, 3);std::cout << "After removing edge 1-3:\n";gl.print();
      }
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2804.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Node.js 在 Windows Server 上的离线部署方案

Node.js 在 Windows Server 上的离线部署方案 离线部署的核心是提前准备所有依赖资源&#xff08;避免在线下载&#xff09;&#xff0c;并通过本地配置完成服务搭建&#xff0c;整体分为「依赖准备」「环境配置」「项目部署」「服务注册」4个阶段。 一、提前准备离线资源&am…

18.web api 9

3.M端事件4.js插件

母猪姿态转换行为识别:计算机视觉与行为识别模型调优指南

> 在现代智能化养殖中,母猪姿态识别是健康监测的关键技术。本文将带你从0到1构建高精度母猪姿态识别系统,准确率可达95%以上! ## 一、为什么母猪姿态识别如此重要? 母猪的行为姿态是其健康状况的重要指标: - **站立姿态**:可能表示发情期或进食需求 - **侧卧姿态**:…

Unity进阶--C#补充知识点--【Unity跨平台的原理】Mono与IL2CPP

来源于唐老狮的视频教学&#xff0c;仅作记录和感悟记录&#xff0c;方便日后复习或者查找 一.跨平台基本原理 知识回顾&#xff1a; ①在之前我们已经知道了跨语言的原理是.Net体系下定义了这些语言需要遵守的工业标准CLI。因此实现了面向.Net的语言都可以被编译转化成统一规…

LeetCode:无重复字符的最长子串

目录 解题过程: 描述: 分析条件: 正确解题思路: 通过这道题可以学到什么: 解题过程: 描述: 3. 无重复字符的最长子串 提示 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为…

JUC读写锁

文章目录一、读写锁概述1.1 核心目标1.2 核心思想1.3 关键规则与保证1.4 核心组件二、使用示例2.1 采用独占锁的姿势读、写数据2.2 使用读写锁读、写数据2.3 锁降级 **&#xff08;Lock Downgrading&#xff09;**三、应用场景3.1 缓存系统【高频读、低频更新】3.2 配置中心【配…

docker compose再阿里云上无法使用的问题

最原始的Dokcerfile # 使用官方Python 3.6.8镜像 FROM python:3.6.8-slimWORKDIR /app# 复制依赖文件 COPY requirements.txt .RUN pip install --upgrade pip # 检查并安装依赖&#xff08;自动处理未安装的包&#xff09; RUN pip install --no-cache-dir -r requirements.tx…

【运维进阶】LNMP + WordPress 自动化部署实验

LNMP WordPress 自动化部署实验 一、实验目标 通过 Ansible 自动化工具&#xff0c;在目标服务器&#xff08;lnmp 主机组&#xff09;上搭建 LNMP 架构&#xff08;Linux 系统 Nginx 网页服务器 MariaDB 数据库 PHP 脚本语言&#xff09;&#xff0c;并部署 WordPress 博…

豆包 Java的23种设计模式

Java的23种设计模式是软件开发中常用的设计思想总结&#xff0c;根据用途可分为三大类&#xff1a;创建型、结构型和行为型。 一、创建型模式&#xff08;5种&#xff09; 用于处理对象创建机制&#xff0c;隐藏创建逻辑&#xff0c;使程序更灵活。 单例模式&#xff1a;保证一…

RISC-V汇编新手入门

有空就更。一、基础核心概念&#xff1a;什么是汇编语言&#xff1f;汇编语言是直接对应 CPU 指令的低级编程语言&#xff0c;每一行汇编代码基本对应一条 CPU 能直接执行的指令。相比 C 语言等高级语言&#xff0c;汇编更贴近硬件&#xff0c;能直接操作 CPU 的寄存器、内存和…

[每周一更]-(第155期):Go 1.25 发布:新特性、技术思考与 Go vs Rust 竞争格局分析

作为一名 Go 研发工程师&#xff0c;我一直关注 Go 语言的演进。2025 年 8 月 12 日&#xff0c;Go 团队发布了 Go 1.25 版本&#xff0c;这是继 Go 1.24 之后的又一重要更新。 这个版本聚焦于工具链优化、运行时改进和实验性功能引入&#xff0c;没有引入破坏性语言变化&#…

【网络安全】Webshell的绕过——绕过动态检测引擎WAF-缓存绕过(Hash碰撞)

目录 一、前言 二、环境 三、了解动态检测引擎 3.1 shuffle — 打乱数组 3.2 mt_srand — 播下一个更好的随机数发生器种子 四、缓存导致的绕过【hash碰撞】 五、总结 一、前言 在渗透测试过程中&#xff0c;成功获取 WebShell 时难免遇到 Web 应用防火墙&#xff08;WA…