第四周周记(7.31-8.6)

(一)二叉树

1.二叉树的性质

二叉树的特点就是每个结点至多有两棵子树; 二叉树的子树有左右之分;  二叉树的存储和处理比一般树简单. 任何树都可以方便地转换为二叉树来存储和处理。 

 像上述的这个图形一样的我们就称为二叉树。

2.二叉树的存储结构

(1)动态二叉树

数据结构中一般这么定义二叉树:

struct Node {
    int value ;//节点的值,可以定义多个值
    node *lson ,*rosn;//指向左右子节点;
};

动态新建一个Node时,用new运算符动态申请一个节点。使用完毕后,应该用delete命令释放他,否则内存会泄露。动态二叉树的优点是不会浪费内存,缺点就是需要关心,一不小心就会出错。

(2)用静态数组存储二叉数

在竞赛算法中,为了编码简单,加快速度,一般用静态数组实现二叉树。下面定义一个大小为N的结构体数组。

struct Node {
    char value;
    int lson, rson;//左右孩子,竞赛时把lson rson简写为l,r;
}tree[N];

 3.二叉树的遍历

 这边我们讲的是深度优先遍历。

(1)先序遍历

按树根、左子树、右子树的顺序访问

 上面的图片中的二叉树按照先序遍历进行走的话,遍历的结果的是EBADCGFIH 先序遍历的第一个节点是根,接下来是他的伪代码:

void preorder (node *root){  
     cout << root ->value;  //输出
     preorder (root ->l);   //递归左子树
     preorder (root ->r);   //递归右子树
}

(2)中序遍历 

按照左子树、树根、右子树进行遍历。我们按照图一的树来走的话是ABCDEFHHI  我们可以发现,中序遍历的中间是我们的根节点E 在跟节点的左边是我们左子树的部分,在我们右边是我们的右子树部分 这就是中序遍历的一大优点。下面是他的伪代码:

void inorder (node *root){  
	inorder (root ->l);   //递归左子树
     cout << root ->value;  //输出
     inorder (root ->r);   //递归右子树
}

(3)后序遍历

按照左子树、右子树、树根进行遍历。按照图一走的话是ACDBFHIGE 根节点在最后 下面是他的伪代码:

void preorder(node* root) {
    preorder(root->l);   //递归左子树
    preorder(root->r);   //递归右子树
    cout << root->value;  //输出
}

注意:“中序遍历+先序遍历”,或者“中序遍历+后序遍历”,都能确定一棵树。 但是只有“先序遍历+后序遍历”,不能确定一棵树。例如下图,它们的先序遍历都是"1 2 3",后序遍历都是"3 2 1"。

 接下来我们看一个关于这三个遍历的例题:

Binary Tree Traversals(hdu1710)

 这个题目就是输入先序遍历和中序遍历 让我们输出后续遍历的结果 这个题目的思路就是 

1)先序遍历的第一个数是整棵树的根,例如样例中的“1”。再对照中序遍历,“1”左边的“4 7 2”都在根的左子树上,右边的“8 5 9 3 6”都在根的右子树上。得到下图最左子图。 (2)递归上述过程。

如下图所示:

 代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
int pre[N];//先序
int in[N];//中序
int post[N];//后序
int idx = 0;//标记数组下标的位置
struct node {
	int value;//节点的值
	node* left, * right;//左子树,右子树
	//初始化
	node(int v = 0, node* l = NULL, node* r = NULL) {
		value = v;
		left = l;
		right = r;
	}
};
void build(int left, int right, int& layer, node*& tree);
void postOrder(node* root);
int main() {
	int n;
	while (cin >> n){
		//读入数据
		//先序序列
		for (int i = 0; i < n; i++) {
			cin >> pre[i];
		}
		//中序序列
		for (int i = 0; i < n; i++) {
			cin >> in[i];
		}
		//建树
		node* root; //定义树根节点
		int t = 0;
		build(0, n - 1, t, root);
		idx = 0;//初始化idx的值
		postOrder(root);
		for (int i = 0; i < n - 1; i++) {
			cout << post[i] << " ";
		}
		cout << post[n - 1] << endl;
	}
	return 0;
}
//创建二叉树
void build(int left, int right, int& layer, node*& tree) {
	// 1.在先序遍历中找到根节点pre[layer - 1];
	int t = pre[layer];
	//2.在中序遍历中找到树根的位置pos,初始化为-1
	int pos = -1;
	for (int i = left; i <= right; i++) {
		if (in[i] == t) {//找到了树根
			pos = i;//记录树根的位置
			break;
		}
	}
	//如果没找到树根就直接结束
	if (pos == -1) {
		return;
	}
	//创建树根节点
	tree = new node(t);
	layer++;
	//递归创建左子树[left,pos - 1]
	if (left < pos) {
		build(left, pos - 1, layer, tree->left);
	}
	//递归创建右子树[pos + 1,right]
	if (right > pos) {
		build(pos + 1, right, layer, tree->right);
	}
}
//后序遍历
void postOrder(node* root) {
	if (root == NULL) {//递归出口
		return;
	}
	postOrder(root->left);//递归遍历左子树
	postOrder(root->right);//递归遍历右子树
	post[idx++] = root->value;//保存树根
}
//先序遍历
void preOrder(node* root) {
	if (root == NULL) {//递归出口
		return;
	}
	cout << root->value << " ";//树根
	preOrder(root->left); //左子树
	preOrder(root->right); //右子树
}
//中序遍历
void inOrder(node* root)
{
	if (root == NULL) return; //递归出口
	inOrder(root->left); //左子树
	cout << root->value << " ";//树根
	inOrder(root->right); //右子树
}

其中对于node*的解释在下面:

 4.二叉堆

堆可以看成一棵完全二叉树。用数组实现的二叉树堆,树中的每个结点与数组中存放的元素对应。树的每一层,除了最后一层可能不满,其他每一层都是满的。 二叉堆中的每个结点,都是以它为父结点的子树的最小值。 

 用数组A[]存储完全二叉树,结点数量为n,A[0]不用,A[1]为根结点,有以下性质:

(1)i > 1的结点,其父结点位于i/2;

(2)如果2i > n,那么i没有孩子;如果2i+1 > n,那么i没有右孩子;

(3)如果结点i有孩子,那么它的左孩子是2i,右孩子是2i+1。

 看每个数的下标符合上述的规律和定义 

二叉堆的操作
(1)上浮

 (2)下沉

 堆经常用于实现优先队列,上浮对应优先队列的插入操作push(),下沉对应优先队列的删除操作pop();

介绍了二叉堆的操作,那我们能写出手写堆吗 当然可以 

堆(洛谷P3378)

 代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int h[N];
int len = 0;
void push(int x){
	h[++len]=x;
	int i=len;
	while(i > 1 && h[i] < h[i/2]){
		swap(h[i],h[i/2]);
		i/=2;
	}
}
void pop(){
	h[1] = h[len];
	len --;
	int i = 1;
	while(2 * i <=len){
		int s = 2 * i;
		if(s < len && h[s + 1] < h[s]){
			s ++;
		}
		if(h[s] < h[i]){
			swap(h[s],h[i]);
			i = s;
		}
		else{
			break;
		}
	}
}
int main(){
	int n;
	cin >> n;
	int op ,x;
	while(n--){
		cin >> op;
		if(op==1){
			cin >> x;
			push(x);
		}
		else if(op ==2){
			cout << h[1] << endl;
		}
		else{
			pop();
		}
	} 
}

上面就是我们手写了一个堆    

优先队列

一个优先队列声明的基本格式是:priority_queue<结构类型> 队列名;

就像这样:

priority_queue <int> i;
priority_queue <double> d;

但我们一般都这么写:

priority_queue <node> q;
//node是一个结构体
//结构体里重载了‘<’小于符号
priority_queue <int,vector<int>,greater<int> > q;
//不需要#include<vector>头文件
//注意后面两个“>”不要写在一起,“>>”是右移运算符
priority_queue <int,vector<int>,less<int> >q;

这个优先队列有什么用呢?他能默认帮我们把输入的数字排好大小,然后任我们处置 比如:

#include<bits/stdc++.h>
using namespace std;
priority_queue<int >q;
int main() {
	q.push(10), q.push(23), q.push(12), q.push(6), q.push(2);
	while (!q.empty()) {
		cout << q.top()<<" ";
		q.pop();
	}
	return 0;
}

输出的结果就是 23 12 10 6 2 他默认帮我们从小到大排好序 

至于对于结构体函数如何排序 我们看接下来的代码

#include<bits/stdc++.h>
using namespace std;
struct node {
	int x, y;
	bool operator<(const node& a)const
	{
		return x < a.x;
	}
}k;
priority_queue<node>q;
int main() {
	k.x = 10, k.y = 100; q.push(k);
	k.x = 12, k.y = 60; q.push(k);
	k.x = 14, k.y = 40; q.push(k);
	k.x = 6, k.y = 80; q.push(k);
	k.x = 8, k.y = 20; q.push(k);
	while (!q.empty()) {
		cout <<"("<< q.top().x<<"," << q.top().y <<") "<<" ";
		q.pop();
	}
	return 0;
}

程序大意就是插入(10,100),(12,60),(14,40),(6,20),(8,20)这五个node。
再来看看它的输出:(14,40) (12,60) (10,100) (8,20) (6,80)  它也是按照重载后的小于规则,从大到小排序的。

less和greater的优先队列:

先来看定义这两个优先队列的格式:

priority_queue <int,vector<int>,less<int> > p;
priority_queue <int,vector<int>,greater<int> > q;

然后我们看一个简单的用到这两个队列的案例:

#include<cstdio>
#include<queue>
using namespace std;
priority_queue <int,vector<int>,less<int> > p;
priority_queue <int,vector<int>,greater<int> > q;
int a[5]={10,12,14,6,8};
int main()
{
	for(int i=0;i<5;i++)
		p.push(a[i]),q.push(a[i]);
		
	printf("less<int>:");
	while(!p.empty())
		printf("%d ",p.top()),p.pop();	
		
	printf("\ngreater<int>:");
	while(!q.empty())
		printf("%d ",q.top()),q.pop();
}

输出结果:less<int>:14 12 10 8 6 greater<int>:6 8 10 12 14

所以我们可以看到 less是从大到小进行排序 而greater是从大到小进行排序

还有就是里面我们不一定要加那个greater<int> 我们可以自己构造bool的判断函数进行判断  比如如下代码:

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n;
struct node
{
	int fir,sec;
	void Read() {scanf("%d %d",&fir,&sec);}
}input;

struct cmp1
{
	bool operator () (const node &x,const node &y) const
	{
		return x.fir<y.fir;
	}
};//当一个node x的fir值小于另一个node y的fir值时,称x<y

struct cmp2
{
	bool operator () (const node &x,const node &y) const
	{
		return x.sec<y.sec;  
	}
};//当一个node x的sec值小于另一个node y的sec值时,称x<y

struct cmp3
{
	bool operator () (const node &x,const node &y) const
	{
		return x.fir+x.sec<y.fir+y.sec; 
	}
};//当一个node x的fri值和sec值的和小于另一个node y的fir值和sec值的和时,称x<y

priority_queue<node,vector<node>,cmp1> q1;
priority_queue<node,vector<node>,cmp2> q2;
priority_queue<node,vector<node>,cmp3> q3;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) input.Read(),q1.push(input),q2.push(input),q3.push(input);
	
	printf("\ncmp1:\n");
	while(!q1.empty()) printf("(%d,%d) ",q1.top().fir,q1.top().sec),q1.pop();	
		
	printf("\n\ncmp2:\n");
	while(!q2.empty()) printf("(%d,%d) ",q2.top().fir,q2.top().sec),q2.pop();	
		
	printf("\n\ncmp3:\n");
	while(!q3.empty()) printf("(%d,%d) ",q3.top().fir,q3.top().sec),q3.pop();	
}

我们构造了三个自定义的cmp 里面镶嵌了一个bool类型的operator 来排列各自需要的排列顺序

以上就是所有关于优先队列的内容 ..

下面是一些关于二叉堆的题目 我们一题一题来分析和理解

5.二叉树课后题目

先序排列(洛谷p1030)

想法:

  1. 输出该部分后序遍历最后一项(后序遍历是左右根,所以最后一项是根)
  2. 在中序遍历中找到他
  3. 分成左右两部分
  4. 判断是否为空
  5. 如果不为空,回到第1步

这里有一个问题,就是在中序和后序中的位置对不好,以样例为例,当输出A和B之后,中序的DC位于2、3的位置(位置用0--3表示),而后序在1、2位置,这怎么办呢?
其实,通过观察,我们会发现,一个位置,每被划分为右半部分一次,中序和后序的位置就会错开1。还是以样例为例,DC在有且仅有第一次划分是被分到右边,所以中序和后序的位置会错开1个点。 那么,了解了这点以后,就不会有什么大问题了

下面就是对应的代码:

#include<bits/stdc++.h>
using namespace std;
string s2, s3;

void dis(int l, int r, int s)
{
    cout << s3[r - s];
    for (int i = l; i <= r; i++)
        if (s2[i] == s3[r - s])
        {
            if (i != l) dis(l, i - 1, s);
            if (i != r) dis(i + 1, r, s + 1);
        }
    return;
}
int main()
{
    cin >> s2 >> s3;
    int l = s2.length();
    dis(0, l - 1, 0);
    return 0;
}
新二叉树(洛谷P1305)

这个就是一个新二叉树的写法 就是判断根和左右节点的事情 下面是代码

#include<bits/stdc++.h>
using namespace std;
int n;
char a[30][3];
void f(char x)
{
    if (x != '*')
    {
        cout << x;
        for (int i = 1; i <= n; i++)
            if (a[i][0] == x)
            {
                f(a[i][1]);
                f(a[i][2]);
            }
    }
    return;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i][0] >> a[i][1] >> a[i][2];
    f(a[1][0]);
    return 0;
}
中位数(洛谷p1168)

  想法:这边的思想就是创造两个堆 一个是大根堆 一个是小根堆 然后有一个中间值 每次存两个 如果两个堆的个数是一样的 那么我就输出中间值 这个时候中位数就是mid 但有一种情况就是我前面输入的两个数字都比mid大 那做完这两次操作的时候有一个堆的数量是没有加的  那这个时候我们就要做一个操作 重新寻找mid 然后还要让两个堆的数量一样 那这个时候我们输出的中位数才是中位数 接下来是我们的代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
#include<queue>
using namespace std;
int n;
int a[100100];
int mid;
priority_queue<int, vector<int>, less<int> >q1;//大根堆
priority_queue<int, vector<int>, greater<int> >q2;//小根堆
int main() {
    cin >> n;
    scanf("%d", &a[1]);
    mid = a[1];
    cout << mid << endl;//mid初值是a[1]
    for (int i = 2; i <= n; i++) {
        scanf("%d", &a[i]);
        if (a[i] > mid) q2.push(a[i]);
        else q1.push(a[i]);
        if (i % 2 == 1) {//第奇数次加入
            while (q1.size() != q2.size()) {
                if (q1.size() > q2.size()) {
                    q2.push(mid);
                    mid = q1.top();
                    q1.pop();
                }
                else {
                    q1.push(mid);
                    mid = q2.top();
                    q2.pop();
                }
            }
            cout << mid << endl;
        }
    }
    return 0;
}
合并果子(洛谷p1090)

 这个题目用个优先队列就行 每次取上面的两个数字 然后加起来 再存入数组 循环反复 代码如下


#include<bits/stdc++.h>
using namespace std;
priority_queue<int, vector<int>, greater<int> > q;
int main() {
    int n;
    cin >>n;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        q.push(x);
    }
    int ans = 0;
    while (q.size() != 1) {
        int a = q.top();
        q.pop();
        int b = q.top();
        q.pop();
        ans += a + b;
        q.push(a + b);
    }
    cout << ans << endl;
    return 0;
}

(二)DFS搜索算法

它这个算法的本质其实是递归算法 他就是往死里递归  

接下来我们看一个有关这个搜索的题目:

(1)全排序

 这个题目看到的第一个瞬间就是用暴力枚举(这边我就不写了)但这个很可惜是个不确定的数字输入 然后算法复杂度是O(n^n)  我们也可以用stl的next_permutation来写  但要记得前面先给他排序号 (这个方法对于这个题目来说貌似是最快的) 但这个题目其实可以用我们的深度优先搜索DFS 它就是递归 在递归的同时很好的利用到我们的vector容器 接下来就看我们的代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;

struct ARRAY : vector<int>
{
	ARRAY append(int n) { ARRAY t(*this); t.push_back(n);       return t; }//把n这个数字加进来
	ARRAY remove(int i) { ARRAY t(*this); t.erase(t.begin() + i); return t; }//删除容器中第i个元素
};
void f(ARRAY L, ARRAY R)
{
	if (R.size() == 0)//当R容器里面没有数字了
	{
		for (int i = 0; i < L.size(); i++) printf("%5d", L[i]);
		printf("\n");
		return;
	}
	for (int i = 0; i < R.size(); i++)
	{
		f(L.append(R[i]), R.remove(i));//递归  往死里递归它
	}
}
int  main()
{
	ARRAY L, R;
	int n;
	scanf("%d", &n);
	for (int i = 1; i <=n ; i++) R.push_back(i);
	f(L, R);
}

它这个现在前面构造了两个算是自定义的结构体吧 一个是append 添加 把这个i加到原有的容器里 一个是remove移除 把第i个数从容器中除掉  然后之后呢 就是一直递归递归递归 可能 有些人看不懂那个中间的f(L.append(R[i]),R.remove(i)); 我这边引用一个图片 看下面的图片

 它就是逐渐一个个分一个个分 这个ABCD就好比1234 有那种子函数的那种感觉 我觉得看这张图片之后我们就会比较好理解这个其中的递归

(2)部分排序:

上面那个是全排序 就是将所有数字按照一定顺序全部输出 上面也说了 用我们STL里面的next_permutation 但当我们想输出部分排序的时候 也就是 比如想输出四个数字里面的三个数字 然后从小到大排序的时候 我们就用不到这个容器了 那这个时候我们也可以用这个DFS来做到 

 这个时候我们在原来的代码基础上改一下 就是当左边的容器的数量达到m的时候 就输出 然后继续递归 下面是代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;

struct ARRAY : vector<char>
{
	ARRAY append(char n) { ARRAY t(*this); t.push_back(n);       return t; }//把n这个数字加进来
	ARRAY remove(int i) { ARRAY t(*this); t.erase(t.begin() + i); return t; }//删除容器中第i个元素
};
void f(ARRAY L, ARRAY R,int m)
{
	if (L.size() == m)//当L容器的长度等于m的时候 就输出 然后继续递归
	{
		for (auto it = L.begin(); it != L.end(); it++) {
			cout << *it;
		}	
		printf("\n");
	}

	for (int i = 0; i < R.size(); i++)
	{
		f(L.append(R[i]), R.remove(i),m);//递归  往死里递归它
	}
}
int  main()
{
	ARRAY L, R;
	int n,m;
	scanf("%d%d", &n,&m);
	for (int i = 1; i <= n; i++) R.push_back((i-1)+'A');
	f(L, R,m);
}

(3)求组合数

 这个我们也可以用DFS搜索算法进行编程:
 

#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;

struct ARRAY : vector<int>
{
	ARRAY append(int n) { ARRAY t(*this); t.push_back(n);       return t; }//把n这个数字加进来
	ARRAY remove(int i) { ARRAY t(*this); t.erase(t.begin() + i); return t; }//删除容器中第i个元素
};
void f(ARRAY L, ARRAY R,int m)
{
	if (L.size() == m)//当L容器的长度等于m的时候 就输出 然后继续递归
	{
		for (auto it = L.begin(); it != L.end(); it++) {
			printf("%3d",*it);
		}	
		printf("\n");
	}

	for (int i = 0; i < R.size(); i++)
	{
		if (L.size() > 0) {
			if (L.back() >= R[i])continue;//如果L的最后一位大于R的第一位 那么继续递归遍历
			//他这个管了就是里面的数字只能升序 如果例如1234 四个数字 然后取3个数字
			//132 4这样的布局是不行的 它是不会输出132的 只有134 2  然后输出134
		}
		f(L.append(R[i]), R.remove(i), m);
	}
}
int  main()
{
	ARRAY L, R;
	int n,m;
	scanf("%d%d", &n,&m);
	for (int i = 1; i <= n; i++) R.push_back((i));
	f(L, R,m);
}

(4)有重复元素的排列问题

 代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;

struct ARRAY : vector<char>
{
	ARRAY append(char n) { ARRAY t(*this); t.push_back(n);       return t; }//把n这个数字加进来
	ARRAY remove(int i) { ARRAY t(*this); t.erase(t.begin() + i); return t; }//删除容器中第i个元素
};
long long  ans = 0;
void f(ARRAY L, ARRAY R)
{
	if (R.size() == 0) {
		for (auto it = L.begin(); it != L.end(); it++) {
			cout << *it;
		}
		ans++;
		printf("\n");
		return;
	}
	for (int i = 0; i < R.size(); i++) {
		if (i > 0) 
			if (R[i - 1] == R[i]) //把重复的元素就是排除 继续
				continue;
		f(L.append(R[i]), R.remove(i));
	}
}
int  main()
{
	ARRAY L, R;
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		char x;
		cin >> x;
		R.push_back(x);
	}
	sort(R.begin(), R.end());
	f(L, R);
	cout << ans;
}

大致格式都和上面的题目差不多 只不过多了一步排除重复的元素的一个步骤 挺好理解的。

 (5)排列棋子

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;

struct ARRAY : vector<int >
{
	ARRAY append(int  n) { ARRAY t(*this); t.push_back(n);       return t; }//把n这个数字加进来
	ARRAY remove(int i) { ARRAY t(*this); t.erase(t.begin() + i); return t; }//删除容器中第i个元素
};
void f(ARRAY L, ARRAY A,ARRAY B)
{
	if (A.empty() && B.empty()) {
		for (auto it = L.begin(); it != L.end(); it++) {
			cout << *it;
		}
		cout << endl;
	}
	if (A.size() > 0) {
		f(L.append(A[0]), A. remove(0), B);
	}
	if (B.size() > 0) {
		f(L.append(B[0]),A, B.remove(0));
	}
}
int  main()
{
	ARRAY L, A, B;
	int a, b;
	cin >> a>>b ;
	for (int i = 0; i < a; i++) {
		A.push_back(0);
	}
	for (int i = 0; i < b; i++) {
		B.push_back(1);
	}
	f(L, A,B);
}

这个就是有a个白棋子和b个黑棋子然后去从小到大输出 然后分别取最上面的一个数给L 递归

(6)拆分自然数

也是递归思想 下面是代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;
struct ARRAY : vector<int>
{
    ARRAY append(int v) { ARRAY t(*this); t.push_back(v); return t; }
};
int x;
void f(ARRAY a, int n)
{
    if (n <= 0)
    {
        if (a.size() <= 1)return;//排除最后一个数字是7
        //防止是7=7;
        printf("%d", a[0]);
        for (int i = 1; i < a.size(); i++) printf("%+d", a[i]);
        printf("\n");
    }
    for (int i = 1; i <= n; i++)
    {
           if(a.size()>0)
                if (a.back() > i)continue;
           f(a.append(i), n - i);
    }
}
int main()
{
    ARRAY a;
    scanf("%d", &x);
    f(a, x);
}

(三)BFS搜索算法

bfs算法的全名是宽度优先搜索 它可以理解为“全面扩散、逐层递进” 对于DFS来讲 BFS使用的空间比较大 但是搜索的时间会大大降低  下面看几个BFS的题目

(1)lake counting s (洛谷p1596)

 这个题目意思就是有多少个水坑 怎么判断是个独立水坑 就是四面八方都是“.”的时候那我们这个时候就变成了一个独立的水坑  那我们就创建八个数组然后判断是否周围还有“#” 然后一直查找下去就行了 其实感觉这个题目用dfs写可能会更快 我先把dfs的代码写在下面

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
int n, m;
int ans = 0;
char a[110][110];

int fxx[9] = { 0,-1,-1,-1,0,0,1,1,1 };
int fxy[9] = { 0,0,-1,1,-1,1,-1,0,1 };
void dfs(int x, int y) {
	int r, c;
	a[x][y] = '.';
	for (int i = 1; i <= 8; i++) {
		r = x + fxx[i];
		c = y + fxy[i];
		if (r < 1 || r>n || c<1 || c>m || a[r][c]=='.')
			continue;
		
		a[r][c] = '.';//覆盖覆盖再覆盖
		dfs(r, c);
	}
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >>a[i][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'W') {
				ans++;
				dfs(i, j);
			}
		}
	}
	cout << ans<<endl;
	return 0;
}

这个DFS的代码就是当你找到W的时候 我先将ans+一下 然后就开始搜索 搜索他四面八方的有没有“#” 有的话就是把它覆盖为“.”   然后继续查找查找再查找 这个感觉很好理解 然后接下来我放BFS的代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
char a[N][N];//田地
int v[N][N] = { 0 };//标记是否已经搜索过或者是否需要搜索
struct node {
	int x, y;
};
//方向数组(8个方向)
int dx[] = { -1, -1, 0, 1, 1, 1, 0, -1 };
int dy[] = { 0, 1, 1, 1, 0, -1, -1, -1 };
int n, m;
//判断是否出界
int check(int x, int y) {//判断是否在棋盘范围内
	if (x <= n && x >= 1 && y <= m && y >= 1) {
		return 1;
	}
	return 0;
}
void BFS(int sx, int sy) {
	queue<node> q;
	node st;
	st.x = sx;
	st.y = sy;
	q.push(st);
	v[sx][sy] = 1;
	while (!q.empty()) {
		int x = q.front().x;
		int y = q.front().y;
		for (int i = 0; i < 8; i++) {
			int tx = x + dx[i];
			int ty = y + dy[i];
			if (check(tx, ty) == 1 && v[tx][ty] == 0) {
				node tmp;
				tmp.x = tx;
				tmp.y = ty;
				v[tx][ty] = 1;
				q.push(tmp);
			}
		}
		q.pop();
	}
}
int main() {
	cin >> n >> m;
	//输入田地
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
			if (a[i][j] == '.') {//如果是旱地就不用搜索
				v[i][j] = 1;
			}
		}
	}
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'W' && v[i][j] == 0) {//如果遇到没有搜索过的水坑就搜索
				BFS(i, j);//i 和 j表示搜索开始的起点
				cnt++;//经过几次搜索就表示有几个水坑
			}
		}
	}
	cout << cnt << endl;
	return 0;
}

光看代码长度我们就可以看出 BFS确实费空间奥

上面都有注释 就不多解释了 我觉得这个题目用DFS来写会比较好一点。

(2)填涂颜色(洛谷p1162)

 这个题目就是让你搜索一个被1包围起来的地方 然后把里面的0全部变成2 题意是很好理解的 那我们这个时候就去想是用DFS还是用BFS 很显然我们这边用BFS会更好 我们先把在框外的0给填满1 这样我们就能区别里面的0和外面的0的区别 那填满1之后我们怎么把这些0和那些本来就是1的数组区分开来呢?再开一个数组好了 我们一开始就判断 如果原来的那个数就是1 那我另外一个数组的数据就是-1 否则就是0 这样我们把三中类型的数据给区分了 接下来是代码部分:

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;

int a[35][35];
int b[35][35];
int n;

void dfs(int x, int y) {
	if (x <= 0 || y <= 0 || x > n || y > n || a[x][y] != 0) {
		return;
	}
	a[x][y] = 1;
	dfs(x - 1, y);
	dfs(x, y - 1);
	dfs(x + 1, y);
	dfs(x, y + 1);
}
int main() {
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> a[i][j];
			if (a[i][j] == 1) {
				b[i][j] = -1;
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		dfs(1, i);
		dfs(n, i);
		dfs(i, 1);
		dfs(i, n);//把边界开始一直搜 搜到就是有1包围的那一刻 外面的0此时a[i][j]都变成1了 里面的是没有变的 而原本是1的b[i][j]就是-1 根据这三个条件来判断是哪个数字
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (b[i][j] == -1) {
				cout << 1<<" ";
			}
			else {
				if (a[i][j] == 1) {
					cout << 0<<" ";
				}
				else {
					cout << 2<<" ";
				}
			}
		}
		cout << endl;
	}
	return 0;
}

(3)马的遍历

 

这个是一个BFS的搜索题目 题目让我们判断我们给出的点跳到棋盘上任意一点是否能走到?如果能走到 那我需要几步? 那我们就一个一个搜索呗 就以原点为中心 有八个方向 然后跳 每次跳的时候去判断  这是第几次 然后在这个点的基础上再去往外面搜索 循环结束的条件就是我跳的所有地方都是我走过的 或者是我跳了之后走到棋盘外面了 下面就是代码:

#include<bits/stdc++.h>
using namespace std;
int dir[][2] = { {-2,-1},{-1,-2},{-2,1},{-1,2},{1,-2},{2,-1},{2,1},{1,2} };
int mp[405][405];
struct pos {
	int r, c, d;
	pos(int a=0,int b=0,int d=0):r(a),c(b),d(d){}
};
void bfs(int r, int c);
int n, m, sr, sc;
int main() {
	cin >> n >> m >> sr >> sc;
	memset(mp, -1, sizeof(mp));
	mp[sr][sc] = 0;
	bfs(sr, sc);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cout << mp[i][j] << '\t';
		}
		cout << endl;
	}
	return 0;
}
void bfs(int r, int c) {
	queue<pos>q;
	q.push(pos(r,c));
	mp[sr][sc] = 0;
	while (!q.empty()) {
		pos p = q.front();
		q.pop();
		for (int i = 0; i < 8; i++) {
			int nr = p.r + dir[i][0];
			int nc = p.c + dir[i][1];
			int nd = p.d + 1;
			if (nr >= 1 && nr <= n && nc >= 1 && nc<=m&&mp[nr][nc] == -1) {
				mp[nr][nc] = nd;
				q.push(pos(nr, nc, nd));
			}
		}
	}
}

我们可以看到 DFS和BFS的差别还是有很多的 我们把两个代码对比看一下

 

 一个是递归 一个是用到了队列的这个容器 大致的写法结构是完全不同 

周总结

第一天的贪心算法我没有写进里面 我觉得贪心是相对比较简单的一个算法,所以我就没有写进去 那时候的几个习题做的也还可以  之后的二叉树以及几个搜素确实是比较难理解的 对于剪枝还是没能很好理解它的概念和用法 希望下周能抽时间去看一下竞赛书的能容 这周的有些写法也还不是很熟悉 希望继续加油 勉励

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值