基础数据结构与算法总结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,判断桥算法步骤

首先对图进行深度优先搜索,并且对每个节点的dfn和low值进行标记

当搜索到4节点的时候,发现没有邻接点没有被访问了,这时我们也发现,4节点最早可以回退到1节点,这时我们开始更新low=1,并且开始回退,回退的路径上的节点的low值也要更新为1,当回退到5节点的时候,发现,还有7节点没有被访问,便会访问7节点,low=7,然后又开始回退。

这里,7节点的low值不可以更新为1,因为由7这条路径无法回退到1节点

桥的判断法则:当孩子节点的low值比父亲节点的dfn值大,那么孩子和父亲节点所构成的边就是桥,比如说上图的5和7节点所构成的边就是桥。

割点的判断法则:

但是如果这个节点恰巧是根节点,那么这个时候,条件要再加一条,就是说,要有两个孩子节点的low值大于本节点的dnf,才可以说这个根节点是一个割点。

3.1有向图的强连通分量

9.1, 具体代码如下:

1,tarjan算法求桥

如果你觉得这里的数据结构看不懂的话,那么请去看前面的链式前向星来补充一下这方面的知识。

#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算法求割点

#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,最短路径算法(Dijkstra算法和Floyd算法)

1,Dijkstra算法

算法步骤:

图使用邻接矩阵存储

S为源点集合,V-S是源点之外的其他顶点,dist数组用来保存这个源点到各个顶点的距离,dist数组会随着V-S集合中的顶点加入S集合而不断的更新,p数组用来保存去往下个顶点的前驱顶点。

寻找当前最短路径

选取好顶点之后加入S集合

借助当前加入S集合的顶点,更新源点去往其他节点的值,但是要满足如下条件。dist[j] > G.Edge[t][j]+dist[t]。

重复以上步骤:

算法分析:

2,Floyd算法

算法步骤:

如下图:

首先我们借0点,但是只有是这个节点的入度才可以借,比如说2到1原来是5,但是我们借0之后,2->0->1,权值为4,所以我们改dist[2][1] = 4,当然前驱节点也要跟着改变p[2][1] =0,另一条边也是同理。

借完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数组存储的时候作为下标

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

循环的代码如下:

首先出栈栈顶元素,并且且记录此元素,但是这个元素是顶点数组的下标。

将这个下标保存到topu数组中。

使用p指针开始遍历邻接表,注意这里是邻接表,而不是逆邻接表。

首先我们要给这些邻接节点的入度-1,因为在这之前,我们出栈了入度为0的节点。

然后我们再去检查这些邻接节点的入度是否有等于0的,如果有,则直接将该邻接节点压入栈中。

然后指针下移。

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;//指针下移 
        }

输入示例:

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

运行示例:

注意:

1,拓扑排序的顺序是不唯一的,这和你的输入顺序有关系

2,邻接表用于求每个结点的入度,而邻接表用于遍历每个节点。

12,查找和折半查找 

1,查找的类型

有修改的查找是动态查找,反之就是静态查找

二叉查找树在好的的情况下算法时间复杂度是O(logn),但是在最坏的情况下,算法时间复杂度会退化到O(n)。平衡二叉查找树可以很好得解决以上的问题。平衡二叉查找树分为:avl,treep,splay,红黑树,SBT。

散列查找其实就是哈希表。

1.1,顺序查找:

顺序查找的优化算法:

但是算法时间复杂度还是没有减少:仍然是O(n)

1.2,折半查找

非递归折半查找:

递归折半查找:

注意,参数和结束条件

算法分析:

我们假设执行了x次

我们也可以通过画递归树去算出算法的时间复杂度:

12.1,折半查找 

#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,顺序查找(优化版)

#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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值