基础数据结构与算法总结2(附有完整代码,适合小白)

目录

前言

7.链式前向星

71,具体代码为:

8,搜索技术之深搜DFS和宽搜BFS

8.1,具体代码如下

 9,连通分量连通图,tarjan算法

9.1, 具体代码如下:

1,tarjan算法求桥

2,tarjan算法求割点

10,最短路径算法(Dijkstra算法和Floyd算法)

 10.1,Dijkstra算法完整代码如下

10.2,Floyd算法完整代码如下:

 11,拓扑排序

 11.1拓扑排序完整代码如下:

12,查找和折半查找 

12.1,折半查找 

12.2,顺序查找(优化版)


前言

以下算法和数据结构代码都可以在本人的GitHub仓库中找到,欢迎大家来下载,可以的话,请给博主的GitHub来一个star,GitHub链接如下https://github.com/hifuda/algorithm-and-data-structure,但是我还是把完整的代码也放在了我的本篇博客上,为了照顾不玩GitHub的朋友。本篇博客是跟着b站视频来学习的,链接如下https://www.bilibili.com/video/BV1LV411z7Bq/?spm_id_from=333.337.search-card.all.click&vd_source=d5a99b3b2961a4345794b35726979478,由于篇幅有限,我打算分篇更新。感谢大家的观看,求个赞!

7.链式前向星

特征:

链式前向星其实就是静态建立的邻接表,时间效率为O(m),空间效率也为O(m)。遍历效率也为O(m)。

对于下面的数据,第一行5个顶点,7条边。接下来是边的起点,终点和权值。也就是边1 -> 2 权值为1。

5 7
1 2 1
2 3 2
3 4 3
1 3 4
4 1 5
1 5 6
4 5 7

链式前向星存的是以【1,n】为起点的边的集合,对于上面的数据输出就是:

1 //以1为起点的边的集合
1 5 6
1 3 4
1 2 1
 
2 //以2为起点的边的集合
2 3 2
 
3 //以3为起点的边的集合
3 4 3
 
4  //以4为起点的边的集合
4 5 7
4 1 5
 
5 //以5为起点的边不存在

我们先对上面的7条边进行编号第一条边是0以此类推编号【0~6】,然后我们要知道两个变量的含义:

  • Next,表示与这个边起点相同的上一条边的编号。
  • head[ i ]数组,表示以 i 为起点的最后一条边的编号。

 head数组一般初始化为-1,为什么是 -1后面会讲到。加边函数是这样的:

void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
    edge[cnt].to = v; //终点
    edge[cnt].w = w; //权值
    edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[u] = cnt++;//更新以u为起点上一条边的编号
}

我们只要知道next,head数组表示的含义,根据上面的数据就可以写出下面的过程:

对于1 2 1这条边:edge[0].to = 2;     edge[0].next = -1;      head[1] = 0;

对于2 3 2这条边:edge[1].to = 3;     edge[1].next = -1;      head[2] = 1;

对于3 4 3这条边:edge[2].to = 4;     edge[2],next = -1;      head[3] = 2;

对于1 3 4这条边:edge[3].to = 3;     edge[3].next = 0;       head[1] = 3;

对于4 1 5这条边:edge[4].to = 1;     edge[4].next = -1;      head[4] = 4;

对于1 5 6这条边:edge[5].to = 5;     edge[5].next = 3;       head[1] = 5;

对于4 5 7这条边:edge[6].to = 5;     edge[6].next = 4;       head[4] = 6;

遍历函数是这样的:

for(int i = 1; i <= n; i++)//n个起点
    {
        cout << i << endl;
        for(int j = head[i]; j != -1; j = edge[j].next)//遍历以i为起点的边
        {
            cout << i << " " << edge[j].to << " " << edge[j].w << endl;
        }
        cout << endl;
    }

 

第一层for循环是找每一个点,依次遍历以【1,n】为起点的边的集合。第二层for循环是遍历以 i 为起点的所有边,k首先等于head[ i ],注意head[ i ]中存的是以 i 为起点的最后一条边的编号。然后通过edge[ j ].next来找下一条边的编号。我们初始化head为-1,所以找到你最后一个边(也就是以 i 为起点的第一条边)时,你的edge[ j ].next为 -1做为终止条件。

71,具体代码为:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;//点数最大值
int n, m, cnt;//n个点,m条边
struct Edge
{
    int to, w, next;//终点,边权,同起点的上一条边的编号
}edge[maxn];//边集
int head[maxn];//head[i],表示以i为起点的第一条边在边集数组的位置(编号)
void init()//初始化
{
    for (int i = 0; i <= n; i++) head[i] = -1;
    cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
    edge[cnt].to = v; //终点
    edge[cnt].w = w; //权值
    edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[u] = cnt++;//更新以u为起点上一条边的编号
}
int main()
{
    cin >> n >> m;
    int u, v, w;
    init();//初始化
    for (int i = 1; i <= m; i++)//输入m条边
    {
        cin >> u >> v >> w;
        add_edge(u, v, w);//加边
        /*
        加双向边
        add_edge(u, v, w);
        add_edge(v, u, w);
        */
    }
    for (int i = 1; i <= n; i++)//n个起点
    {
        cout << i << endl;
        for (int j = head[i]; j != -1; j = edge[j].next)//遍历以i为起点的边
        {
            cout << i << " " << edge[j].to << " " << edge[j].w << endl;
        }
        cout << endl;
    }
    return 0;
}

8,搜索技术之深搜DFS和宽搜BFS

1,深度优先搜索

如果是使用邻接矩阵存储的,则先从小的开始访问,如果是使用邻接表存储的,则先从大的开始访问

例题:

访问步骤如下(如下图形使用邻接矩阵存储):

1,首先访问根节点,将根节点的标识改为true表示已访问

2,然后看根节点下有没有其他的没有被访问的节点,发现有2和3。由于是使用邻接矩阵存储,所以先访问小的节点2。

3,将节点2标记为true表示为已访问,然后看是否有其他为被访问的节点,发现4,5和6。先访问4

4,将节点4标记为true表示为已访问,然后看是否有其他为被访问的节点,发现5和2。但是2已经被访问,所以访问5.

5,将节点5标记为true表示为已访问,然后看是否有其他为被访问的节点,然后发现没有了,所以回退到上一个节点4,然后看4节点下是否有其他为被访问的节点。

6,没有则继续回退,回退到2,然后看2节点下是否有其他为被访问的节点。发现了6。

剩下的步骤同理,不再赘述。

执行完以上步骤之后,产生一个深度优先搜索树。

生成树:包含所有节点的树。

树:连通且没有回路,或者边数=顶点数-1

算法实现:

基于邻接矩阵的深度优先遍历

基于邻接表的深度优先遍历

非连通图,要判断是否还有节点没有被访问

算法分析:

2,宽度优先搜索

可以使用队列实现

广度优先搜索树:

算法步骤:

算法实现:

基于邻接矩阵的广度优先搜索

基于邻接表的广度优先搜索

算法分析:

8.1,具体代码如下

深搜示例图:

1,邻接矩阵深搜

//邻接矩阵存储无向图,深搜 ,先来后服务类似于栈,所以可以用递归 
#include<iostream>
using namespace std;

#define MaxVnum 100      //顶点数最大值
bool visited[MaxVnum];   //访问标志数组,其初值为"false"
typedef char VexType;    //顶点的数据类型,根据需要定义
typedef int EdgeType;    //边上权值的数据类型,若不带权值的图,则为0或1

typedef struct FMGragh{
    int Enum,Vnum;        //存储顶点的个数和边的个数
    EdgeType Edge[MaxVnum][MaxVnum]; 
    VexType v[MaxVnum];
}FMGragh;

//寻找顶点在顶点表中的位置 
int Locatevex(FMGragh f,char a){
    int i;
    for(i=0;i < f.Vnum;i++){
        if(f.v[i] == a){
            return i;
        }
    }
    //cout << "无法找到对应顶点" << endl;
    return -1; 
}

//初始化有向图
void CreateF(FMGragh &f){
    int i,j,y,z;
    char m,n;
    cout << "请输入图的顶点个数" << endl;
    cin >> f.Vnum;
    cout << "请输入图的边的条数" << endl;
    cin >> f.Enum;
    cout << "请输入图的顶点元素的值" << endl;
    for(i = 0;i < f.Vnum;i++){
        cin >> f.v[i]; 
    }
    for(j = 0;j < f.Enum;j++){
    cout << "请输入边的邻接的两个顶点(第一个元素为弧头,第二个元素为弧尾)" << endl;
    cin >> m >> n;
    y = Locatevex(f,m);        //寻找m元素在顶点表中的位置,弧头
    z = Locatevex(f,n);        //寻找n元素在顶点表中的位置,弧尾
    if(y != -1 && z != -1){
        f.Edge[y][z] = f.Edge[z][y] = 1;
    }else{
        cout << "无法找到对应顶点,请重新输入" << endl;
        j--;
    }     
    } 
} 

//打印有向图
void printF(FMGragh f){
    int i,j;
    for(i = 0;i < f.Vnum;i++){
        for(j = 0;j < f.Vnum;j++){
            cout << f.Edge[i][j] << "\t";
        }
        cout << endl;
    }
} 

//深搜
void DFS_F(FMGragh f,int v){
    int i;
    visited[v] = true;
    for(i = 0;i < f.Vnum;i++){
        if(f.Edge[v][i]!=0 && visited[i]!=true){
            cout << "元素下标 " << v << "\t" << i << endl;
            cout << f.v[i] << "\t";
            DFS_F(f,i);
        }
    }
} 

int main(){
    char v;
    int u;
    FMGragh f;
    CreateF(f);
    printF(f);
    cout << "请输入起始遍历节点的位置:";
    cin >> v; 
    u = Locatevex(f,v);
    if(u != -1){
        cout << "深度优先搜索遍历连通图结果:" <<endl;
        DFS_F(f,v);
    }else{
        cout << "顶点值输入错误" << endl;
    }
    DFS_F(f,v);
    return 0;
}

运行示例

2,邻接表深搜

//邻接表存储无向图,深搜 ,先来后服务类似于栈,所以可以用递归 
//邻接表存储无向图
#include<iostream>
using namespace std;

#define MaxVnum 100
bool visited[MaxVnum];       //访问标志数组,其初值为"false"
typedef char NodeType;

typedef struct LNode{        //定义邻接节点数据结构 
    struct LNode *next;     //用于指向下一个邻接节点 
    NodeType Ndata;         //节点中的值 
}LNode,*ListNode; 

typedef struct LVex{        //定义顶点数据结构 
    ListNode first;            //用于指向第一个邻接节点 
    NodeType Vdata;            //存储顶点信息 
}LVex,*ListVex;

typedef struct Vexs{        //用于保存图的简要信息的数据结构 
    LVex lv[MaxVnum];        //用于线性保存顶点信息 
    int Enum,Vnum;            //用于保存边数和顶点数 
}Vexs; 

//最好把方法先声明,然后再调用 
void Insertedge(Vexs &v);
int LocateVex(Vexs v,NodeType a);
void CreateFG(Vexs &v);
void printFG(Vexs v);
void DFS_FG(Vexs v,int a);

int main(){
    Vexs v;
    char u;
    int m;
    CreateFG(v);
    printFG(v);
    cout << "请输入起始遍历节点的位置:";
    cin >> u;
    m = LocateVex(v,u);
    if(m!=-1){
        cout << "深度优先搜索遍历连通图结果:" <<endl;
        DFS_FG(v,m);
    }
    return 0;
}

//深搜
void DFS_FG(Vexs v,int a){
    ListNode p;
    int i,m;
    p = v.lv[a].first;
    cout << v.lv[a].Vdata << "\t";
    visited[a] = true;
    for(i = 0;i < v.Vnum;i++){
        m = LocateVex(v,p->Ndata);
        if(p && visited[m]!=true){
            DFS_FG(v,m); //继续递归 
        }
        p = p->next;
    }
} 

//插入边 
void Insertedge(Vexs &v){
    int i,j,z;
    NodeType m,n;
    ListNode s,q;
    for(z = 0;z < v.Enum;z++){
        cout << "请输入邻接的顶点" << endl;
        cin >> m >> n;
        i = LocateVex(v,m);    //弧头节点的位置 
        j = LocateVex(v,n);    //弧尾节点的位置 
        cout << "弧头" << i << "\t" << "弧尾" << j << endl; 
        if(i != -1 && j != -1){
            s = new LNode;
            s->Ndata = n;
            s->next = v.lv[i].first;
            v.lv[i].first = s; //注意这里first指针已经指向第一个邻接节点,而不是它本身 
            q = new LNode;
            q->Ndata = m;
            q->next = v.lv[j].first;
            v.lv[j].first = q;
            cout << "头插法插入" << v.lv[i].first->Ndata << endl; 
            cout << "头插法插入" << v.lv[j].first->Ndata << endl; 
        }else{
            cout << "顶点不存在,请重新输入" << endl;
            z--; 
        }
    } 
    
}

//用于寻找该字符在顶点线性表中的位置 
int LocateVex(Vexs v,NodeType a){
    int i;
    for(i = 0;i < v.Vnum;i++){
        if(v.lv[i].Vdata == a){
            return i;
        }
    }
    return -1;
} 
//创建邻接表保存有向图
void CreateFG(Vexs &v){
    int i;
    cout << "请输入有向图的顶点的个数" << endl;
    cin >> v.Vnum;
    cout << "请输入有向图的边的条数" << endl;
    cin >> v.Enum;
    cout << "请输入有向图的顶点值" << endl;
    for(i = 0;i < v.Vnum;i++){
        cin >> v.lv[i].Vdata;
        v.lv[i].first = NULL;
    }
    for(i = 0;i < v.Vnum;i++){//打印输入的顶点 
        cout << v.lv[i].Vdata << "\t";
    }
    cout << endl; 
    Insertedge(v);
} 

//打印有向图
void printFG(Vexs v){
    int i,j;
    ListNode p;
    for(i = 0;i < v.Vnum;i++){
        //p = v.lv[i].first->next;千万注意这里不可以这么写 
        p = v.lv[i].first;
        cout << "[" << v.lv[i].Vdata << "]:"; 
        while(p){
            cout << p->Ndata << "\t";
            p = p->next;
        }
        cout << endl;
    }
} 

运行示例

宽搜示例图:

3,邻接矩阵宽搜

//邻接矩阵存储无向图,宽搜,先来先服务,可以使用队列 
#include<iostream>
#include<queue> 
using namespace std;

#define MaxVnum 100      //顶点数最大值
bool visited[MaxVnum];   //访问标志数组,其初值为"false"
typedef char VexType;    //顶点的数据类型,根据需要定义
typedef int EdgeType;    //边上权值的数据类型,若不带权值的图,则为0或1

typedef struct AMGragh {
    EdgeType Edge[MaxVnum][MaxVnum]; //存储边的信息 
    VexType v[MaxVnum];              //存储顶点值 
    int Enum,Vnum;                     //存储顶点和边的个数 
}AMGragh;

//查找顶点所在的位置便且返回 
int Locatevex(AMGragh a,char c){
    int i;
    for(i=0;i < a.Vnum;i++){
        if(a.v[i] == c){
            return i;
        }
    }
    //cout << "无法找到对应顶点" << endl;
    return -1; 
}

//初始化邻接矩阵
void CreateL(AMGragh &a){
    int i,j,y,z;
    char m,n;
    cout << "请输入图的顶点个数" << endl;
    cin >> a.Vnum;
    cout << "请输入图的边的条数" << endl;
    cin >> a.Enum;
    cout << "请输入图的顶点元素的值" << endl;
    for(i = 0;i < a.Vnum;i++){
        cin >> a.v[i]; 
    }
    for(j = 0;j < a.Enum;j++){
    cout << "请输入边的邻接的两个顶点" << endl;
    cin >> m >> n;
    y = Locatevex(a,m);        //寻找m元素在顶点表中的位置 
    z = Locatevex(a,n);        //寻找n元素在顶点表中的位置 
    if(y != -1 && z != -1){
        a.Edge[y][z] = a.Edge[z][y] = 1;
    }else{
        cout << "无法找到对应顶点,请重新输入" << endl;
        j--;
    }     
    } 
    
} 

//打印邻接矩阵
void printL(AMGragh a){
    int i,j;
    for(i = 0;i < a.Vnum;i++){
        for(j = 0;j < a.Vnum;j++){
            cout << a.Edge[i][j] << "\t";
        }
        cout << endl;
    }
}  

//宽搜
void BFS_AM(AMGragh a,int s){
    int u,n;
    queue<int> Q;//创建一个普通队列(先进先出),里面存放int类型
    cout << a.v[s] << "\t";
    visited[s] = true;
    Q.push(s);//源点v入队
    while(!Q.empty()){
        u = Q.front();
        Q.pop();
        for(n = 0;n < a.Vnum;n++){
            if(a.Edge[u][n] && !visited[n]){
               cout<<a.v[n]<<"\t";
               visited[n]=true;
               Q.push(n);
            }    
        }
    }
} 

int main(){
    int i;
    char c;
    AMGragh a;
    CreateL(a);
    printL(a);
    cout << "请输入宽搜的起始节点:";
    cin >> c;
    i = Locatevex(a,c);
    if(i!=-1){
        cout << "广度优先搜索遍历连通图结果:" <<endl;
        BFS_AM(a,i); 
    }
    return 0;
}

运行示例

4,邻接表宽搜

//邻接表存储无向图,宽搜 
#include<iostream>
#include<queue> 
using namespace std;

#define MaxVnum 100
bool visited[MaxVnum];       //访问标志数组,其初值为"false"
typedef char NodeType;

typedef struct LNode{        //定义邻接节点数据结构 
    struct LNode *next;     //用于指向下一个邻接节点 
    NodeType Ndata;         //节点中的值 
}LNode,*ListNode; 

typedef struct LVex{        //定义顶点数据结构 
    ListNode first;            //用于指向第一个邻接节点 
    NodeType Vdata;            //存储顶点信息 
}LVex,*ListVex;

typedef struct Vexs{        //用于保存图的简要信息的数据结构 
    LVex lv[MaxVnum];        //用于线性保存顶点信息 
    int Enum,Vnum;            //用于保存边数和顶点数 
}Vexs; 

//最好把方法先声明,然后再调用 
void Insertedge(Vexs &v);
int LocateVex(Vexs v,NodeType a);
void CreateFG(Vexs &v);
void printFG(Vexs v);
void BFS_AL(Vexs v,int a);

int main(){
    Vexs v;
    char u;
    int m;
    CreateFG(v);
    printFG(v);
    cout << "请输入起始遍历节点的位置:";
    cin >> u;
    m = LocateVex(v,u);
    if(m!=-1){
        cout << "深度优先搜索遍历连通图结果:" <<endl;
        BFS_AL(v,m);
    }
    return 0;
}

//深搜
void BFS_AL(Vexs v,int a){
    ListNode p;
    int i,m;
    queue<int> Q;
    cout << v.lv[a].Vdata << "\t";    //访问这个邻接节点 
    visited[a] = true;                //将标识标记为已访问 
    Q.push(a);                        //将这个节点入队 
    while(!Q.empty()){                //假如队列不为空,继续循环 
        m = Q.front();                
        Q.pop();                    //对头出队 
        p = v.lv[m].first;            //p指针指向对头元素的首个邻接节点 
        while(p){                    //当p指针不为空,继续循环 
            i = LocateVex(v,p->Ndata);     //查询此节点在顶点表中的位置 
            if(!visited[i]){            //看此节点是否被访问 
                cout << v.lv[i].Vdata << "\t";    //访问这个节点
                visited[i] = true;             //将标识标记为已访问 
                Q.push(i);                    //将这个节点入队 
            }
            p = p->next;                //指针下移一位 
        }
        
    }
} 

//插入边 
void Insertedge(Vexs &v){
    int i,j,z;
    NodeType m,n;
    ListNode s,q;
    for(z = 0;z < v.Enum;z++){
        cout << "请输入邻接的顶点" << endl;
        cin >> m >> n;
        i = LocateVex(v,m);    //弧头节点的位置 
        j = LocateVex(v,n);    //弧尾节点的位置 
        cout << "弧头" << i << "\t" << "弧尾" << j << endl; 
        if(i != -1 && j != -1){
            s = new LNode;
            s->Ndata = n;
            s->next = v.lv[i].first;
            v.lv[i].first = s; //注意这里first指针已经指向第一个邻接节点,而不是它本身 
            q = new LNode;
            q->Ndata = m;
            q->next = v.lv[j].first;
            v.lv[j].first = q;
            cout << "头插法插入" << v.lv[i].first->Ndata << endl; 
            cout << "头插法插入" << v.lv[j].first->Ndata << endl; 
        }else{
            cout << "顶点不存在,请重新输入" << endl;
            z--; 
        }
    } 
    
}

//用于寻找该字符在顶点线性表中的位置 
int LocateVex(Vexs v,NodeType a){
    int i;
    for(i = 0;i < v.Vnum;i++){
        if(v.lv[i].Vdata == a){
            return i;
        }
    }
    return -1;
} 
//创建邻接表保存有向图
void CreateFG(Vexs &v){
    int i;
    cout << "请输入有向图的顶点的个数" << endl;
    cin >> v.Vnum;
    cout << "请输入有向图的边的条数" << endl;
    cin >> v.Enum;
    cout << "请输入有向图的顶点值" << endl;
    for(i = 0;i < v.Vnum;i++){
        cin >> v.lv[i].Vdata;
        v.lv[i].first = NULL;
    }
    for(i = 0;i < v.Vnum;i++){//打印输入的顶点 
        cout << v.lv[i].Vdata << "\t";
    }
    cout << endl; 
    Insertedge(v);
} 

//打印有向图
void printFG(Vexs v){
    int i,j;
    ListNode p;
    for(i = 0;i < v.Vnum;i++){
        //p = v.lv[i].first->next;千万注意这里不可以这么写 
        p = v.lv[i].first;
        cout << "[" << v.lv[i].Vdata << "]:"; 
        while(p){
            cout << p->Ndata << "\t";
            p = p->next;
        }
        cout << endl;
    }
} 

运行示例

 9,连通分量连通图,tarjan算法

1.连通图和连通分量

连通图:图中任意两点都是可达的

连通图的连通分量就是它本身

如上图有三个连通分量

有向图才有强连通之说。

2,无向图的桥和割点

5~7,5~8都是桥

割点和桥的关系:

1,有割点不一定有桥,有桥一定存在割点

2,桥一定是割点依附的边

3,无向图的双连通分量

点双连通分量:没有割点

边双联通分量:没有桥

把每一个点双连通看作一个缩点,割点依然是割点,这样我们就可以得到一颗树

3,tarjan算法

前置知识:

1,判断桥算法步骤

First, perform a depth-first search on the graph, and mark the dfn and low values ​​of each node.

When node 4 is searched, we find that no adjacent points have been visited. At this time, we also find that node 4 can be rolled back to node 1 at the earliest. At this time, we start to update low=1 and start rolling back. The low value of the nodes on the path should also be updated to 1. When rolling back to node 5, it is found that there are still 7 nodes that have not been visited, so node 7 will be visited, low=7, and then the rollback starts again.

Here, the low value of node 7 cannot be updated to 1 because the path from 7 cannot be rolled back to node 1.

Bridge judgment rule: When the low value of the child node is greater than the dfn value of the parent node, then the edge formed by the child and the father node is a bridge. For example, the edge formed by nodes 5 and 7 in the figure above is a bridge.

Judgment rules for cut points:

But if this node happens to be the root node, then at this time, one more condition needs to be added, that is, the low value of two child nodes must be greater than the dnf of this node, then the root node can be said to be a cut point.

3.1 Strongly connected components of directed graphs

9.1, the specific code is as follows:

1. Tarjan algorithm to find bridges

If you feel that the data structure here is not understandable, then please read the previous chained forward star to supplement your knowledge in this area.

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1000+5;
int n,m;				//n表示顶点数,m表示边数 
int head[maxn],cnt;		//head存储顶点 
struct Edge
{
	int to,next;		//两个邻接节点 
}e[maxn<<1];			//maxn<<1,由于边的数目一般比点的数目要多,所以防止溢出 

int low[maxn],dfn[maxn],num;	//low存储可以回去的最早的节点,dfn表示深度优先遍历的顺序 
void add(int u,int v)
{
	e[++cnt].next=head[u];		
	e[cnt].to=v;
	head[u]=cnt;	
}
void tarjan(int u,int fa)
{
	dfn[u]=low[u]=++num;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa)
			continue;
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])
				cout<<u<<"—"<<v<<"是桥"<<endl; 
		}
		else
			low[u]=min(low[u],dfn[v]);
	}
}

void init()
{
	memset(head,0,sizeof(head));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	cnt=num=0;
}

int main()
{
	while(cin>>n>>m)
	{
		init();
		int u,v;
		while(m--)
		{
			cin>>u>>v;	
			add(u,v);  	//由于是无向图,所以要插两次 
			add(v,u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i])
				tarjan(1,0);
	}
	return 0;
}

2. Tarjan algorithm to find cut points

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1000+5;
int n,m;
int head[maxn],cnt,root;
struct Edge
{
	int to,next;
}e[maxn<<1];

int low[maxn],dfn[maxn],num;
void add(int u,int v)
{
	e[++cnt].next=head[u];
	e[cnt].to=v;
	head[u]=cnt;	
}
void tarjan(int u,int fa)
{
	dfn[u]=low[u]=++num;
	int count=0;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa)
			continue;
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])
			{
				count++;
				if(u!=root||count>1)
					cout<<u<<"是割点"<<endl; 
			}	
		}
		else
			low[u]=min(low[u],dfn[v]);
	}
}

void init()
{
	memset(head,0,sizeof(head));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	cnt=num=0;
}

int main()
{
	while(cin>>n>>m)
	{
		init();
		int u,v;
		while(m--)
		{
			cin>>u>>v;
			add(u,v);
			add(v,u);
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i])
			{
				root=i;
				tarjan(i,0);
			 } 
	}
	return 0;
}

10. Shortest path algorithm (Dijkstra algorithm and Floyd algorithm)

1. Dijkstra algorithm

Algorithm steps:

Graphs are stored using adjacency matrices

S is the source point set, VS is other vertices other than the source point. The dist array is used to save the distance from this source point to each vertex. The dist array will be continuously updated as the vertices in the VS set are added to the S set. The p array Used to save the predecessor vertex to the next vertex.

Find the current shortest path

After selecting the vertices, add them to the S set

With the help of the vertices currently added to the S set, the values ​​from the source point to other nodes are updated, but the following conditions must be met. dist[j] > G.Edge[t][j]+dist[t].

Repeat the above steps:

Analysis of Algorithms:

2. Floyd’s algorithm

Algorithm steps:

As shown below:

First we borrow 0 points, but only the in-degree of this node can be borrowed. For example, 2 to 1 was originally 5, but after we borrowed 0, 2->0->1, the weight is 4, so we change dist [2][1] = 4, of course the predecessor node will also change p[2][1] =0, and the same is true for the other edge.

借完0点,之后我们在借1点,重复如上操作,依次类推,借2点...。

当然,我们会在借完的基础上再借。比如说2到3,我们可以借1,但是要在我们借0的基础上借1.也就是说走的是如下的路径

2->0->1->3

此时dist[2][3] = dist[2][1]+dist[1][3]

算法分析:

 10.1,Dijkstra算法完整代码如下

测验图

1,Dijkstra算法完整代码

//邻接矩阵存储网(有向图带权) 
#include<iostream>
#include <string.h>
using namespace std;

#define MaxVnum 100      //顶点数最大值
typedef char VexType;   //顶点的数据类型,根据需要定义
typedef int EdgeType;   //边上权值的数据类型,若不带权值的图,则为0或1
const int inf = 1e7;    //定义一个很大的树表示点之间不可达
int dist[MaxVnum];
int p[MaxVnum];
bool flag[MaxVnum];
typedef struct FMGragh{
    int Enum,Vnum;        //存储顶点的个数和边的个数
    EdgeType Edge[MaxVnum][MaxVnum]; 
    VexType v[MaxVnum];
}FMGragh;

//寻找顶点在顶点表中的位置 
int Locatevex(FMGragh f,char a){
    int i;
    for(i=0;i < f.Vnum;i++){
        if(f.v[i] == a){
            return i;
        }
    }
    //cout << "无法找到对应顶点" << endl;
    return -1; 
}

//初始化有向图
void CreateF(FMGragh &f){
    //memset(f.Edge, inf, sizeof(f.Edge)); 这种写法是错误的 
    int i,j,y,z,g;
    char m,n;
    cout << "请输入图的顶点个数" << endl;
    cin >> f.Vnum;
    cout << "请输入图的边的条数" << endl;
    cin >> f.Enum;
    cout << "请输入图的顶点元素的值" << endl;
    for(i = 0;i < f.Vnum;i++){
        cin >> f.v[i]; 
    }
    for(i = 0;i < f.Vnum;i++){
        for(j = 0;j < f.Vnum;j++){
            f.Edge[i][j] = inf;    
        }
    }
    for(j = 0;j < f.Enum;j++){
    cout << "请输入边的邻接的两个顶点和权值(第一个元素为弧头,第二个元素为弧尾)" << endl;
    cin >> m >> n >> g;
    y = Locatevex(f,m);        //寻找m元素在顶点表中的位置,弧头
    z = Locatevex(f,n);        //寻找n元素在顶点表中的位置,弧尾
    if(y != -1 && z != -1){
        //a.Edge[y][z] = a.Edge[z][y] = 1;
        f.Edge[y][z] = g; 
    }else{
        cout << "无法找到对应顶点,请重新输入" << endl;
        j--;
    }     
    } 
} 

//打印有向图
void printF(FMGragh f){
    int i,j;
    for(i = 0;i < f.Vnum;i++){
        for(j = 0;j < f.Vnum;j++){
            cout << f.Edge[i][j] << "\t";
        }
        cout << endl;
    }
} 

//初始化dist和p数组
void Dijkstra(FMGragh f,int i){
    int n,j;
    int temp,m;//m记录下标 
    for(n = 0;n <f.Vnum;n++){
        dist[n] = f.Edge[i][n];
        flag[n] = false;
        if(dist[n] == inf){
            p[n] = -1;           //不可达就赋为-1 
        }else{
            p[n] = i;            //说明顶点n与源点i相邻,设置顶点i的前驱p[n] = i
        }
    }
    flag[i] = true;
    dist[i] = 0;
    for(n = 0;n < f.Vnum;n++){//寻找邻接节点权值最小的边
        temp = inf,m = i;
        for(j = 0;j < f.Vnum;j++){
            if(!flag[j] && temp > dist[j]){
                m = j;
                temp = dist[j]; 
            }
        }
        if(m==i) return ; //如果其他节点都已经被访问(!flag[j])或者本节点没有和其它节点相连(temp > dist[j])就返回 
        flag[m] = true;//将这个节点加入到源点集合中 
        for(j = 0;j < f.Vnum;j++){
            if(!flag[j] && f.Edge[m][j]<inf){//判断节点是否被遍历过和这个节点是否可达其他的节点 
                if(dist[j] > (dist[m]+f.Edge[m][j])){//判断由这个m节点到j节点的路径权值是否大于直接去j节点的路径权值 
                    p[j] = m;                    //不大于的话,就更新这个前驱数组 
                    dist[j] = dist[m]+f.Edge[m][j];    //更新到达这个节点的路径权值 
                }
            }
        }
    }
} 
//回溯
void findpath(FMGragh f,int i){
    int m = i; 
    cout << "路径长度为:" << dist[i] << endl;
    cout << "路径为:"; 
    cout << f.v[m] << "\t";
    while(true){
        m = p[m];
        cout << f.v[m] << "\t";
        if(m == 0){
            return;
        }
    }
} 

int main(){
    char i,m;
    int n;
    FMGragh f;
    CreateF(f);
    printF(f);
    cout << "请输入源点:";
    cin >> i;
    n = Locatevex(f,i);
    Dijkstra(f,n);
    cout << "请输入你想要去的节点:";
    cin >> m;
    n = Locatevex(f,m);
    findpath(f,n);
    return 0;
}

功能代码详解:

void Dijkstra(FMGragh f,int i){
    int n,j;
    int temp,m;//m记录下标 
    for(n = 0;n <f.Vnum;n++){
        dist[n] = f.Edge[i][n];
        flag[n] = false;
        if(dist[n] == inf){
            p[n] = -1;           //不可达就赋为-1 
        }else{
            p[n] = i;            //说明顶点n与源点i相邻,设置顶点i的前驱p[n] = i
        }
    }
    flag[i] = true;
    dist[i] = 0;
    for(n = 0;n < f.Vnum;n++){//寻找邻接节点权值最小的边
        temp = inf,m = i;
        for(j = 0;j < f.Vnum;j++){
            if(!flag[j] && temp > dist[j]){
                m = j;
                temp = dist[j]; 
            }
        }
        if(m==i) return ; //如果其他节点都已经被访问(!flag[j])或者本节点没有和其它节点相连(temp > dist[j])就返回 
        flag[m] = true;//将这个节点加入到源点集合中 
        for(j = 0;j < f.Vnum;j++){
            if(!flag[j] && f.Edge[m][j]<inf){//判断节点是否被遍历过和这个节点是否可达其他的节点 
                if(dist[j] > (dist[m]+f.Edge[m][j])){//判断由这个m节点到j节点的路径权值是否大于直接去j节点的路径权值 
                    p[j] = m;                    //不大于的话,就更新这个前驱数组 
                    dist[j] = dist[m]+f.Edge[m][j];    //更新到达这个节点的路径权值 
                }
            }
        }
    }
} 

1,首先初始化p,dist和flag数组。

2,在邻接节点中寻找权值最小并且与之相关联的边。

temp = inf,m = i;//用m来存储最小邻接节点的下标
for(j = 0;j < f.Vnum;j++){
if(!flag[j] && temp > dist[j]){
m = j;
temp = dist[j]; 
}
}

3,如果其他节点都已经被访问(!flag[j])或者本节点没有和其它节点相连(temp > dist[j])就返回

if(m==i) return;

 4,找到后将这个邻接节点加入S集合

flag[m] = true;

5,借助新加进来的节点,更新源节点去往其他节点的路径

for(j = 0;j < f.Vnum;j++){
    if(!flag[j] && f.Edge[m][j]<inf){//判断节点是否被遍历过和这个节点是否可达其他的节点 
    if(dist[j] > (dist[m]+f.Edge[m][j])){//判断由这个m节点到j节点的路径权值是否大于直接去j节点的路径权值 
    p[j] = m;                    //不大于的话,就更新这个前驱数组 
    dist[j] = dist[m]+f.Edge[m][j];    //更新到达这个节点的路径权值 
    }
            }
        }

注意这两个判断语句:

if(!flag[j] && f.Edge[m][j]

if(dist[j] > (dist[m]+f.Edge[m][j])){//判断由这个m节点到j节点的路径权值是否大于直接去j节点的路径权值,如下图借助B到达C,明显比直接到达C的权值要小,所以更新。

运行示例:

a b 2
a c 5
b c 2
b d 6
c d 7
d c 2
d e 4
c e 1

 

10.2,Floyd算法完整代码如下:

//邻接矩阵存储网(有向图带权) 
#include<iostream>
#include <string.h>
using namespace std;

#define MaxVnum 100      //顶点数最大值
typedef char VexType;   //顶点的数据类型,根据需要定义
typedef int EdgeType;   //边上权值的数据类型,若不带权值的图,则为0或1
const int inf = 1e7;    //定义一个很大的树表示点之间不可达 
int dist[MaxVnum][MaxVnum];
int p[MaxVnum][MaxVnum];
//bool flag[MaxVnum];不需要标记节点是否被访问 

typedef struct FMGragh{
    int Enum,Vnum;        //存储顶点的个数和边的个数
    EdgeType Edge[MaxVnum][MaxVnum]; 
    VexType v[MaxVnum];
}FMGragh;

//寻找顶点在顶点表中的位置 
int Locatevex(FMGragh f,char a){
    int i;
    for(i=0;i < f.Vnum;i++){
        if(f.v[i] == a){
            return i;
        }
    }
    //cout << "无法找到对应顶点" << endl;
    return -1; 
}

//初始化有向图
void CreateF(FMGragh &f){
    int i,j,y,z,g;
    char m,n;
    cout << "请输入图的顶点个数" << endl;
    cin >> f.Vnum;
    cout << "请输入图的边的条数" << endl;
    cin >> f.Enum;
    cout << "请输入图的顶点元素的值" << endl;
    for(i = 0;i < f.Vnum;i++){
        cin >> f.v[i]; 
    }
    for(i = 0;i < f.Vnum;i++){
        for(j = 0;j < f.Vnum;j++){
            f.Edge[i][j] = inf;
            if(i == j){
            f.Edge[i][j] = 0;    
            }    
        }
    }
    for(j = 0;j < f.Enum;j++){
    cout << "请输入边的邻接的两个顶点和权值(第一个元素为弧头,第二个元素为弧尾)" << endl;
    cin >> m >> n >> g;
    y = Locatevex(f,m);        //寻找m元素在顶点表中的位置,弧头
    z = Locatevex(f,n);        //寻找n元素在顶点表中的位置,弧尾
    if(y != -1 && z != -1){
        //a.Edge[y][z] = a.Edge[z][y] = 1;
        f.Edge[y][z] = g; 
    }else{
        cout << "无法找到对应顶点,请重新输入" << endl;
        j--;
    }     
    } 
} 

//打印有向图
void printF(FMGragh f){
    int i,j;
    for(i = 0;i < f.Vnum;i++){
        for(j = 0;j < f.Vnum;j++){
            cout << f.Edge[i][j] << "\t";
        }
        cout << endl;
    }
} 

void Floyd(FMGragh f){
    int i,j,n;
    for(i = 0;i < f.Vnum;i++){            //初始化dist数组和p数组 
        for(j = 0;j < f.Vnum;j++){
            dist[i][j] = f.Edge[i][j];  //将数组Edge复制到dist中 
            if(f.Edge[i][j] == inf && i!=j){
                p[i][j] = -1;            //如果节点之间不可达,那么p[i][j] = -1
            }else{
                p[i][j] = i;            //如果节点之间可达,将i到j的前驱记为i,比如说2到3的前驱就是2 
            }
        }
    }
    for(i = 0;i < f.Vnum;i++){            //遍历每一个要借的点 
        for(j = 0;j < f.Vnum;j++){        //双层循环,遍历矩阵中的每一个元素 
            for(n = 0;n < f.Vnum;n++){    
                if(dist[j][n] > dist[j][i]+dist[i][n]){
                    dist[j][n] = dist[j][i]+dist[i][n];
                    p[j][n] = p[i][n];    //改变前驱节点 
                }        
            }
        }
    } 
    
} 
void print(FMGragh f)
{
    int i,j;
    for(i=0;i<f.Vnum;i++)//输出最短距离数组
    {
        for(j=0;j<f.Vnum;j++)
            cout<<dist[i][j]<<"\t";
        cout<<endl;
    }
    cout<<endl;
    for(i=0;i<f.Vnum;i++)//输出前驱数组
    {
        for(j=0;j<f.Vnum;j++)
            cout<<p[i][j]<<"\t";
        cout<<endl;
    }
}

void findpath(FMGragh f,int a,int b){ //一个是源点,一个是终点 
    int m;
    m = b;
    while(true){
        m = p[a][m];
        cout << f.v[m] << "\t";
        if(m == a){
            return;
        }    
    }
} 

int main(){
    int a,b;
    char c,d; 
    FMGragh f;
    CreateF(f);
    //printF(f);
    Floyd(f);
    print(f);
    cout << "请输入源点和终点:";
    cin >> c >> d;
    a = Locatevex(f,c);    
    b = Locatevex(f,d);
    cout << "最小路径(逆序):" << d << "\t"; 
    findpath(f,a,b);    
    return 0;
}

运行实例

 11,拓扑排序

1,拓扑排序

两个特点:无环,有向

在我们的程序中是不会删除顶点和出发边的,我们只会让入度减一,比如说C0和C1,当C0被访问之后,C1的入度就会减一。

程序中,入度减一

算法步骤:

邻接表存储:

但是邻接表存储,找出度容易找入度难

所以我们使用逆邻接表。

逆邻接表:其实我们改变的是图中的箭头,只要让图中的箭头反向就好。

 11.1拓扑排序完整代码如下:

测试图:

完整代码如下:

//拓扑排序 
#include<iostream>
#include<stack>
using namespace std;

#define MaxVnum 100
typedef int NodeType;

typedef struct LNode{    //定义邻接节点数据结构 
    struct LNode *next;    //用于指向下一个邻接节点 
    NodeType Ndata;        //节点中的值 
}LNode,*ListNode; 

typedef struct LVex{    //定义顶点数据结构 
    ListNode first;     //用于指向第一个邻接节点 
    NodeType Vdata;     //存储顶点信息 
}LVex,*ListVex;

typedef struct Vexs{    //用于保存图的简要信息的数据结构 
    LVex lv[MaxVnum];   //用于线性保存顶点信息 
    LVex Relv[MaxVnum]; //逆邻接表 
    int Enum,Vnum;      //用于保存边数和顶点数 
}Vexs; 

int du[MaxVnum];        //存储节点的入度 
int topu[MaxVnum];      //存储拓扑序列 

//最好把方法先声明,然后再调用 
void Insertedge(Vexs &v);
int LocateVex(Vexs v,NodeType a);
void CreateFG(Vexs &v);
void printFG(Vexs v);
void GetDu(Vexs v); 
void topuAOV(Vexs v);

int main(){
    Vexs v;
    CreateFG(v);
    printFG(v);
    GetDu(v);
    topuAOV(v);
    return 0;
}

void topuAOV(Vexs v){
    int m,n,i,k,x;
    ListNode p;
    stack<int> S;      //初始化一个栈S,需要引入头文件#include<stack>
    for(m = 0;m < v.Vnum;m++){
        if(du[m] == 0){
            S.push(m); //度为0的元素入栈,但是入栈的是元素的下标 
        }
    }
    n = 0;
    while(!S.empty()){
        i = S.top();//栈顶元素复制 
        S.pop();    //出栈 
        //cout << endl << i << "\t";
        topu[n] = i;//为拓扑排序赋值    
        n++;
        p = v.lv[i].first;    //注意,我们在这里使用邻接表遍历,而不是逆邻接表,否则会出错 
        while(p){
            k = p->Ndata; //遍历邻接节点
            x = LocateVex(v,k); //找到这个邻接节点的位置
            
            du[x]--;    //该顶点的入度减一 
            if(!du[x]){ //如果该节点的度为0,就入栈 
                
                S.push(x); 
            }
            p = p->next;//指针下移 
        }
    } 
    if(n < v.Vnum){
        cout << "该图有回路";
        return;
    } 
    cout << "该图的拓扑排序为:";
    for(m = 0;m < v.Vnum;m++){
        cout << v.lv[topu[m]].Vdata << "\t";
    }
}

void GetDu(Vexs v){
    int i;
    ListNode p;            //指向邻接点元素 
    for(i = 0;i < v.Vnum;i++){//循环遍历各个顶点 
        p = v.Relv[i].first;    //让p指针指向元素的邻接节点,注意从逆邻接表中读出入度 
        while(p){
            du[i]++;        //du数组从0开始存储 
            p = p->next;
        }
    }
    for(i= 0;i < v.Vnum;i++){
        cout << "du[" << i << "]" << du[i] << endl;
    } 
}

//插入边 
void Insertedge(Vexs &v){
    int i,j,z;
    NodeType m,n;
    ListNode s;
    for(z = 0;z < v.Enum;z++){
        cout << "请输入邻接的顶点" << endl;
        cin >> m >> n;
        i = LocateVex(v,m);    //弧头节点的位置 
        j = LocateVex(v,n);    //弧尾节点的位置 
        cout << "弧头" << i << "\t" << "弧尾" << j << endl; 
        if(i != -1 && j != -1){
            s = new LNode;
            s->Ndata = n;
            s->next = NULL;
            s->next = v.lv[i].first;
            v.lv[i].first = s; //注意这里first指针已经指向第一个邻接节点,而不是它本身 
            s = new LNode;       //重复一遍上面的动作只不过顺序反了,我们在这里插入弧头 
            s->Ndata = m;
            s->next = NULL;
            s->next = v.Relv[j].first;
            v.Relv[j].first = s; 
            cout << "头插法插入" << v.lv[i].first->Ndata << endl; 
        }else{
            cout << "顶点不存在,请重新输入" << endl;
            z--; 
        }
    } 
    
}

//用于寻找该字符在顶点线性表中的位置 
int LocateVex(Vexs v,NodeType a){
    int i;
    for(i = 0;i < v.Vnum;i++){
        if(v.lv[i].Vdata == a){
            return i;
        }
    }
    return -1;
} 
//创建邻接表保存有向图
void CreateFG(Vexs &v){
    int i;
    cout << "请输入有向图的顶点的个数" << endl;
    cin >> v.Vnum;
    cout << "请输入有向图的边的条数" << endl;
    cin >> v.Enum;
    cout << "请输入有向图的顶点值" << endl;
    for(i = 0;i < v.Vnum;i++){
        cin >> v.lv[i].Vdata;
        v.Relv[i].Vdata = v.lv[i].Vdata;
        v.lv[i].first = NULL;
        v.Relv[i].first = NULL;
    }
    for(i = 0;i < v.Vnum;i++){//打印输入的顶点 
        cout << v.lv[i].Vdata << "\t";
    }
    cout << endl; 
    Insertedge(v);
} 

//打印有向图
void printFG(Vexs v){
    int i,j;
    ListNode p,q;
    cout <<"邻接表如下:" << endl;
    for(i = 0;i < v.Vnum;i++){
        //p = v.lv[i].first->next;千万注意这里不可以这么写 
        p = v.lv[i].first;
        cout << "[" << v.lv[i].Vdata << "]:"; 
        while(p){
            cout << p->Ndata << "\t";
            p = p->next;
        }
        cout << endl;
    }
    cout <<"逆邻接表如下:" << endl;
    for(i = 0;i < v.Vnum;i++){
        q = v.Relv[i].first;
        cout << "[" << v.Relv[i].Vdata << "]:"; 
        while(q){
            cout << q->Ndata << "\t";
            q = q->next;
        }
        cout << endl;
    }
} 

算法解析:

求每个结点的入度,注意如下代码中我们需要在逆邻接表中得到每个节点的入度

void GetDu(Vexs v){
    int i;
    ListNode p;            //指向邻接点元素 
    for(i = 0;i < v.Vnum;i++){//循环遍历各个顶点 
        p = v.Relv[i].first;    //让p指针指向元素的邻接节点,注意从逆邻接表中读出入度 
        while(p){
            du[i]++;        //du数组从0开始存储 
            p = p->next;
        }
    }
    for(i= 0;i < v.Vnum;i++){
        cout << "du[" << i << "]" << du[i] << endl;
    } 
}

 获取拓扑排序

void topuAOV(Vexs v){
    int m,n,i,k,x;
    ListNode p;
    stack<int> S;      //初始化一个栈S,需要引入头文件#include<stack>
    for(m = 0;m < v.Vnum;m++){
        if(du[m] == 0){
            S.push(m); //度为0的元素入栈,但是入栈的是元素的下标 
        }
    }
    n = 0;
    while(!S.empty()){
        i = S.top();//栈顶元素复制 
        S.pop();    //出栈 
        //cout << endl << i << "\t";
        topu[n] = i;//为拓扑排序赋值    
        n++;
        p = v.lv[i].first;    //注意,我们在这里使用邻接表遍历,而不是逆邻接表,否则会出错 
        while(p){
            k = p->Ndata; //遍历邻接节点
            x = LocateVex(v,k); //找到这个邻接节点的位置
            du[x]--;    //该顶点的入度减一 
            if(!du[x]){ //如果该节点的度为0,就入栈 
                
                S.push(x); 
            }
            p = p->next;//指针下移 
        }
    } 
    if(n < v.Vnum){
        cout << "该图有回路";
        return;
    } 
    cout << "该图的拓扑排序为:";
    for(m = 0;m < v.Vnum;m++){
        cout << v.lv[topu[m]].Vdata << "\t";
    }
}

求出拓扑排序的过程是先来后服务的,所以我们使用栈。

首先,我们将入度为0的节点,全部压入栈中

for(m = 0;m < v.Vnum;m++){
        if(du[m] == 0){
            S.push(m); //度为0的元素入栈,但是入栈的是元素的下标 
        }
    }

使用n作为计数器,在topu数组存储的时候作为下标

加入判断,当栈不为空的时候,执行循环

循环的代码如下:

First pop the top element of the stack and record this element, but this element is the subscript of the vertex array.

Save this subscript to the topu array.

Use the p pointer to start traversing the adjacency list. Note that this is an adjacency list, not an inverse adjacency list.

First we need to give the in-degree of these adjacent nodes -1, because before this, we popped the node with an in-degree of 0.

Then we check whether the in-degree of these adjacent nodes is equal to 0. If so, directly push the adjacent node onto the stack.

Then the pointer moves down.

i = S.top();//栈顶元素复制 
        S.pop();    //出栈 
        //cout << endl << i << "\t";
        topu[n] = i;//为拓扑排序赋值    
        n++;
        p = v.lv[i].first;    //注意,我们在这里使用邻接表遍历,而不是逆邻接表,否则会出错 
        while(p){
            k = p->Ndata; //遍历邻接节点
            x = LocateVex(v,k); //找到这个邻接节点的位置
            du[x]--;    //该顶点的入度减一 
            if(!du[x]){ //如果该节点的度为0,就入栈 
                
                S.push(x); 
            }
            p = p->next;//指针下移 
        }

Input example:

1 2
1 3
1 4
3 2
3 5
4 5
6 5
6 4

Run the example:

Notice:

1. The order of topological sorting is not unique, which is related to your input order.

2. The adjacency list is used to find the in-degree of each node, and the adjacency list is used to traverse each node.

12. Search and half search 

1. Type of search

A modified search is a dynamic search, and vice versa is a static search.

The algorithm time complexity of binary search tree is O(logn) in the good case, but in the worst case, the algorithm time complexity will degenerate to O(n). Balanced binary search trees can solve the above problems very well. Balanced binary search trees are divided into: avl, treep, splay, red-black tree, SBT.

A hash lookup is actually a hash table.

1.1, sequential search:

Optimization algorithm for sequential search:

However, the algorithm time complexity has not been reduced: it is still O(n)

1.2, search by half

Non-recursive binary search:

Recursive half search:

Note that parameters and end conditions

Analysis of Algorithms:

We assume that it is executed x times

We can also calculate the time complexity of the algorithm by drawing a recursion tree:

12.1, half search 

#include<iostream>
#include<cstdlib> //排序sort函数需要该头文件
#include<algorithm>
using namespace std;
const int M=100;
int x,n,i;
int s[M];

int BinarySearch(int s[],int n,int x)//二分查找非递归算法
{
   int low=0,high=n-1;  //low指向有序数组的第一个元素,high指向有序数组的最后一个元素
   while(low<=high)
   {
       int middle=(low+high)/2;  //middle为查找范围的中间值
       if(x==s[middle])  //x等于查找范围的中间值,算法结束
          return middle;
       else if(x>s[middle]) //x大于查找范围的中间元素,则从左半部分查找
              low=middle+1;
            else            //x小于查找范围的中间元素,则从右半部分查找
              high=middle-1;
    }
    return -1;
}

int recursionBS (int s[],int x,int low,int high) //二分查找递归算法
{
    //low指向数组的第一个元素,high指向数组的最后一个元素
    if(low>high)              //递归结束条件
        return -1;
    int middle=(low+high)/2;  //计算middle值(查找范围的中间值)
    if(x==s[middle])          //x等于s[middle],查找成功,算法结束
        return middle;
    else if(x<s[middle])      //x小于s[middle],则从前半部分查找
             return recursionBS (s,x,low,middle-1);
           else               //x大于s[middle],则从后半部分查找
             return recursionBS (s,x,middle+1,high);
}

int main()
{
    cout<<"该数列中的元素个数n为:";
    cin>>n;
    cout<<"请依次输入数列中的元素:";
    for(i=0;i<n;i++)
       cin>>s[i];
    sort(s,s+n); //二分查找的序列必须是有序的,如果无序需要先排序
    cout<<"排序后的数组为:";
    for(i=0;i<n;i++)
    {
       cout<<s[i]<<" ";
    }
    cout<<endl;
    cout<<"请输入要查找的元素:";
    cin>>x;
    //i=BinarySearch(s,n,x);
    i=recursionBS(s,x,0,n-1);
    if(i==-1)
      cout<<"该数列中没有要查找的元素"<<endl;
    else
      cout<<"要查找的元素在第"<<i+1<<"位"<<endl;//位序和下标差1
    return 0;
}

12.2, Sequential search (optimized version)

#include <iostream>

using namespace std;
#define Maxsize 100

int SqSearch(int r[],int n,int x)//顺序查找
{
    for(int i=0;i<n;i++) //要判断i是否超过范围n
        if(r[i]==x) //r[i]和x比较
            return i;//返回下标
    return -1;
}

int SqSearch2(int r2[],int n,int x)//顺序查找优化算法
{
    int i;
    r2[0]=x;//待查找元素放入r[0],作为监视哨
    for(i=n;r2[i]!=x;i--);//不需要判断i是否超过范围
    return i;
}

int main()
{
    int i,n,x,r[Maxsize],r2[Maxsize+1];
    cout<<"请输入元素个数n为:"<<endl;
    cin>>n;
    cout<<"请依次n个元素:"<<endl;
    for(int i=0;i<n;i++)
    {
        cin>>r[i];
        r2[i+1]=r[i];//r2[]数组0空间未用,做监视哨
    }
    cout<<endl;
    cout<<"请输入要查找的元素:";
    cin>>x;
    //i=SqSearch(r,n,x);
//    if(i==-1)
//      cout<<"该数列中没有要查找的元素"<<endl;
//    else
//      cout<<"要查找的元素在第"<<i+1<<"位"<<endl;
    i=SqSearch2(r2,n,x);
    if(i==0)
      cout<<"该数列中没有要查找的元素"<<endl;
    else
      cout<<"要查找的元素在第"<<i<<"位"<<endl;
    return 0;
}

Guess you like

Origin blog.csdn.net/weixin_64972949/article/details/132946313