图海寻径——图相关算法的奇幻探索之旅

一、图的表示

1. 邻接矩阵 (Adjacency Matrix)

#include <iostream>
#include <vector>
#include <queue>
#include <limits>using namespace std;class GraphMatrix {
private:int numVertices;vector<vector<int>> adjMatrix;const static int INFINITY = numeric_limits<int>::max();public:GraphMatrix(int vertices) : numVertices(vertices), adjMatrix(vertices, vector<int>(vertices, INFINITY)) {}void addEdge(int u, int v, int weight) {if (u >= 0 && u < numVertices && v >= 0 && v < numVertices) {adjMatrix[u][v] = weight;adjMatrix[v][u] = weight; // 如果是无向图,则需要双向设置}}void BFS(int startVertex);void DFSUtil(int vertex, vector<bool>& visited);void DFS();
};void GraphMatrix::BFS(int startVertex) {vector<bool> visited(numVertices, false);queue<int> q;visited[startVertex] = true;q.push(startVertex);while (!q.empty()) {int current = q.front();cout << "Visited " << current << endl;q.pop();for (int i = 0; i < numVertices; ++i) {if (adjMatrix[current][i] != INFINITY && !visited[i]) {visited[i] = true;q.push(i);}}}
}void GraphMatrix::DFSUtil(int vertex, vector<bool>& visited) {visited[vertex] = true;cout << "Visited " << vertex << endl;for (int i = 0; i < numVertices; ++i) {if (adjMatrix[vertex][i] != INFINITY && !visited[i])DFSUtil(i, visited);}
}void GraphMatrix::DFS() {vector<bool> visited(numVertices, false);for (int i = 0; i < numVertices; ++i)if (!visited[i])DFSUtil(i, visited);
}

2. 邻接表 (Adjacency List)

#include <iostream>
#include <vector>
#include <list>
#include <queue>using namespace std;class GraphList {
private:int numVertices;vector<list<pair<int, int>>> adjLists;public:GraphList(int vertices) : numVertices(vertices), adjLists(vertices) {}void addEdge(int u, int v, int weight) {adjLists[u].push_back(make_pair(v, weight));adjLists[v].push_back(make_pair(u, weight)); // 如果是无向图,则需要双向添加}void BFS(int startVertex);void DFSUtil(int vertex, vector<bool>& visited);void DFS();
};void GraphList::BFS(int startVertex) {vector<bool> visited(numVertices, false);queue<int> q;visited[startVertex] = true;q.push(startVertex);while (!q.empty()) {int current = q.front();cout << "Visited " << current << endl;q.pop();for (auto& edge : adjLists[current]) {int neighbor = edge.first;if (!visited[neighbor]) {visited[neighbor] = true;q.push(neighbor);}}}
}void GraphList::DFSUtil(int vertex, vector<bool>& visited) {visited[vertex] = true;cout << "Visited " << vertex << endl;for (auto& edge : adjLists[vertex]) {int neighbor = edge.first;if (!visited[neighbor])DFSUtil(neighbor, visited);}
}void GraphList::DFS() {vector<bool> visited(numVertices, false);for (int i = 0; i < numVertices; ++i)if (!visited[i])DFSUtil(i, visited);
}

二、图的遍历算法

(一)深度优先搜索(DFS)

  1. 核心原理
    • 以深度优先为策略,从起始顶点出发,沿着一条路径持续深入探索,直至无法继续或达成特定条件(如找到目标顶点)。随后回溯到前一步,继续探寻其他未访问路径,直至遍历完起始顶点所在连通分量的所有顶点。若图中存在未访问顶点,则选取其一作为新起始点,重复上述流程,直至整个图的所有顶点均被访问。
  2. 实现方式
    • 递归实现:在递归函数中,首先标记当前顶点已访问,接着遍历其邻接顶点,对未访问的邻接顶点递归调用 DFS 函数。以下是使用邻接表存储图的递归 DFS 代码:
class GraphDFS {
private:vector<bool> visited;void DFSUtil(Graph& graph, int v) {visited[v] = true;cout << v << " ";for (const Edge& edge : graph.adjList[v]) {int neighbor = edge.to;if (!visited[neighbor]) {DFSUtil(graph, neighbor);}}}public:void DFS(Graph& graph) {visited.resize(graph.numVertices, false);for (int i = 0; i < graph.numVertices; ++i) {if (!visited[i]) {DFSUtil(graph, i);}}}
};
  1. 应用场景
    • 图的连通性检测:执行一次 DFS 遍历,若所有顶点均能被访问到,则图是连通的;反之则不连通。
    • 连通分量求解:多次调用 DFS,每次从一个未访问顶点启动,可获取图的各个连通分量。
    • 迷宫探索:将迷宫建模为图,迷宫中的格子作为顶点,相邻格子间的通道作为边,DFS 可用于找寻从迷宫入口到出口的路径。

(二)广度优先搜索(BFS)

  1. 核心原理
    • 从给定起始顶点开始,先访问该顶点,接着依次访问其所有未访问的邻接顶点,然后再访问这些邻接顶点的未访问邻接顶点,依此类推,按照层次顺序逐层向外拓展,直至遍历完图中的所有顶点。
  2. 实现方式
    • 借助队列实现。首先将起始顶点入队并标记为已访问,然后循环取出队首顶点,访问其未访问邻接顶点并将这些邻接顶点入队,直至队列为空。以下是使用邻接表存储图的 BFS 示例代码:
class GraphBFS {
public:void BFS(Graph& graph, int start) {vector<bool> visited(graph.numVertices, false);queue<int> q;visited[start] = true;q.push(start);while (!q.empty()) {int current = q.front();q.pop();cout << current << " ";for (const Edge& edge : graph.adjList[current]) {int neighbor = edge.to;if (!visited[neighbor]) {visited[neighbor] = true;q.push(neighbor);}}}}
};
  1. 应用场景
    • 无权图最短路径计算:在 BFS 遍历过程中,从起始顶点到其他顶点的路径长度即为它们在队列中被访问的层数,可用于求解无权图中两点间的最短路径长度及路径。
    • 网络爬虫页面遍历:网络爬虫从起始页面出发,按照广度优先顺序遍历链接页面,能够优先抓取与起始页面距离较近的页面,确保信息获取的全面性和层次性。

三、最小生成树算法

(一)普里姆算法(Prim)

  1. 核心原理
    • 从图中任选一个顶点作为起始点,构建初始的最小生成树集合。随后不断从剩余顶点中挑选与当前最小生成树集合相连且权值最小的边所对应的顶点,将其纳入最小生成树集合,持续该过程直至所有顶点均被加入。
  2. 实现细节
    • 维护两个顶点集合,已在最小生成树中的顶点集合U和未在其中的顶点集合V - U。使用数组key记录每个顶点到当前最小生成树的最小权值边的权值,初始除起始顶点外均设为无穷大,同时用数组parent记录每个顶点在最小生成树中的父顶点。以下是使用邻接表存储图的 Prim 算法代码:
class PrimMST {
public:int prim(Graph& graph, int start) {vector<bool> inMST(graph.numVertices, false);vector<int> key(graph.numVertices, INT_MAX);vector<int> parent(graph.numVertices, -1);key[start] = 0;for (int count = 0; count < graph.numVertices - 1; ++count) {int minKey = INT_MAX, minIndex = -1;for (int v = 0; v < graph.numVertices; ++v) {if (!inMST[v] && key[v] < minKey) {minKey = key[v];minIndex = v;}}inMST[minIndex] = true;for (const Edge& edge : graph.adjList[minIndex]) {int neighbor = edge.to;int weight = 1;  // 如果是带权图,这里改成对应边的权值if (!inMST[neighbor] && weight < key[neighbor]) {key[neighbor] = weight;parent[neighbor] = minIndex;}}}int sumWeight = 0;for (int i = 1; i < graph.numVertices; ++i) {sumWeight += key[i];  // 累加最小生成树边的权值(这里简单示例,实际带权图边权计算更复杂)}return sumWeight;}
};
  1. 时间复杂度分析
    • 若采用邻接矩阵存储图,时间复杂度为(O(n^2)),其中(n)为顶点数。若运用堆优化,时间复杂度可降至(O((n + m)log n)),其中(m)为边数。

(二)克鲁斯卡尔算法(Kruskal)

  1. 核心原理
    • 首先将图中所有边按照权值从小到大排序,然后依次考察每条边。若选取某条边不会与已选边构成回路,则将其纳入最小生成树,直至选取了(n - 1)条边((n)为顶点数)。
  2. 实现细节
    • 需要借助并查集数据结构判断边加入后是否形成回路。并查集用于维护顶点的连通性信息,初始每个顶点各自属于独立集合,加入边时判断边的两个顶点是否在同一集合,若不在则合并两个集合。
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 边的数据结构
struct Edge {int src, dest, weight;
};// 比较函数,用于对边按照权重排序
bool compare(const Edge& a, const Edge& b) {return a.weight < b.weight;
}// 并查集 (Union-Find) 的类
class UnionFind {
private:vector<int> parent, rank;public:UnionFind(int size) : parent(size), rank(size, 0) {for (int i = 0; i < size; ++i)parent[i] = i;}// 查找操作,带有路径压缩int find(int x) {if (parent[x] != x)parent[x] = find(parent[x]); // 路径压缩return parent[x];}// 合并操作,带有按秩合并void unionSets(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY])parent[rootY] = rootX;else if (rank[rootX] < rank[rootY])parent[rootX] = rootY;else {parent[rootY] = rootX;rank[rootX]++;}}}
};// Kruskal 算法实现
void kruskalMST(vector<Edge>& edges, int V) {sort(edges.begin(), edges.end(), compare); // 按权重升序排序边UnionFind uf(V);vector<Edge> mst;for (auto& edge : edges) {int rootSrc = uf.find(edge.src);int rootDest = uf.find(edge.dest);if (rootSrc != rootDest) { // 如果不形成环,则加入最小生成树mst.push_back(edge);uf.unionSets(rootSrc, rootDest);}}cout << "Edges in the MST:" << endl;for (auto& e : mst)cout << e.src << " -- " << e.dest << " == " << e.weight << endl;
}int main() {int V = 4; // 顶点数vector<Edge> edges = {{0, 1, 10},{0, 2, 6},{0, 3, 5},{1, 3, 15},{2, 3, 4}};kruskalMST(edges, V);return 0;
}
  1. 时间复杂度分析
    • 时间复杂度主要取决于边的排序操作,通常为(O(mlog m)),其中(m)为边数。在稀疏图中,由于边数相对较少,克鲁斯卡尔算法往往比普里姆算法更具效率。

四、最短路径算法

(一)迪杰斯特拉算法(Dijkstra)

  1. 核心原理
    • 适用于带权有向图(权值非负)。从源点出发,逐步确定到其他顶点的最短路径。维护一个已确定最短路径的顶点集合S,对于不在S中的顶点v,记录从源点到v的当前最短路径长度d[v]。每次从不在S中的顶点里选取d[v]最小的顶点u加入S,并更新与u相邻顶点的d[v]值。
  2. 实现细节
    • 初始化时,源点的d值设为(0),其余顶点设为无穷大。然后持续重复选取最小d值顶点并更新相邻顶点d值的操作,直至所有顶点均被加入S。以下是使用邻接表存储图的 Dijkstra 算法代码:
class DijkstraSP {
public:vector<int> dijkstra(Graph& graph, int source) {vector<int> dist(graph.numVertices, INT_MAX);vector<bool> visited(graph.numVertices, false);dist[source] = 0;for (int count = 0; count < graph.numVertices - 1; ++count) {int minDist = INT_MAX, minIndex = -1;for (int v = 0; v < graph.numVertices; ++v) {if (!visited[v] && dist[v] < minDist) {minDist = dist[v];minIndex = v;}}visited[minIndex] = true;for (const Edge& edge : graph.adjList[minIndex]) {int neighbor = edge.to;int weight = 1;  // 如果是带权图,这里改成对应边的权值if (!visited[neighbor] && dist[minIndex] + weight < dist[neighbor]) {dist[neighbor] = dist[minIndex] + weight;}}}return dist;}
};
  1. 时间复杂度分析
    • 若采用邻接矩阵存储图,时间复杂度为(O(n^2)),其中(n)为顶点数。若使用堆优化,时间复杂度可降为(O((n + m)log n)),其中m为边数。

(二)弗洛伊德算法(Floyd)

  1. 核心原理
    • 可计算出图中任意两个顶点之间的最短路径。基于动态规划思想,对于任意两个顶点(i)和(j),考虑是否经过中间顶点(k)来更新(i)到(j)的最短路径长度。
  2. 实现细节
    • 使用二维数组dist存储顶点之间的最短路径长度。初始时,dist[i][j]为图中(i)到(j)的直接边权值(若有边),否则为无穷大。然后通过三层循环,依次以每个顶点k作为中间顶点更新dist[i][j]的值。以下是使用邻接矩阵存储图的 Floyd 算法示例代码:
class FloydSP {
public:vector<vector<int>> floyd(GraphMatrix& graph) {int n = graph.numVertices;vector<vector<int>> dist = graph.adjMatrix;for (int k = 0; k < n; ++k) {for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {if (dist[i][k]!= INT_MAX && dist[k][j]!= INT_MAX &&dist[i][k] + dist[k][j] < dist[i][j]) {dist[i][j] = dist[i][k] + dist[k][j];}}}}return dist;}
};
  1. 时间复杂度分析
    • 时间复杂度为(O(n^3)),其中(n)为顶点数。由于时间复杂度较高,适用于顶点数较少的图或者对所有顶点对最短路径都有需求的场景。

五、拓扑排序算法

  1. 核心原理
    • 针对有向无环图(DAG),拓扑排序旨在将图中所有顶点排成一个线性序列,使得对于图中的任意一条有向边((u, v)),在序列中(u)均排在(v)之前。
  2. 实现方式(基于入度表)
    • 首先统计每个顶点的入度,将入度为(0)的顶点入队。然后循环取出队首顶点,将其输出到拓扑序列中,并将其所有邻接点的入度减(1),若某个邻接点入度变为(0),则将其入队,直至队列为空。若最终输出的顶点数小于图中顶点总数,则说明图中有环,不存在拓扑排序。以下是拓扑排序的代码:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;// 边结构体,用于邻接表存储边的信息
struct Edge {int to;  // 边指向的顶点Edge(int t) : to(t) {}
};// 图结构体,使用邻接表存储
class Graph {
public:int numVertices;  // 顶点数量vector<vector<Edge>> adjList;  // 邻接表Graph(int n) : numVertices(n), adjList(n) {}// 添加边(有向图示例)void addEdge(int from, int to) {adjList[from].push_back(Edge(to));}
};class TopologicalSort {
public:vector<int> topologicalSort(Graph& graph) {int n = graph.numVertices;// 用于记录每个顶点的入度vector<int> inDegree(n, 0);// 计算每个顶点的入度for (int i = 0; i < n; i++) {for (const Edge& edge : graph.adjList[i]) {inDegree[edge.to]++;}}queue<int> zeroDegreeQueue;// 将入度为0的顶点入队for (int i = 0; i < n; i++) {if (inDegree[i] == 0) {zeroDegreeQueue.push(i);}}vector<int> topologicalOrder;// 进行拓扑排序while (!zeroDegreeQueue.empty()) {int current = zeroDegreeQueue.front();zeroDegreeQueue.pop();topologicalOrder.push_back(current);// 更新相邻顶点的入度,并将入度变为0的顶点入队for (const Edge& edge : graph.adjList[current]) {inDegree[edge.to]--;if (inDegree[edge.to] == 0) {zeroDegreeQueue.push(edge.to);}}}// 若排序后的顶点数小于图中顶点总数,则说明图中有环,不存在拓扑排序if (topologicalOrder.size() < n) {cout << "图中存在环,无法进行拓扑排序" << endl;return {};}return topologicalOrder;}
};int main() {Graph graph(6);graph.addEdge(5, 2);graph.addEdge(5, 0);graph.addEdge(4, 0);graph.addEdge(4, 1);graph.addEdge(2, 3);graph.addEdge(3, 1);TopologicalSort ts;vector<int> result = ts.topologicalSort(graph);if (!result.empty()) {cout << "拓扑排序结果为: ";for (int vertex : result) {cout << vertex << " ";}cout << endl;}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从差分电容到多轴测量:解读 BendLabs 柔性弯曲传感器核心技术

BendLabs是一家技术公司&#xff0c;致力于通过灵活的软传感解决方案将运动测量和理解带给世界。BendLabs柔性弯曲传感器由医用级有机硅制成&#xff0c;能够满足精确、多轴、柔软、灵活的传感需求。BendLabs柔性弯曲传感器采用差分电容原理&#xff0c;具有高精度、低功耗、无…

【数字电路与逻辑设计】实验二 数值比较器

文章总览&#xff1a;YuanDaiMa2048博客文章总览 【数字电路与逻辑设计】实验二 数值比较器 一、实验内容二、设计过程&#xff08;一&#xff09;真值表&#xff08;二&#xff09;设计思路 三、源代码&#xff08;一&#xff09;代码说明&#xff1a;&#xff08;二&#xff…

39 vector深入理解 · 迭代器失效深度浅拷贝

目录 一、迭代器失效 &#xff08;一&#xff09;外部迭代器失效 1、扩容引起的野指针问题 2、删除引起的逻辑问题 二、深度浅拷贝 一、迭代器失效 迭代器可以理解为像指针一样的类对象&#xff0c;但不要一味地认为迭代器就是指针&#xff0c;指针可以实现迭代器&#xff…

2024年认证杯SPSSPRO杯数学建模C题(第一阶段)云中的海盐解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 C题 云中的海盐 原题再现&#xff1a; 巴黎气候协定提出的目标是&#xff1a;在2100年前&#xff0c;把全球平均气温相对于工业革命以前的气温升幅控制在不超过2摄氏度的水平&#xff0c;并为1.5摄氏度而努力。但事实上&#xff0c;许多之前的…

AI智能体Prompt预设词指令大全+GPTs应用使用

AI智能体使用指南 直接复制在AI工具助手中使用&#xff08;提问前&#xff09; 可前往SparkAi系统用户官网进行直接使用 SparkAI系统介绍文档&#xff1a;Docs 常见AI智能体GPTs应用大全在线使用 自定义添加制作AI智能体进行使用&#xff1a; 文章润色器 你是一位具有敏锐洞察…

Origin快速拟合荧光寿命、PL Decay (TRPL)数据分析处理-方法二

1.先导入数据到origin 2.导入文件的时候注意&#xff1a;名字短的这个是&#xff0c;或者你打开后看哪个里面有800&#xff0c;因为我的激光重频是1.25Hz&#xff08;应该是&#xff0c;不太确定单位是KHz还是MHz&#xff09;&#xff0c;所以对应的时间是800s。 3.选中两列直接…

Mybatis框架进阶(标签)

1. <if>标签 DROP DATABASE IF EXISTS mybatis_test; CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4; use mybatis_test;DROP TABLE IF EXISTS user_info; CREATE TABLE user_info (id INT ( 11 ) NOT NULL AUTO_INCREMENT,username VARCHAR ( 127 ) NOT…

【知识点】图与图论入门

何为图论 见名知意&#xff0c;图论 (Graph Theory) 就是研究 图 (Graph) 的数学理论和方法。图是一种抽象的数据结构&#xff0c;由 节点 (Node) 和 连接这些节点的 边 (Edge) 组成。图论在计算机科学、网络分析、物流、社会网络分析等领域有广泛的应用。 如下&#xff0c;这…

泷羽sec-burp(4)burp常见用法 以及 漏洞测试理论 学习笔记

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

Linux上传代码的步骤与注意事项

最近因为工作需要&#xff0c;要上传代码到 DPDK 上&#xff0c;代码已经上传成功&#xff0c;记录一下过程&#xff0c;给大家提供一个参考。我这次需要上传的是pmd&#xff0c;即poll mode driver。 1 Coding Style 要上传代码&#xff0c;第一件事就是需要知道Coding Styl…

vllm0.5.0的v1/completions各参数说明

一、调用示例 curl -X POST \http://ip:8001/v1/completions \-H accept: application/json \-H Content-Type: application/json \-d {"model": "qwen-api","prompt": ["讲个中文笑话"],"best_of": 1,"n": 1,&qu…

Java项目实战II基于微信小程序的作品集展示(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着移动互联网技术的飞速…

物联网入门-Arduino的下载与配置教程(以ESP32为例)-2024

教程介绍 本次教程主要讲述如何下载与配置Arduino&#xff0c;以及开发版对应驱动的下载安装 原文链接&#xff1a;物联网入门-Arduino的下载与配置教程(以ESP32为例)-2024 步骤概述 1&#xff1a;下载Arduino 2&#xff1a;安装Arduino 3&#xff1a;下载安装驱动 4&am…

13.在 Vue 3 中使用OpenLayers加载鹰眼控件示例教程

在 WebGIS 开发中&#xff0c;鹰眼控件 是一个常用的功能&#xff0c;它可以为用户提供当前地图位置的概览&#xff0c;帮助更好地定位和导航。在本文中&#xff0c;我们将基于 Vue 3 的 Composition API 和 OpenLayers&#xff0c;创建一个简单的鹰眼控件示例。 效果预览 在最…

Flink如何基于数据版本使用最新离线数据

业务场景 假设批量有一张商户表&#xff0c;表字段中有商户名称和商户分类两个字段。 批量需要将最新的商户名称和分类的映射关系推到hbase供实时使用。 原实现方案 a.原方案内容 为解决批量晚批问题&#xff0c;批量推送hbase表时一份数据产生两类rowkey&#xff1a;T-1和…

从GCC源码分析C语言编译原理——源码表层分析(脚本篇)

目录 一、目录结构 二、有意思的小功能 三、install脚本 脚本变量和设置 程序名称变量 模式和命令 参数解析 主要逻辑 四、主要功能脚本 ------------------------------------------------------------------------------------------------------------------------…

Latex转word(docx)或者说PDF转word 一个相对靠谱的方式

0. 前言 投文章过程中总会有各种各样的要求&#xff0c;其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中&#xff0c;其中公式转换是一个头疼的地方&#xff0c;还有很多图表&#xff0c;格式等等&#xff0c;想想就让人头疼欲裂。实践中摸索出一条相对靠…

挑战用React封装100个组件【010】

Hello&#xff0c;大家好&#xff0c;今天我挑战的组件是这样的&#xff01; 今天这个组件是一个打卡成功&#xff0c;或者获得徽章后的组件。点击按钮后&#xff0c;会弹出礼花。项目中的勋章是我通过AI生成的&#xff0c;还是很厉害的哈&#xff01;稍微抠图直接使用。最后面…

企业实践|广州新华学院携手泰迪智能科技开展大数据开发企业实践圆满结束

12月3日&#xff0c;新华学院健康学院携手广东泰迪智能科技股份有限公司联合开展大数据开发企业实践活动圆满结束&#xff0c;健康学院专业老师陈键聪及来自信息资源管理专业2023级24名学生参与此次活动结业仪式。泰迪智能科技董事长张良均、校企合作经理吴桂锋、钟秋平出席。 …

设计模式的艺术读书笔记

设计模式的艺术 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 创建的艺术创建型模式单例模式饿汉式单例与懒汉式单例的讨论通过静态内部类实现的更好办法 简单工厂模式 面向对象设计原则概述 单一职责原则 单一职责…