提示:以下是本篇文章正文内容,下面案例可供参考
一、树和二叉树
树( Tree )是n( n ≥ 0 )个节点的有限集
- 结点 :包含一个数据元素及若干个指向其它结点的分支信息
- 结点的度 :一个结点的子树个数称为此结点的度
- 叶子结点 :度为0的结点,即无后继的结点,也称为终端结点
- 子结点 :一个结点含有的子树的根结点称为该结点的子结点
- 父结点 :若一个结点含有子结点,则这个结点称为其子结点的父结点
- 结点的层次 :从根结点开始定义,根结点的层次为1,根的直接后继的层次为2,以此类推
- 树的高度( 深度 ) :树中所有结点的层次的最大值
二叉树( Binary Tree )是n( n ≥ 0 )个结点所构成的集合
关于二叉树的性质此处不做详细介绍在《数据结构》C语言版|第二版可见,有详细证明
树与二叉树的区别:
树 | 二叉树 | |
---|---|---|
性质 | 是一种数据结构 | 每个结点最多有两个子树 的一种树结构 |
结点 | 每个结点有0个或多个子结点,没有父结点的结点称为根结点,每个非根结点有且只有一个父结点 | 每个结点最多有两个子树 |
种类 | 包括无序树、有序树、二叉树和霍夫曼树等 | 包括完全二叉树、满二叉树和平衡二叉树 |
二、递归
- 含义:函数调用本身( 自己调用自己)
- 构成递归的条件:①能将一个问题转换成一个更小的问题,且新问题与原问题解法相同②必须有递归出口( 否则会造成死循环)
- 递归函数的底层是由栈实现的,是系统帮我们写好的,可以直接使用
- 递归 = 递推 + 回溯
下面看一个例子:
汉诺塔问题:假设有3个分别命名为A、B和C的塔座,在塔座A上插有n个直径大小各不相同,依小到大编号为1,2,…,n的圆盘。现要求 A将塔座A上的n个圆盘移至塔座C上,并仍按同样顺序叠排,圆盘移动时必须遵循下列规则:
(1)每次只能移动一个圆盘;
(2)圆盘可以插在A、B和C中的任一塔座上;
(3)任何时刻都不能将一个较大的圆盘压在较
小的圆盘之上。
可将过程分为4步,如图 ( 图仅为示例 ):
代码如下(示例):
#include<iostream>
using namespace std;
int m=0;
void move(char A,int n,char C)//将编号为n的圆盘从A移动到C
{
cout<<"次数"<<++m<<":"<<n<<" "<<A<<"-->"<<C<<endl;
}
void han(int n,char A,char B,char C)//将A上的n个圆盘经过B移动到C
{
if(n==1) move(A,1,C);//递归出口
else
{
han(n-1,A,C,B);
move(A,n,C);
han(n-1,B,A,C);
}
}
int main()
{
han(3,'A','B','C');
return 0;
}
运行结果:
三、排序
1.快速排序
①手写快排
主要思想:分治
步骤:
1.确定分界点可以为a[l],a[(l+r)/2],a[r]或是随机
2.调整范围,使得第一个区间中的数小于等于x,第二个区间的值大于等于x
3.递归处理左右两段
例:
代码如下(示例):
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N];
int n;
void qsort(int l,int r)
{
if(l>=r) return;//当区间不存在时直接返回
int i=l-1,j=r+1;//由于在后边的while循环中i,j分别先做了i++,j--,所以将i,j分别定义为l-1,r+1
int mid=a[(l+r)/2];//确定分界点
while(i<j)
{
do i++;while(a[i]<mid);
do j--;while(a[j]>mid);
if(i<j) swap(a[i],a[j]);
}
qsort(l,j);//当递归范围为到j时mid的值不可定义为r,否则会有边界问题,陷入死循环
qsort(j+1,r);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
qsort(0,n-1);
for(int i=0;i<n;i++) cout<<a[i]<<" ";
return 0;
}
运行结果:
②sort快排(可以直接调用c++写好的sort函数即可)
sort函数的使用方法
- 使用sort函数需要头文件#include< algorihim>
- 模板:sort ( begin , end , cmp )
{ 参数begin:要排序的数组的起始地址
参数end:结束的地址( 最后一位要排序的地址的下一个地址)
参数cmp:排序的方法,可以从大到小,也可以从小到大,当不写该参数时,默认从小到大的排序方式}
代码如下(示例):
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int a[10]={2,3,1,4,5};
sort(a,a+5);
for(int i=0;i<5;i++)
cout<<a[i]<<" ";
return 0;//运行结果:1 2 3 4 5
}
代码如下(示例):
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int a,int b)
{
return a>b;//如果为a<b即为升序,如果为a>b即为降序
}
int main()
{
int a[10]={2,3,1,4,5};
sort(a,a+5,cmp);
for(int i=0;i<5;i++)
cout<<a[i]<<" ";
return 0;//运行结果:5 4 3 2 1
}
sort对字符的排序
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
string a="hello";
sort(a.begin(),a.end());
cout<<a;
return 0;//运行结果:ehllo
}
sort对字符串的排序
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
string a[10];
for(int i=0;i<3;i++)
cin>>a[i];
sort(a,a+3);
for(int i=0;i<3;i++)
cout<<a[i]<<endl;
return 0;
}
运行结果:
sort对结构体的排序
例题:
调查学校组每个同学的生日,并按照从大到小的顺序排序。
输入:有2行,第1行为每组总人数n,第2行至第n+1行分别是每人的姓名s、出生年y、月m、日d。
输出:有n行,即n个生日从大到小同学的姓名。(如果有两个同学生日相同,输入靠后的同学先输出)
代码如下(示例):
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int n,i;
struct stu{
string s;
int y,m,d;
int shu;
}a[N];//定义结构体来存储每个人的信息
bool cmp(stu a,stu b)
{
if(a.y!=b.y) return a.y<b.y;
if(a.m!=b.m) return a.m<b.m;
if(a.d!=b.d) return a.d<b.d;
return a.shu>b.shu; //当年月日都相等时,输入靠后的同学先输出
}
int main()
{
cin>>n;
for(i=0;i<n;i++)
{
cin>>a[i].s>>a[i].y>>a[i].m>>a[i].d;
a[i].shu=i+1;//记录输入学生的顺序
}
sort(a,a+n,cmp);//用结构体时使用sort必须用cmp
for(i=0;i<n;i++)
cout<<a[i].s<<endl;
return 0;
}
运行结果:
2.归并排序
其主要思想也为分治
步骤:
1.确定分界点mid=(l+r)/2,该点与快排不同的是,归并排序分界点为整个数组的中间位置,即下标的中间值,而快排分界点是数组中随机的一个值
2.递归排序左右区间
3.归并区间,合二为一
题目同为手写快排的题目
代码如下(示例):
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N];
int tmp[N];//辅助数组,该数组存放归并后的结果
int n;
void megesort(int l,int r)
{
if(l>=r) return;
int mid=(l+r)/2;//分界点
megesort(l,mid);megesort(mid+1,r);//递归排序两个区间
int k=0,i=l,j=mid+1;//k表示当前tmp数组里有多少数
while(i<=mid&&j<=r)
{
if(a[i]<=a[j]) tmp[k++]=a[i++];
else tmp[k++]=a[j++];
}
while(i<=mid) tmp[k++]=a[i++];//如果左半边没有循环完,则将左边剩下数的接到后边
while(j<=r) tmp[k++]=a[j++];//同上
for(i=l,j=0;i<=r;i++,j++) a[i]=tmp[j];//最后把结果从tmp里面赋值回a中
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
megesort(0,n-1);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
return 0;
}
运行结果:
四、DFS( 深度优先搜索 )、BFS( 广度优先搜索)
数据结构 | ||
---|---|---|
DFS | stack( 栈 ) | 不具最短性 |
BFS | queue( 队列 ) | 可求"最短路" |
下面看一道题,分别由BFS算法和DFS算法实现:
1.DFS算法
DFS的搜索方式
深度优先搜索遍历类似于树的先序遍历,是树的先序遍历的推广。即向深处搜索,直到搜不到之后开始回溯,这样经过重复进而完成搜索,属于盲目搜索。
上述例题代码如下(示例):
#include<iostream>
#include<cstring>
using namespace std;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
const int N=30;
char g[N][N];//用于存图
int st[N][N];//标记某点是否被搜过
int m,n;//n行m列
int res;
void dfs(int x,int y)
{
st[x][y]=1;//标记被遍历
for(int i=0;i<4;i++)//遍历四个方向
{
int a=x+dx[i],b=y+dy[i];//邻接点坐标
if(a<0||a>=n||b<0||b>=m) continue;//出界
if(st[a][b]==1) continue;//已经被搜过
if(g[a][b]!='.') continue;//障碍
res++;
dfs(a,b);
}
}
int main()
{
while(cin>>m>>n)
{
if(m==0&&n==0) break;
res=1;
memset(st,0,sizeof st);//memset(数组名,值,sizeof 数组名)用来初始化数组st,此处即初始化为0.需要用到头文件<cstring>
for(int i=0;i<n;i++) cin>>g[i];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='@')
{
dfs(i,j);
break;
}
cout<<res<<endl;
}
return 0;
}
运行结果
//输入:
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0
//输出:
45
此外DFS可进行全排列的操作
下面看一道题根据题来分析
代码如下(示例):
#include<iostream>
using namespace std;
const int N=10;
int a[N];//a数组每次只存一个全排列
int st[N];//记录是否被搜过,st[i]=1代表i被搜过了
int n;
void dfs(int u)//代表当前填的是哪个位置
{
if(u==n)//已排完一个序列
{
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
return;
}
for(int i=1;i<=n;i++)
if(st[i]==0)//i未被用过
{
a[u]=i;
st[i]=1;
dfs(u+1);//递归到下一个位置
st[i]=0;//恢复现场
}
}
int main()
{
cin>>n;
dfs(0);//从位置0开始填
return 0;
}
运行结果:
2.BFS算法
BFS算法的搜索方式
广度优先搜索遍历类似于树的按层次遍历的过程。即一层一层的搜索,可以同时看很多条路,直到把一层中全部搜索完后再扩展到下一层,被称为暴力搜索。
上述例题代码如下(示例):
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
const int N=30;
char g[N][N];//存图
int st[N][N];//标记是否被搜过
int m,n,res;
void bfs(int x,int y)
{
queue<PII> q;
q.push({x,y});
while(q.size()>0)//队列不空
{
PII t=q.front();//取出队头
q.pop();//出队
for(int i=0;i<4;i++)//遍历四个方向
{
int a=t.first+dx[i],b=t.second+dy[i];//队头邻接点坐标
if(a<0||a>n||b<0||b>m) continue;//出界
if(st[a][b]==1) continue;//已经被搜过
if(g[a][b]!='.') continue;//障碍
res++;
q.push({a,b});
st[a][b]=1;//标记(a,b)已被访问
}
}
}
int main()
{
while(cin>>m>>n)
{
if(m==0&&n==0) break;
res=1;
memset(st,0,sizeof st);
for(int i=0;i<n;i++) cin>>g[i];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='@')
{
bfs(i,j);
break;
}
cout<<res<<endl;
}
return 0;
}
//输入:
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0
//输出:
45
对于BFS可以求出最短路径问题
例题:
代码如下(示例):
#include<iostream>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
const int N=110;
int g[N][N];//存图
int dist[N][N];//dist[i][j]表示(0,0)到(i,j)的最短距离,即求dist[n-1][m-1]
int m,n;//n行m列
int bfs(int x,int y)
{
queue<PII> q;
q.push({x,y});//起点入队
while(q.size())//队列不空
{
PII t=q.front();//记录队头
q.pop();//队头出队
for(int i=0;i<4;i++)
{
int a=t.first+dx[i],b=t.second+dy[i];
if(a<0||a>=n||b<0||b>=m) continue;//出界
if(g[a][b]==1) continue;//障碍
if(dist[a][b]!=0) continue;//已经被搜过
dist[a][b]=dist[t.first][t.second]+1;
q.push({a,b});
}
}
return dist[n-1][m-1];
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>g[i][j];
cout<<bfs(0,0);
return 0;
}
运行结果: