目录

最小生成树

1. 普利姆算法(加点法)

2. 克鲁斯卡尔算法(加边法)

最短路径

1. 单源点的最短路径(迪杰斯特拉算法)

2. 每一个顶点之间的最短路径(弗洛伊德算法)

拓扑排序

AOV-网

关键路径

AOE-网


本笔记参考:《数据结构(C语言版)(第2版)》


接下来将记录的是几个常用的算法。

最小生成树

【情景】

现在需要在n个城市之间建立通信网。已知:

  1. 连通n个城市仅需n - 1条线路;
  2. n个城市之间最多可能设置n(n - 1) / 2条线路;
  3. 连通一条线路需要付出相应的经济代价。

【需求】

需要在上述可能的n(n - 1) / 2条线路中选择n - 1条线路,使得总的耗费最少。

【分析】

可以用一个连通图来展示上述情景:

由上述这张网可以建立许多不同的生成树。其中,最合理的通信网是代价之和最小的生成树,这棵生成树就是该连通网的最小生成树

在进行最小生成树的生成时,多数会用到MST性质:假设N = (V, E)是一张连通网,U是顶点集V的一个非空子集。若(u, v)是一条具有最小权值(代价)的边,其中u ∈ U,v ∈ V - U,则必定存在一棵包含边(u, v)的最小生成树。

接下来出现的普利姆算法和克鲁斯卡尔算法都利用了MST性质。

1. 普利姆算法(加点法)

假设N = (V, E)表示连通网G₅,TE是N上最小生成树中边的集合。则普利姆算法的构造过程如图所示(该算法从顶点V₁开始):

该算法寻找对某一顶点而言权值最小的边,再在最小边对应的另一结点上重复该过程。在这一过程中,U中的顶点在不断增加,所以普利姆算法也可以被称为“加点法”。

注意:选择最小边时,若存在多条同样权值的边可选,则任选其一。

该算法需要一个数组承接信息,因此设计辅助数组closedge:

//---辅助数组,用以记录从顶点集U到V-U的权值的最小边---
struct
{VerTexType adjvex; //最小边在U中的那个顶点ArcType lowcost;   //最小边上的权值
}closedge[MVNum];

【参考代码:Min函数】

int Min(int vexnum)
{int k = 0;for (int i = 0; i < vexnum; i++)if (closedge[i].lowcost != 0)k = i;for (int i = 0; i < vexnum; i++)if (closedge[i].lowcost != 0 && closedge[k].lowcost > closedge[i].lowcost)k = i;return k;
}

【参考代码:MiniSpanTree_Prim函数,以邻接矩阵存储(算法本体)】

void MiniSpanTree_Prim(AMGraph G, VerTexType u)
{//从u出发构造G的最小生成树T,并输出T的各条边int k = LocateAMGVex(G, u);                       //k为顶点u的下标for (int j = 0; j < G.vexnum; j++)              //对V-U的每个顶点,初始化closeedge[j]if (j != k)closedge[j] = { u, G.arcs[k][j] };       //{顶点, 权值}closedge[k].lowcost = 0;                     //初始化,初始时U = {u}for (int i = 1; i < G.vexnum; i++)                //选择剩余的n-1个顶点,生成n-1条边{k = Min(G.vexnum);                            //从closedge内存储的各条边中,选出最小边closedge[k]VerTexType u_0 = closedge[k].adjvex;        //u_0是最小边的顶点VerTexType v_0 = G.vexs[k];                    //v_0是最小比边的另一个顶点,v_0∈V - Ucout << u_0 << v_0 << endl;closedge[k].lowcost = 0;                 //将第k个顶点并入U集for (int j = 0; j < G.vexnum; j++)            //将新顶点并入U,再选择新的最小边{if (G.arcs[k][j] < closedge[j].lowcost)    //若k到j的边,其权值比原本的closedge[j].lowcost小closedge[j] = { G.vexs[k], G.arcs[k][j] };}}
}

【算法分析】

设网中有n个顶点,则:

  1. 进行初始化的循环语句的频度是:n;
  2. 第二个循环语句的频度是:n - 1,其中还有两个内循环:
    1. Min函数求最小值的频度是:n - 1;
    2. 重新选择具有最小值的边,其频度是:n。

由此可知,普利姆算法的时间复杂度为O(n²)。该算法与网的边数无关,适合稠密图。


2. 克鲁斯卡尔算法(加边法)

依旧通过连通网G₅对算法进行说明:

克鲁斯卡尔算法同样需要辅助数组,不同的是,需要两个不同的数组完成不同的工作:

//------存储边,及其两个顶点的信息------
struct
{VerTexType Head;   //边的起点VerTexType Tail;  //边的终点ArcType lowcost;  //边的权值
}Edge[MVNum];//------标识两个连通分量------
int Vexset[MVNum];

【参考代码:MiniSpanTree_Kruskal函数,以邻接矩阵存储(算法本体)】

//------克鲁斯卡尔算法(无向网,用邻接矩阵存储)------
void MiniSpanTree_Kruskal(AMGraph G)
{SortEdge(G.arcnum);                    //将数组Edge中的各个元素按权值从小到大排序for (int i = 0; i < G.vexnum; i++)    //初始化时,各个结点各为一个连通向量Vexset[i] = i;for (int i = 0; i < G.arcnum; i++)   //依次查看数组Edge中的边{int v_head = LocateAMGVex(G, Edge[i].Head);        //v_head是边的始点Head的下标int v_tail = LocateAMGVex(G, Edge[i].Tail);        //v_tail是边的终点Tail的下标int cc_head = Vexset[v_head];      //获取始点所在的连通分量int cc_tail = Vexset[v_tail];     //获取终点所在的连通分量if (cc_head != cc_tail)           //若边的两个顶点属于不同的连通分量{cout << Edge[i].Head;      //输出此边cout << Edge[i].Tail << endl;for (int j = 0; j < G.arcnum; j++)if (Vexset[j] == cc_tail)  //统一两个集合的编号Vexset[j] = cc_head;}}
}

【算法分析】

若已学习过堆排序,通过“堆”来存放网中的边,对于包含e条边的网,上述算法排序时间是O(elog₂e)。(但是笔者还未学习到此,故没有将Sort函数的实现展示出来)

在上述算法中,最耗时的操作是合并两个不同的连通分量,若数据结构合理,该步骤的执行时间是O(log₂e)。则整个for循环的执行时间会是O(elog₂e)。

综上所述,克鲁斯卡尔算法的时间复杂度可以达到O(elog₂e)。其只与网的边有关,更适合对稀疏图求最小生成树。

最短路径

假设在计算机上建立一个交通咨询系统,可以用图的结构标识实际的交通网络。其中:

  • 顶点:表示城市;
  • 边:表示城市间的交通联系。

若有人要从A城到B城,不同的人对于路径的选择有不同的要求:譬如要节省交通费用,要速度最快、里程度最少等等。为了在图上能够表示相关信息,就需要对边赋予权,权值就是两城市之间的距离、交通费用等。

在上述情况下,对于路径的度量就被转变为了对权值之和的度量。一般而言,交通图都存在方向,因此,交通网往往通过带权有向网表示。习惯上,① 带权优先网的第一个顶点被称为源点,② 最后一个顶点被称为终点。

1. 单源点的最短路径(迪杰斯特拉算法)

接下来将会讨论:给定带权有向图G和源点v,求从v到G中其余各顶点的最短路径。迪杰斯特拉算法是一个按路径长度递增的次序产生最短路径的算法。

在求解过程中,将把网N = (V, E)的顶点分为两组:

  • S:已求出的最短路径的终点集合(初始时只包含源点v₀);
  • V - S:尚未求出的最短路径的顶点集合(初始时为V - {v₀})。

现在约定,在最短路径问题下所述的路径长度均指其权值。

该算法依照各个顶点与v₀之间的间最短路径长度递增的次序,将集合V - S中的顶点加入到集合S中去。在此过程中,总保持:

从v₀到集合S中各个顶点的路径长度 ≤ 从v₀到集合V - S中各个顶点的路径长度

【例1】

总结有向图G₆中从v₀到各个顶点的最短路径:

源点 终点 最短路径 路径长度
v₀ v₂ (v₀, v₂) 10
v₄ (v₀, v₄) 30
v₃ (v₀, v₄, v₃) 50
v₅ (v, v₄, v₃, v₅) 60
v₁

当路径不存在时,默认该条路径长度为无限大。

上述从v₀到各个顶点的路径长度就是按照递增的顺序进行排列的(其中,从v₀到v₁没有路径)。

为了实现该算法,需要引入辅助的数据结构:

由于每当一个新顶点被加入集合S中时,对于V - S中的各个顶点而言,都会多出一个“中转”顶点,一条“中转”路径,此时需要更新V - S中各个顶点的最短路径长度。

【参考代码】

bool S[MVNum];           //记录从源点v_0到终点v_i是否已被确定最短路径
int Path[MVNum];        //记录从源点v_0到终点v_i,其当前的最短路径上vi的直接前驱顶点的序号
int D[MVNum];           //记录从源点v_0到终点v_i的当前最短路径长度
void ShortestPath_DIJ(AMGraph G, int v_0)
{//---初始化---int n = G.vexnum;          //用n存储G中顶点的个数for (int v = 0; v < n; v++){S[v] = false;           //初始化SD[v] = G.arcs[v_0][v];   //初始化D(为v_0到各个终点的路径长度)if (D[v] < MaxInt)       //若v_0与当前终点之间存在路径Path[v] = v_0;elsePath[v] = -1;      //路径不存在,赋值为-1}S[v_0] = true;                //将v_0加入集合SD[v_0] = 0;                 //源点到源点的距离是0//---求到各个终点的最短路径---for (int i = 1; i < n; i++)    //对剩余n - 1个顶点求最短路径{int min = MaxInt;int v = 0;                        //保管终点for (int j = 0; j < n; j++){if (!S[j] && D[j] < min) //选择当前的最短路径{v = j;min = D[j];}}S[v] = true;for (int j = 0; j < n; j++)     //更新从v_0出发到集合V - S上的所有顶点的最短路径长度{if (!S[j] && (D[v] + G.arcs[v][j] < D[j])){D[j] = D[v] + G.arcs[v][j];    //更新路径长度Path[j] = v;               //更新j的前驱}}}
}

在上述算法中,若IntMax定义过大,可能在最后的if处造成溢出。或者,可以将数组D的类型改为unsigned int类型,通过增大存储空间,来防止溢出。

通过上述算法处理有向图G₆,其结果用表格表示:

有向图G₆的算法处理结果
v = 0 v = 1 v = 2 v = 3 v = 4 v = 5
S true false true true true true
D 0 MaxInt 10 50 30 60
Path -1 -1 0 4 0 3

【算法分析】

该算法的主循环执行了n - 1次,而每次进行主循环,其执行时间都是O(n),故该算法的时间复杂度为O(n²)(对邻接矩阵和邻接表都如此)。

若只需要寻找源点到某一顶点的最短路径,而仍用迪杰斯特拉算法,则时间复杂度不变(一样复杂)。


2. 每一个顶点之间的最短路径(弗洛伊德算法)

若要求每一个顶点之间的最短路径,可以使用两种方法:

  1. 调用n次的迪杰斯特拉算法;
  2. 使用弗洛伊德算法。

实际上,两种方式的时间复杂度都为O(n³),但弗洛伊德算法的形式更简单。

同样的,该算法也需要辅助的数据结构:

弗洛伊德算法不需要标记最短路径是否已被确认,因此不存在数组S。

若将弗洛伊德算法和迪杰斯特拉算法进行比较,会发现弗洛伊德算法也是在不断地修正中得到最终的最短路径:

  1. 算法通过初始化确定顶点之间的路径状况;
  2. 通过遍历寻找每个源点;
  3. 类似于迪杰斯特拉算法,弗洛伊德算法通过将原本的路径长度与“中转”路径长度比较,确定较小值;
  4. 循环,直到所有源点遍历结束。

【参考代码】

int Path[MaxInt][MaxInt];            //记录最短路径上顶点v_j的前一个顶点的序号
int D[MaxInt][MaxInt];              //记录顶点v_i和v_j之间的最短路径长度
void ShortestPath_Floyd(AMGraph G)
{for (int i = 0; i < G.vexnum; i++){//---初始化---for (int j = 0; j < G.vexnum; j++){D[i][j] = G.arcs[i][j];if (D[i][j] < MaxInt)Path[i][j] = i;elsePath[i][j] = -1;}D[i][i] = 0;}//---遍历,寻找每个源点的最短路径---for (int v = 0; v < G.vexnum; v++){for (int i = 0; i < G.vexnum; i++){for (int j = 0; j < G.vexnum; j++){if (D[i][v] + D[v][j] < D[i][j])      //若从i经k到j的一条路径更短{D[i][j] = D[i][v] + D[v][j];     //更新DPath[i][j] = Path[v][j];          //更改j的前驱为v}}}}
}

接下来通过G₇简单说明上述算法:

若把该算法的G₇的初始化结果即最终结果通过表格的形式显示,则如:

G₇的弗洛伊德算法 初始化结果(表格1)
D Path
序号(下标) 0 1 2 3 0 1 2 3
0 0 1 4 -1 0 -1 0
1 0 5 2 -1 -1 1 1
2 3 9 0 8 2 2 -1 2
3 6 0 -1 -1 3 -1

而当算法结束,就可以得到其最终的最短路径结果:

若需要通过表格2获取关于某一最短路径的信息,例如获得顶点0到顶点3的最短路径信息,可以参考:

  • 最短路径长度:D[0][3] = 3。表示顶点0和顶点3之间的最短路径长度是3。
  • 最短路径:Path[0][3] = 1(在当前最短路径中,顶点3的前驱是顶点1),Path[0][1] = 0。表示从顶点0到顶点3的最短路径是<0, 1>,<1, 3>。

拓扑排序

AOV-网

一个无环的有向图被称为有向无环图(Directed Acycline Graph),简称DAG图。这种图通常被用于描述一项工程或者系统的进行过程。一般地,一项工程可以可以被分为若干个被称为活动的子工程,子工程之间往往有着某些约束,譬如子过程开始的先后顺序。

以课程的学习为例:必修课可以被分为基础课和专业课。基础课独立于其他课程,而专业课的学习却存在先后顺序,例如:

必修课之间的关系
课程编号 课程名称 先修课程
C₁ 程序设计基础
C₂ 离散数学 C₁
C₃ 数据结构 C₁、C₂
C₄ 汇编语言 C₁
C₅ 高级语言程序设计 C₃、C₄
C₆ 计算机原理 C₁₁
C₇ 编译原理 C₃、C₅
C₈ 操作系统 C₃、C₆
C₉ 高等数学
C₁₀ 线性代数 C₉
C₁₁ 普通物理 C₉
C₁₂ 数值分析 C₁、C₉、C₁₀

如图所示:

类似于上图这种,通过顶点表示活动,使用弧表示活动间优先关系的有向图被称为:顶点表示活动的网(Activity On Vertex Network),简称AOV-网

注意:在AOV-网中不应该出现有向环,因为若存在有向环,则代表着有活动是以自己为先决条件的。若这样设计,则工程将会无法进行,程序陷入死循环。

为了防止环的出现,就需要对有向图图中的顶点进行拓扑排序若网中的顶点都在其的拓扑有序序列中,则该AOV-网中必定不存在环

拓扑排序,就是将AOV-网中的所有顶点排列为一个线性序列,序列满足:

【例如】

当然,在上图中,拓扑有序序列并不止展示出来的两个。

若要输出拓扑排序序列,需要:

  1. 寻找图中无前驱的顶点,输出该顶点;
  2. 删除该顶点以它为尾的弧
  3. 重复上述步骤,直到不存在无前驱的顶点。

如此,一串拓扑排序序列的输出如图所示:

同样地,该算法也需要使用辅助的数据结构:

注:接下来的图将用邻接表进行存储。

【参考代码:FindInDegree函数(求顶点的入度)】

void FindInDegree(ALGraph G)
{ArcNode* p = NULL;for (int i = 0; i < G.vexnum; i++)        //遍历邻接表{if(G.vertices[i].firstarc)p = G.vertices[i].firstarc;while (p){indegree[p->adjvex]++;            //计算入度p = p->nextarc;}}
}

【参考代码:TopologicalSort函数(算法本体)】

int indegree[MVNum];         //用于存放顶点的入度,若顶点没有前驱,则将入度置为0。
int topo[MVNum] = { 0 };       //记录拓扑序列的顶点序号。//------拓扑排序本体------
Status TopologicalSort(ALGraph G, int topo[])
{FindInDegree(G);           //求出各顶点的入度,将其存放在数组indegree中LinkStack S;              //此次所有的是链栈InitStack(S);             //将栈初始化for (int i = 0; i < G.vexnum; i++){if (!indegree[i])Push(S, i);            //将入度为0的顶点入栈}int top = 0;              //对输出的顶点进行计数while (S){int i = 0;Pop(S, i);topo[top++] = i;ArcNode* p = G.vertices[i].firstarc;while (p){indegree[p->adjvex]--;if (!indegree[p->adjvex])  //将入度为0者入栈Push(S, p->adjvex);p = p->nextarc;}}if (top < G.vexnum)return false;elsereturn true;
}

【算法分析】

上述算法所需时间主要集中在三个方面:

  1. 求各顶点入度(FindInDegree函数),时间复杂度为O(e);
  2. 建立入度为0的顶点栈,时间复杂度为O(n)(若有向图无环,每个顶点入栈一次,出栈一次);
  3. 入度减1的操作在循环中总共执行了e次,时间复杂度为O(e)。

综上所述,总的时间复杂度为O(n + e)

关键路径

AOE-网

与AOV-网相对的,也存在以边表示活动的网,被称为AOE-网(Activity On Edge)。AOE-网是一个带权的有向无环图,例如:

通常,AOE-网被用来估算工程的完成时间,或者判断哪些活动会是影响工程进度的关键。

在上图中,存在着从V₀ ~ V₈共9个事件。其中,每个事件表示在其之前的活动已经结束,而在其之后的活动可以开始。例如:V₄表示a₄和a₅已经完成,a₇和a₈可以开始。

一个工程的开始和结束是唯一的,因此,(在正常情况下)AOE-网只有一个入度为0的点,即源点,也只有一个出度为0的点,即汇点。AOE-网中存在着一些概念:

概念 解释
路径的带权路径长度(简称路径长度) 一条路径各弧上的权值之和
关键路径 即从源点到汇点,其中带权路径长度最长的路径
关键活动 是关键路径上的活动

在上图中,各个概念对应着:

  • 源点:V₀;
  • 汇点:V₈;
  • 关键路径:(V₀, V₁, V₄, V₆, V₈) 或 (V₀, V₁, V₄, V₇, V₈);
  • 关键活动:(a₁, a₄, a₇, a₁₀) 或 (a₁, a₄, a₈, a₁₁)。(所谓关键活动,只要其中的某项活动能够缩短一天,则整个工程也能缩短一天)

为了找到关键路径,就需要4个能够进行描述的量:

由上可知:对于关键活动aₓ,必定存在 e(x) = l(x) 。把这个结论反过来,将每一个e(x) = l(x)的活动aₓ找出来,就可以形成关键路径。因此,现在的问题就转换为求出上述四个量的问题:

逆拓扑排序可以通过反向查找数组topo实现。

【参考代码】

int ve[MVNum] = { 0 };              //事件的最早发生时间
int vl[MVNum] = { 0 };             //事件的最迟发生时间
Status CriticalPath(ALGraph G)
{int topo[MVNum] = { 0 };if (!TopologicalSort(G, topo))    //存在有向环return false;int n = G.vexnum;for (int i = 0; i < n; i++)ve[i] = 0;                  //设定最早发生时间的初值//---求最早发生时间---for (int i = 0; i < n; i++){int k = topo[i];                     //取得拓扑排序中的顶点序号ArcNode* p = G.vertices[k].firstarc; //p指向k的第一个邻接顶点while (p != NULL)                        //更新k的所有邻接顶点的最早发生时间{int j = p->adjvex;                  //取得邻接顶点的序号if (ve[j] < ve[k] + p->info)      //更新顶点j的最早发生时间ve[j] = ve[k] + p->info;p = p->nextarc;                      //p指向k的下一个邻接顶点}}for (int i = 0; i < n; i++)                   //设定最迟发生时间的初值vl[i] = ve[n - 1];//---求最迟发生时间---for (int i = n - 1; i >= 0; i--){int k = topo[i];                      //取得逆拓扑序列中的顶点序号ArcNode* p = G.vertices[k].firstarc;while (p != NULL){int j = p->adjvex;if (vl[k] > vl[j] - p->info)     //更新顶点k的最迟发生时间vl[k] = vl[j] - p->info;p = p->nextarc;}}//---判断每一活动是否是关键活动---for (int i = 0; i < n; i++){ArcNode* p = G.vertices[i].firstarc; //p指向i的第一个邻接顶点while (p != NULL){int j = p->adjvex;                 //取得邻接顶点的序号int e = ve[i];                      //取得活动<vi, vj>的最早开始时间int l = vl[j] - p->info;         //计算活动<vi, vj>的最迟开始时间if (e == l)                            //若为关键活动,输出cout << G.vertices[i].data << G.vertices[j].data << endl;p = p->nextarc;}}
}

【算法分析】

为了求出ve(i)、ve(i)、e 和 l这四个量,就需要遍历每个顶点和每个顶点边表中的所有边结点进行查找,因此,求关键路径的时间复杂度为O(n + e)

若一个工程有不止一条关键路径,仅仅提高其中一条关键路径上关键活动的速度,是不足以缩短整个工程的工期的。

数据结构入门6-2(图 - 图的应用)相关推荐

  1. Linux入门基础思维导图

    Linux入门基础思维导图 01 发行版本 02 系统目录 欢迎关注微信公众号[厦门微思网络].www.xmws.cn专业IT认证培训19周年 03 环境安装 04 SSH服务 05 启动模式策略 / ...

  2. c++ 图的连通分量是什么_学习数据结构第五章:图(图的遍历操作)

    第五章:图(图的遍历操作) 1.图的遍历 图的遍历:从图中某一顶点出发,按照某种搜索方法沿着图中的边对图中的所有顶点访问依次且仅访问一次 其实树的层次遍历和图的广度优先搜索类似,可以把这个二叉树看成一 ...

  3. 数据结构:并查集和图

    并查集 并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题. 常涉及两个基本操作: (1)合并两个不相交的集合 (2)判断两个元素是否属于同一个集合. 将N个不同元素分成一组不想交的集 ...

  4. Hadoop 入门学习思维导图

    Hadoop 入门学习思维导图:

  5. 2020考研-王道数据结构-图-图的遍历

    说在开头 某些语法采用的是c11标准. 博客中中的代码均为可执行程序,以函数的形式给出,最后所有的程序会在博客完结后上传到CSDN. 头文件定义 #include <iostream> # ...

  6. 数据结构——树的思维导图,帮助自己记忆。

    数据结构--树的思维导图,帮助自己记忆.

  7. 数据结构(二十一)——图和图的应用

    文章目录 前言 图(graph) 1)图的基本概念及特性 2)图的描述 a. 无权图描述 b. 加权图描述 3)图的类实现 4)图的遍历 a. 广度优先搜索 b. 深度优先搜索 c. 两种搜索算法的比 ...

  8. 数据结构与算法思维导图(学习笔记)

    版本 数据结构与算法思维导图V1.0 V1.0分享版本这个可以直接看,不需要下载. 说明 1.free 2.目前内容主要包含内容包含: 数据结构与算法思维导图 包含:线性表.顺序结构.链式结构,栈与队 ...

  9. 数据结构总结及思维导图(王道考研)

    数据结构 第一章:数据结构的 基本概念 定义 在任何问题中,数据元素都不是孤立存在的,而是在它们之间存在着某种关系,这种数据元素相互之间的关系称为结构(Structure).数据结构是相互之间存在一种 ...

  10. 外汇蜡烛图入门,基本蜡烛图形态

    外汇蜡烛图入门,基本蜡烛图形态 什么是蜡烛图交易? 在我们的动画角色哥斯拉还是一只可爱的小蜥蜴时,日本人就已经创造了他们用来交易大米的古老版本技术分析方法.对,就是用来交易大米的方法. 一名叫做史蒂夫 ...

最新文章

  1. java string.indexof(string)_Java StringBuffer indexOf()方法
  2. 图表对比详解:亚马逊、微软和谷歌云的机器学习即服务哪家强
  3. 经典Golang语法50问!
  4. 学计算机趣图,我的世界:六张玩家自制趣图,最后一张,想起了“骗”父母买电脑...
  5. 化工网站开发_西部地区鼓励投资化工(石化)项目征求意见发布
  6. linux获取性能指数,Linux环境获取系统性能数据
  7. 历史命令与实时记录(redhat6.8)
  8. rjdbc读取mysql_R通过RJDBC连接外部数据库 (转)
  9. 【Leetcode】【Easy】Implement strStr()
  10. 网络爬虫--10.使用正则表达式的爬虫
  11. 石油、黄金与美元的游戏
  12. webservice传递特殊字符时的解决的方法
  13. 爬取人力资源社保局咨询问题
  14. 计算机专业毕业设计中期考核表,研究生中期考核表导师评语
  15. php 请求java_怎么php发送get请求给Java,然后返回想要的具体参数
  16. 树莓派架设VNC服务
  17. 无法启动程序因为计算机中丢失dev,DevUseAnalyzerTask.dll
  18. mysql数据库修复工具 innodb表数据恢复 ibd文件恢复工具
  19. Mac手动关闭暗黑模式
  20. HTML5 特殊符号大全

热门文章

  1. Deci and Centi Seconds parsing in java
  2. 运放电压跟随电路应用
  3. JavaScript数组方法大全(分为会不会改变原数组)
  4. 猜单词游戏更新啦 (0.88.2及1.88.3)
  5. 快捷截图、标注和取色的小工具snipaste
  6. [WHS] Windows Home Server 官方中文站点上线
  7. 在Windows 7中使用搜索连接器从您的桌面搜索网站
  8. 进击的UI----------------(常见快捷键的使用)
  9. 数据挖掘 顶级期刊_SEER数据挖掘如果用上这些图会更好发
  10. [高数][高昆轮][高等数学上][第一章-函数与极限]03.函数的极限