第三周学习总结


提示:以下是本篇文章正文内容,下面案例可供参考

一、树和二叉树

树( 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( 广度优先搜索)

数据结构
DFSstack( 栈 )不具最短性
BFSqueue( 队列 )可求"最短路"

下面看一道题,分别由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;
}	

运行结果:
在这里插入图片描述


总结

以上就是今天总结的内容,关于这些知识的学习,都只是基础的层面,还需要在往后的学习中更加透彻的去理解拓展,多加的运用来掌握。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值