C++数据结构与算法详解:链表、栈、队列、树、二叉树和图结构的实现与应用

news/2025/2/3 22:39:51/

C/C++ 基础知识 三

  • 一、链表
    • 1.1 定义
    • 1.2 实现
      • 1.2.1 单向链表
      • 1.2.2 双向链表
      • 1.2.3 常见操作(反转 合并 查找)
    • 1.3 应用
  • 二、 栈-Stack 队列-Queue
    • 1.1 定义
    • 1.2 实现
      • 1.2.1 数组实现 栈和队列 示例:
      • 1.2.3链表实现 栈和队列 示例:
    • 1.3 应用
  • 三、树和二叉树
    • 1.1 定义
      • 1.1.1 树的定义
      • 1.1.2 树的特点
      • 1.1.3 二叉树的定义
      • 1.1.4 二叉树的特点
    • 1.2 实现
      • 1.2.1 实现方法
      • 1.2.2 常用算法
    • 1.3 应用
  • 四、 图结构
    • 1.1 定义
      • 1.1.1 图的定义
      • 1.1.2 图的类型
    • 1.2 实现
      • 1.2.1 实现方法
      • 1.2.2 常用算法
    • 1.3 应用
  • 五、小结回顾

一、链表

1.1 定义

链表是一种常见的数据结构由一系列节点按顺序连接而成,一般每个节点包含一个数据元素和一个指向下一个节点的引用。
链表有多种类型:

  • 单向链表:每个节点只有一个指向下一个节点的引用
  • 双向链表:每个节点有一个指向前一个节点和一个指向后一个节点的引用
  • 循环链表:尾节点指向头节点形成循环
  • 双向循环链表:既是双向链表又是循环链表

1.2 实现

1.2.1 单向链表

单向链表是指每个节点包含一个指向下一个节点的指针,最后一个节点的指针为NULL。
以下是单向链表的基本结构:

struct ListNode 
{int val;ListNode *next;ListNode(int x) : val(x), next(NULL) {}
};

1.2.2 双向链表

双向链表是在单向链表的基础上,每个节点还包含一个指向上一个节点的指针。
以下是双向链表的基本结构:

struct ListNode 
{int val;ListNode *prev;ListNode *next;ListNode(int x) : val(x), prev(NULL), next(NULL) {}
};

1.2.3 常见操作(反转 合并 查找)

/* 链表反转 */
Node* reverseLinkedList(Node* head) {Node* newHead = nullptr;while(head != nullptr) {Node* tmp = head->next;head->next = newHead;newHead = head;head = tmp;}return newHead;
}/* 链表合并 */
Node* mergeLinkedList(Node* head1, Node* head2) {Node* dummy = new Node;Node* cur = dummy;while(head1 != nullptr && head2 != nullptr) {if(head1->data < head2->data) {cur->next = head1;head1 = head1->next;} else {cur->next = head2;head2 = head2->next;}cur = cur->next;}cur->next = (head1 != nullptr ? head1 : head2);return dummy->next;
}/* 链表查找 */
Node* findNode(Node* head, int data) {Node* cur = head;while(cur != nullptr && cur->data != data) {cur = cur->next;}return cur;
}

1.3 应用

链表在程序中有着多种应用 例如:

  • 实现LRU缓存淘汰算法
  • 实现文件系统中的文件块分配
  • 链表作为数据流的缓冲区

使用双向循环链表实现的LRU缓存示例:

class LRUCache 
{
private:struct CacheNode {int key;int value;CacheNode(int k, int v) : key(k), value(v) {}};int capacity;list<CacheNode> cacheList;unordered_map<int, list<CacheNode>::iterator> cacheMap;public:LRUCache(int capacity) {this->capacity = capacity;}int get(int key) {if (cacheMap.find(key) == cacheMap.end()) {return -1;}// 把当前节点移到链表头部cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);return cacheMap[key]->value;}void put(int key, int value) {if (cacheMap.find(key) == cacheMap.end()) {if (cacheList.size() == capacity) {// 删除链表最后一个节点int key = cacheList.back().key;cacheMap.erase(key);cacheList.pop_back();}cacheList.push_front(CacheNode(key, value));cacheMap[key] = cacheList.begin();} else {// 修改节点的值,并移到链表头部cacheMap[key]->value = value;cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]);}}
};

使用LRU缓存:

LRUCache cache = LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << endl; // 输出 1
cache.put(3, 3);
cout << cache.get(2) << endl; // 输出 -1,因为键 2 已经被删除了
cache.put(4, 4);
cout << cache.get(1) << endl; // 输出 -1,因为键 1 已经被删除了
cout << cache.get(3) << endl; // 输出 3
cout << cache.get(4) << endl; // 输出 4

二、 栈-Stack 队列-Queue

1.1 定义

  • 栈(Stack):后进先出(LIFO: Last In First Out)
  • 队列(Queue):先进先出(FIFO: First In First Out)

栈和队列是C++提供的两种基本数据类型,定义如下:

// 栈的定义
template <class T> class Stack {
public:Stack();                // 构造函数void push(const T&);    // 将元素加入栈顶,返回 voidvoid pop();             // 移除栈顶的元素,返回 voidT& top();               // 返回栈顶元素的引用,不移除该元素const T& top() const;   // 返回常量栈顶元素的引用,不移除该元素bool empty() const;     // 判断栈是否为空,返回 boolint size() const;       // 返回栈中元素的个数
};
// 队列的定义
template<class T> class Queue {
public:Queue();                // 构造函数void push(const T&);    // 将元素加入队尾,返回 voidvoid pop();             // 移除队头的元素,返回 voidT& front();             // 返回队头元素的引用,不移除该元素const T& front() const; // 返回常量队头元素的引用,不移除该元素bool empty() const;     // 判断队是否为空,返回 boolint size() const;       // 返回队中元素的个数
};

1.2 实现

栈和队列的实现方法比较简单可以使用数组或链表来实现

1.2.1 数组实现 栈和队列 示例:

// 数组实现的栈
template <class T> class Stack {T* a;int n;              // 栈的大小int t;              // 栈顶元素的下标
public:Stack(int maxn) {a = new T[maxn];n = maxn;t = -1;}bool empty() const {return t == -1;}void push(const T& x) {a[++t] = x;}void pop() {--t;}T top() const {return a[t];}
};// 数组实现的队列
template<class T> class Queue {T* a;               int n;              // 队列的大小int head, tail;     // 队头和队尾的下标
public:Queue(int maxn) {a = new T[maxn];n = maxn;head = 0;tail = -1;}bool empty() const {return head > tail;}void push(const T& x) {a[++tail] = x;}void pop() {++head;}T front() const {return a[head];}
};

1.2.3链表实现 栈和队列 示例:

// 链表实现的栈
template <class T> class Stack {// 定义链表节点struct node {T data;node* next;node(const T& d, node* n = nullptr): data(d), next(n) {}};node* t;            // 栈顶节点
public:Stack() : t(nullptr) {}    // 默认构造函数bool empty() const {return t == nullptr;}void push(const T& x) {t = new node(x, t);}void pop() {node* tmp = t;t = t->next;delete tmp;}T& top() const {return t->data;}
};// 链表实现的队列
template <class T> class Queue {// 定义链表节点struct node {T data;node* next;node(const T& d, node* n = nullptr) : data(d), next(n) {}};node* head;         // 队头节点node* tail;         // 队尾节点
public:Queue() : head(nullptr), tail(nullptr) {}    // 默认构造函数bool empty() const {return head == nullptr;}void push(const T& x) {if (tail != nullptr) {tail->next = new node(x);tail = tail->next;} else {head = tail = new node(x);}}void pop() {node* tmp = head;head = head->next;delete tmp;if (head == nullptr) {tail = nullptr;}}T& front() const {return head->data;}
};

1.3 应用

栈和队列是常用的数据结构在程序中的应用广泛,例如:

  • 表达式求值
  • 函数调用
  • 网络请求处理
  • 路径搜索

如下使用栈来实现表达式求值示例:

// 使用栈来实现表达式求值
double calc(const string& s) {Stack<double> stk;for (int i = 0; i < s.size(); ++i) {if (isdigit(s[i])) {int j = i;while (j < s.size() && (isdigit(s[j]) || s[j] == '.')) ++j;stk.push(stod(s.substr(i, j - i)));i = j - 1;} else if (s[i] == '+') {double b = stk.top(); stk.pop();double a = stk.top(); stk.pop();stk.push(a + b);} else if (s[i] == '-') {double b = stk.top(); stk.pop();double a = stk.top(); stk.pop();stk.push(a - b);} else if (s[i] == '*') {double b = stk.top(); stk.pop();double a = stk.top(); stk.pop();stk.push(a * b);} else if (s[i] == '/') {double b = stk.top(); stk.pop();double a = stk.top(); stk.pop();stk.push(a / b);}}return stk.top();
}

三、树和二叉树

1.1 定义

1.1.1 树的定义

树是一种非线性的数据结构,它由n个节点组成的有限集合,其中每个节点是一个具有唯一标识的对象,且节点之间存在着一些特定的关系。通常这些关系可以用父子关系来描述。每个节点可以有多个子节点,但是一个节点只有一个父节点,树中有且仅有一个无父节点的节点这个节点被称为根节点。

1.1.2 树的特点

  1. 树中的每个节点都有唯一的标识
  2. 树中每个节点至少有一个子节点
  3. 树中节点之间的关系是“父子”关系
  4. 树中有且仅有一个无父节点的节点这个节点被称为根节点
  5. 树中任意两个节点之间都存在一条唯一的路径

1.1.3 二叉树的定义

二叉树是一种特殊的树结构,每个节点最多有两个子节点,它的左子节点和右子节点分别称为左子树和右子树

1.1.4 二叉树的特点

  1. 二叉树中的每个节点都有唯一的标识
  2. 一个节点最多只有两个子节点
  3. 二叉树中节点之间的关系是“父子”关系
  4. 如果二叉树中任意一个节点(除了叶子节点),其左子树不为空则左子树中的所有节点的值都小于这个节点的值.如果其右子树不为空则右子树中所有节点的值都大于这个节点的值。

1.2 实现

1.2.1 实现方法

树和二叉树可以通过结构体和指针实现,使用链式结构来描述。节点包括一个数据域和两个指针域分别指向左右子树。

// 定义树节点结构体
struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

1.2.2 常用算法

  1. 先序遍历:遍历顺序为根结点、左子树、右子树。
  2. 中序遍历:遍历顺序为左子树、根结点、右子树。
  3. 后序遍历:遍历顺序为左子树、右子树、根结点。
  4. 层序遍历:按层遍历二叉树。

先序遍历的代码示例:

void preorder_traversal(TreeNode *node) {if (node) {cout << node->val << " ";preorder_traversal(node->left);preorder_traversal(node->right);}
}

中序遍历的代码示例:

void inorder_traversal(TreeNode *node) {if (node) {inorder_traversal(node->left);cout << node->val << " ";inorder_traversal(node->right);}
}

后序遍历的代码示例:

void postorder_traversal(TreeNode *node) {if (node) {postorder_traversal(node->left);postorder_traversal(node->right);cout << node->val << " ";}
}

层序遍历的代码示例:

void level_order_traversal(TreeNode *node) {queue<TreeNode *> q;q.push(node);while (!q.empty()) {TreeNode *temp = q.front();q.pop();cout << temp->val << " ";if (temp->left) {q.push(temp->left);}if (temp->right) {q.push(temp->right);}}
}

1.3 应用

在程序中的应用有:

  1. 堆排序算法
  2. 哈夫曼编码
  3. 字典树
  4. AVL树(自平衡二叉搜索树)
  5. B树和B+树(多路搜索树)

四、 图结构

1.1 定义

1.1.1 图的定义

图是一种数据结构,它由节点/顶点 (vertex)和边(edge)组成。
节点表示图中的元素而边描述它们之间的关系
在图中元素间的关系不再是父子关系而是一个相关的关系

// 定义图结构体
struct Graph {int V; // 节点个数vector<int> *adj; // 指向相邻顶点列表的指针Graph(int V) {this->V = V;adj = new vector<int>[V];}void addEdge(int v, int w) { // 添加一条边adj[v].push_back(w); // 无向图,边为双向,v->w 和 w->v 都需加入 adj 中 adj[w].push_back(v);}
};

1.1.2 图的类型

在图论中重点讨论无向图和有向图,以及它们的变种:

  1. 无向图:由节点和无向边组成的图结构,边没有方向,节点间的关系是双向的
  2. 有向图:由节点和有向边组成的图结构,边有方向,节点间的关系是单向的
  3. 带权图:除了节点和边之外还有一些权重信息
  4. 多重图:允许多个节点之间有多条边
// 定义带权图结构体
struct WeightedGraph {int V; // 节点个数vector<pair<int, int>> *adj; // pair中的第一个值表示相邻顶点,第二个值表示权重WeightedGraph(int V) {this->V = V;adj = new vector<pair<int, int>>[V];}void addEdge(int v, int w, int weight) { // 添加一条带权边adj[v].push_back(make_pair(w, weight));adj[w].push_back(make_pair(v, weight)); // 无向图,边为双向,v->w 和 w->v 都需加入 adj 中 }
};

1.2 实现

1.2.1 实现方法

  1. 邻接矩阵:使用一个二维数组表示节点与边之间的关系可以表示带权图
  2. 邻接表:使用一个数组+链表的方式表示节点与边之间的关系可以表示带权图
// 用邻接表实现的广度优先遍历算法
void bfs(Graph graph, int start) {bool *visited = new bool[graph.V]; // 标记节点是否被访问for (int i = 0; i < graph.V; ++i) {visited[i] = false;}queue<int> q;visited[start] = true; // 标记起始节点被访问q.push(start);while (!q.empty()) {int curr = q.front();cout << curr << " "; // 输出访问顺序q.pop();for (auto i = graph.adj[curr].begin(); i != graph.adj[curr].end(); ++i) {if (!visited[*i]) { // 如果该节点未被访问visited[*i] = true; // 标记该节点被访问q.push(*i); // 将该节点加入队列中,等待遍历}}}
}

1.2.2 常用算法

  1. 深度优先遍历(DFS):以深度为优先级,沿着路径遍历,可以通过递归或栈实现
  2. 广度优先遍历(BFS):以广度为优先级,按照“就近原则”,先遍历与起点直接相邻的节点,再遍历距离起点稍远的节点,可以使用队列实现
  3. 最短路径算法:解决两个节点之间的最短路径问题
  4. 最小生成树算法:解决连通图中,权值最小的生成树问题
// 用邻接矩阵实现的深度优先遍历算法
void dfs(int v, bool visited[], int **graph, int size) {visited[v] = true; // 标记该节点被访问cout << v << " "; // 输出访问顺序for (int i = 0; i < size; ++i) {if (graph[v][i] && !visited[i]) { // 如果该节点未被访问且与 v 有连接dfs(i, visited, graph, size); // 递归遍历该节点}}
}

1.3 应用

图作为一种重要的数据结构在程序中的应用有:

  1. 最短路径问题:GPS 导航
  2. 网络问题:路由算法
  3. 社交网络:朋友推荐
  4. 游戏策略:国际象棋
  5. 电路设计:电路布线

五、小结回顾

链表是一种常用的线性数据结构,它可以动态的添加或删除元素并且没有固定大小
链表是由节点组成的每个节点包含一个数据元素和一个指向下一个节点的指针

栈是一种后进先出的数据结构所有元素都在栈顶,可以理解为一种特殊的列表。
栈的基本操作包括入栈和出栈有时会使用压入和弹出这样的类比来描述这些操作。

队列是一种先进先出的数据结构所有元素被放置在队列的末尾并从队列的开头移除。
队列是一种有限制的列表它仅允许在列表的前面进行插入而仅允许在列表的后面进行删除

树是一种非线性数据结构由由节点组成,并且节点之间有特定的关系(如父节点、子节点等)
每个节点可以包含一个数据元素,还可以连接到其他节点。树被广泛应用于各种算法和数据结构中

图是一种非线性的数据结构由一个或多个节点组成并且节点之间有关联
图是由边和节点组成的每个边连接两个节点
图在计算机科学和算法中被广泛应用,例如网络路由、图像处理、算法设计等。


http://www.ppmy.cn/news/63639.html

相关文章

【Linux】Linux安装Redis(图文解说详细版)

文章目录 前言第一步&#xff0c;下载安装包第二步&#xff0c;上传安装包到/opt下&#xff08;老规矩了&#xff0c;安装包在opt下&#xff09;第三步&#xff0c;解压安装包第四步&#xff0c;编译第五步&#xff0c;安装第六步&#xff0c;配置redis第七步&#xff0c;设置开…

【华为OD机试python】静态扫描【2023 Q1 A卷|100分】

华为OD机试- 题目列表 2023Q1 点这里!! 2023华为OD机试-刷题指南 点这里!! 题目描述 静态扫描可以快速识别源代码的缺陷,静态扫描的结果以扫描报告作为输出: 1、文件扫描的成本和文件大小相关,如果文件大小为N,则扫描成本为N个金币 2、扫描报告的缓存成本和文件大小无…

Android编译优化之混淆配置

Android编译优化之混淆配置 背景 为了使用java8及后续java新版本的特性&#xff0c;Google增加了一步编译过程—脱糖&#xff08;desugaring&#xff09;&#xff0c;但这一步会导致更长的编译时间&#xff0c;这也是为什么Google会推出D8和R8编译器来优化编译速度。 什么是脱…

Docker Compose-简单安装与卸载教程

文章目录 Windows安装Linux安装PIP 安装Mac安装Linux下的卸载 Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose&#xff0c;您可以使用 YML 文件来配置应用程序需要的所有服务。然后&#xff0c;使用一个命令&#xff0c;就可以从 YML 文件配置中创建并启…

python对象方法 反射

前言 类实例化得到的对象可以直接调用类中定义的函数&#xff0c;并且可以将对象本身作为第一个参数&#xff0c;那么类能不能也能像对象一样使用类体中的函数而不需要传递第一个参数呢&#xff1f;如果我们使用别人封装好的类&#xff0c;如何判断这个类或者对象是否有某个属…

5.10晚间黄金CPI精准分析及多空交易策略

近期有哪些消息面影响黄金走势&#xff1f;本周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周三&#xff08;5月10日&#xff09;亚欧盘中&#xff0c;现货黄金震荡下跌&#xff0c;现报2030美元/盎司&#xff0c;稍早一度触及2038美元/盎司高点。美联储理事…

ChatGPT作者John Schulman:通往TruthGPT之路

OneFlow编译 翻译&#xff5c;贾川、徐佳渝、杨婷 大型语言模型&#xff08;LLM&#xff09;有一个众所周知的“硬伤”——它们经常会一本正经编造貌似真实的内容。 OpenAI团队希望通过改进强化学习反馈步骤“原生地”阻止神经网络产生幻觉&#xff0c;OpenAI首席科学家Ilya …

Git Clone 报错 `SSL certificate problem: unable to get local issuer certificate`

如果您在尝试克隆Git存储库时得到 “SSL certificate problem: unable to get local issuer certificate” 的错误,这意味着Git无法验证远程存储库的SSL证书。如果SSL证书是自签名的&#xff0c;或者SSL证书链有问题&#xff0c;就会发生这种情况。 $ git clone https://githu…