目录
前言
以下算法和数据结构代码都可以在本人的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;
}