[重修数据结构0x02]从树的遍历、图的遍历到搜索

构造函数的补充知识

一.树的遍历

1.1 树的静态数组存储

由于树的子结点可能有多个,因此一个一个写指针域可能较为麻烦,建议用数组下标来表示地址。

struct node{
	int data;
	int child[maxn];
	}Node[maxn];              //

改进:无法预知子节点个数,有时空间可能无法接受,因此改用vector.

struct node{
	int data;
	vector<int> child;
	}Node[maxn];
	

图1.1

//如图1,1,存储这样一棵树
Node[0].child[0] = 1; 
Node[0].child[1] = 2;
Node[0].child[2] = 3;
Node[2].child.size() ==  0;
//其他略 

1.2 树的遍历

树的先序遍历,如图1.1,可知先序遍历序列为 0-1-4-5-2-3-6
代码如下

void preorder(int  root)
{
	cout<<Node[root].data;
	for(int i=0;i<Node[root].child.size();i++)
		preorder(Node[root].child[i]);
}

上面使用的是遍历,为什么没写明递归边界?不存在递归边界吗(vector存储的特性)
树的层次遍历,队列实现,

void levelorder(int root)
{
	queue<int> q;
	q.push(root);
	while(!q.empty())
	{
		int front = q.front();
		cout<<Node[front].data;
		q.pop();
		for(int i=0;i<Node[front].child.size();i++)
			q.push(Node[front].child[i]);
	}
}

如果需要对层号进行操作,需要在结点就多定义一个数据域

struct node{
	int data;
	int num;
	vector<int> child;
}Node[maxn];

void levelorder(int root)
{
	queue<int> q;
	q.push(root);
	Node[root].num = 0;
	while(!q.empty())
	{
		int front = q.front();
		//访问结点
		q.pop();
		for(int i=0;i<Node[root].child.size();i++)
			int child = Node[root].child[i];
			Node[child].num = Node [root].num +1;
			q.push(child);
	}
}

1.3 从树的遍历看DFS、BFS

遇到深度搜索问题,不妨把状态看作树的结点,然后问题就变为对树的先序遍历,
一句话:将问题转化为

二.图的遍历

2.1图的存储

概念: 有向图、无向图。入度:出边数。出度:入边数。
点权、边权
连通分量:无向连通图(任意两点都连通)的极大连通子图。
强连通分量:(有向图概念)
连通块:连通分量和强连通分量。

1.邻接矩阵:略。顶点少可以用。
2.邻接表:简单的建议用vector实现,方便存点。

struct Node{
	int v;  // 存放边的终点
	int w;   // 存放边权
}
vector<Node> Adj[N]; //邻接表,N为顶点个数,用来存放边终点

//添加1->3的有向边,权值为4
	Node tmp;
	tmp.v = 3;
	tmp.w = 4;
	Adj[1].push_back(tmp);

//定义Node时候,采用构造函数更为方便
struct Node{
	int v;
	int w;
	Node(int_v,int_w):v(_v),w(_w) {}  //记住
	}
	Adj[1].push_back(Node(3,4));

3.链式前向星存储图

int head[maxn];  //点,头
struct edge      //边
{
	int to;
	int weight;
	int next; //存相邻边
}e[maxn];

void add(int u,int v,int c)
{
	e[cnt].to = v ;
	e[cnt].weight = c;
	e[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号,存上一条表
	head[u] = cnt++ ; //类似头插法
}

2.2 图的遍历

1.DFS遍历图

        //DFS 伪代码,记忆,熟练使用
DFS(u){
	vis[u] = true;
	for(遍历u能到达的定点v)
	{
		if(vis[v]== false)
			DFS(v);
	}
	
DFSTrave(G)  		//遍历图G
{
	for(G的所有顶点u)
		if(vis[u] == false)
			DFS[u];
}
const int maxn = 1000;
const int INF = 1000000000;

邻接矩阵DFS

int n,G[maxn][maxn]; // n为顶点数,maxn为最大顶点数目
bool vis[maxn];
void DFS(int u,int depth)
{
	vis[u] = true;
	//对u进行操作
	for(int v =0 ;v<n;v++)
		if(vis[v] == false && G[u][v] != INF)
			DFS(v,depth+1);
}
void DFSTrave()
{
	for(int u=0;u<n;u++)
		if(vis[u]==false)
			DFS(u,1);
}

邻接表DFS

vector<int> ADJ[maxn];
int n;
bool vis[maxn];

void DFS(int u,int depth)
{
	vis[u] == true;
	// 对结点u进行操作
	for(int i = 0;i<ADJ[u].size();i++)
		{
			int v = ADJ[u][i];
			if(vis[v]==false)
				DFS(v,depth+1);
		}
}
void DFSTrave()
{
	for(int u=0;u<n;u++)
		if(vis[u]==false)
			DFS(u,1);
}

链式前向星

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

2.BFS遍历

BFS伪代码

BFS(int u)
{
	queue q;
	inq[u] = true;
	while(q非空)
	{
		q出队;
		访问u;
		for(u可以到达的所有v)
			{
				if(inq[v]==fasle)
					v入队;
				inq[v] = true;
			}
		}
}
BFSTrave(G)
{
	for(G的所有定点u)
		if(inq[u]==false)
			BFS[u];
}

链式前向星

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}

邻接表BFS

vector<int> Adj[maxn];
int n;
bool inq[maxn];
void BFS(int u)
{
	queue<int> q;
	q.push(u);
	inq[u] = true;
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i=0;i<Adj[u].size();i++{	
			int v = Adj[u][i];
			if(inq[v] == false)
				{
					q.push(v);
					inq[v] = true;
				}
		}
	}
}
void DFSTrave(){
	for(int i=0;i<n;i++)
		{
			if(inq[i]==false)
				DFS(i);
		}
}

以下是BFS需要层号的时候

struct Node{
	int v;
	int level;
}
vector<Node> Adj[N];
void BFS(int s)
{
	queue<Node> q;
	Node start;
	start.v = s;
	start.level = 0;
	q.push(start);
	inq[start.v] = true;
	while(!q.empty())
	{
		Node topNode = q.front();
		q.pop();
		int u = topNode.v;
		for(int i=0;i<Adj[u].size();i++)
		{
			Node next = Adj[u][i];
			next.level = topNode.level + 1;
			if(inq[next.v] == false)
			{
				q.push(next);
				inq[next.v] = true;
			}
		}
	}
}

三.基本搜索

下面都要思考:如何存储最优路径,如何存储最优方案。

3.1深度优先搜索(DFS)`

DFS是用递归的方式来枚举所有完整路径以遍历所有情况的搜索方法。
系统栈来实现递归。

案例1.背包问题初探
	有n件物品,每件物品的重量为w[i],价值为c[i]。
	现在需要选出若干物品,放在容量为V的背包里,求最大价值。

用搜索的方法,复杂度为O(2^n)
最直接的搜索想法

const int maxn = 30;
int n,V;
int maxValue = 0;
void DFS(int index, int sumW, int sumC)
{
	if(index == n)
		{
			if(sumW <=V && sumC > maxValue)
				maxValue = sumC;
			return ;
		}
	DFS(index+1,sumW,sumc);
	DFS(index+1,sumW+w[index],sumC+c[index]);
}
DFS(0,0,0);
			

对背包容量V进行剪枝

void DFS(int index,int sumW, int sumC)
{
	if(index == n)
		return ;
	DFS(index+1,sumW,sumC);
	if(sumW+w[index]<=V)
	{
		if(sumC+c[index]>maxValue)
			maxValue = sumC + c[index];
		DFS(index+1, sumW+w[index],sumC+c[index]);
	}
}
案例二.给定N个整数,从中选择K个数,使这K个数和为X,选择平方和最大的方案。
		思考:如何保存最优方案?
int n,k,x;
int maxSumqu = -1;
int A[maxn];
vector<int> temp,ans;
void DFS(int index,int nowK,int sum,int sumSqu)
{
	if(nowK == K &&sum == x)
		{
			if(sumSqu > maxSumSqu)
				{ maxSumSqu = sumSqu;
				  ans = temp;
				 }
			return ;
		}
	 if(index ==n ||nowK > k || sum >x) return ;
	temp.push_back(A[index]);
	DFS(index+1,nowK +1,sum+A[index],sumSqu+A[index]*A[index]);
	temp.pop_back();
	DFS(index + 1,nowK,sum,sumSqu);
}

3.2广度优先搜索BFS

先看伪代码

void BFS(int s)
{
	queue<int> q;
	q.push(s);
	while(!q.empty())
	{
		val top = q.front()取出队头元素;
		访问top;
		队头出队,q.pop();
		将top下一层的每一个元素遍历,全部入队并更新状态;
	}
}
案例一.给出一个m*n的矩阵,状态为01.
上下左右视为相邻,求矩阵中块的个数。

案例一的描述

struct node
{
	int x;
	int y;
}Node;
int n,m;
int matrix[maxnn][maxn];
bool inq[maxn][maxn];
int X[4] =  { 0,0,1,-1} 
int Y[4] =  { 1,-1,0,0}    //增量数组,经常用到
bool judge(int x,int y)
{
	if(x>=n||y>=m||x<0||y<0) return false;
	if(matrix[x][y]==0||inq[x][y]==false) retrun false;
	return true;
}
void BFS(int x,int y)
{
	queue<node> q;
	Node.x = x;
	Node.y = y;
	q.push(Node);
	inq[x][y] = true;
	while(!q.empty())
	{
		node top = Q.front();
		Q.pop();
		for(int i=0;i<4;i++)
		{
			int newX = top.x + X[i];
			int newY = top.y + Y[i];
			if(judege(newX,newY))
			{	Node.x = newX;
				Node.y = newY;
				Q.push(Node);
				inq[newX][newY] = true;
			}
		}
	}
int main()
{
	int ans = 0;
	for(int x=0;x<n;x++)
	{	for(int y=0;y<m;y++)
		{
			if(matrix[x][y] == 1 &&inq[x][y] == false)
			{
				ans++;
				BFS(x,y);
			}
		}
	}
cout<<ans;
return 0;
				
}			
案例二
n*m的迷宫, *表示不可通过,.表示平地,S起点,T终点,只能上下左右移动,求S到T的最短路步数。

案例二样例描述

const int maxn = 100;
struct node
{ 
	int x;
	int y;
	int step;
}S,T,Node;
int n,m;
char maze[maxn][maxn];
bool inq[maxn][maxn];
int X[4] = {0,0,1,-1};
int Y[4] = {1,-1,0,0};
bool test(int x,int y)
{
	if(x>=n||x<0||y>=m||y<0)	return false;
	if(inq[x][y] == true ||maze[x][y] == '*') return false;
	return true;
}
int BFS()
{
	queue<node> q;
	q.push(S);
	while(!q.empty())
	{
		node top = q.front();
		q.pop();
		if(top.x == T.x && top.y == T.y)
			return top.step;
		for(int i=0;i<4;i++)
		{
			int newX = top.x + X[i];
			int newY = top.y + Y[i];
			if(test(newX,newY))
			{
				Node.x = newX;
				Node.y = newY;
				Node.step++;
				q.push(Node);
				inq[newX][newY] = true;
			}
		}
	}
	return -1;
}
		

3.3 记忆化搜索(待解锁补充)

填充:
搜索的低效在于没有能够很好地处理重叠子问题;动态规划虽然比较好地处理了重叠子问题,但是在有些拓扑关系比较复杂的题目面前,又显得无奈。记忆化搜索正是在这样的情况下产生的,它采用搜索的形式和动态规划中递推的思想将这两种方法有机地综合在一起,扬长避短,简单实用,在信息学中有着重要的作用。
  用一个公式简单地说:记忆化搜索=搜索的形式+动态规划的思想。
讲的很全面,等初步学动态规划之后回头再来看

四.练习和知识补充

4.1 PTA 1103 Integer Factorization (30 分)

** pow()函数
** 保留小数点后几位如何输出?cout、double?精度?小数点后?
想要使用setprecision()函数,必须包含头文件#include <iomanip>
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{   
    const double value = 12.3456789;
    cout << value << endl; // 默认以6精度,所以输出为 12.3457
    cout << setprecision(4) << value << endl; // 改成4精度,所以输出为12.35
    cout << setprecision(8) << value << endl; // 改成8精度,所以输出为12.345679
    cout << fixed << setprecision(4) << value << endl; // 输出为12.3457
    cout << value << endl; // 依然显示12.3457
    cout.unsetf( ios::fixed ); // 显示为12.35
    cout << value << endl;
    cout.precision( 6 ); // 恢复成原来的样子,输出为12.3457
    cout << value << endl;
    return 0;
}

** 结构体构造函数怎么用

**for循环的简略表示

题目链接:PTA 1103 Integer Factorization (30 分)
题意:给定N,K,P,将N表示为K个正整数(可以相同,递减排列)的P次方的和,
即 N = n1^P + …nk^P ,多种方案时选择n1+n2+…nk最大的方案。

输入:

//1079
#include <iostream>
#include <vector>
#include <cmath>
#include <iomanip>
using namespace std;

const int maxn =  100010;
struct node
{
    double weight;
    vector<int> child;
}Node[maxn];

double ans;
int N;
double P;
double r;


void dfs(int index,int depth)
{
    if(Node[index].child.size() == 0)
    {
        ans = ans + Node[index].weight*pow((1+r),depth);
        return ;
    }
    for(int i=0;i<Node[index].child.size();i++)
    {
        int newindex = Node[index].child[i];
        dfs(newindex,depth+1);

    }
}
int main()
{
    cin>>N>>P>>r;
    r = r/100;
    int t;
    for(int i=0;i<N;i++)
    {
        int nums;
        cin>>nums;
        if(nums>0)
        {
            for(int j=0;j<nums;j++)
                {
                    cin>>t;
                    Node[i].child.push_back(t);
                }
        }
        if(nums == 0)
        {
            cin>>Node[i].weight;
        }
    }
    dfs(0,0);
    ans = ans*P;
    cout<<fixed<<setprecision(1)<<ans;
    return 0;
}
PTA1090略过
// 1094 The Largest Generation (25 分)
// dfs 完全遍历即可
//深刻理解,层次和dfs、bfs的关系
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int maxn =110;

int maxDepth;
int N,M;
string s1,str;
int par,chil;
int maxGen = -1;
int ans;
int GenNum[maxn];

struct node
{
    int depth;
    vector<int> child;
}Node[maxn];

int trans(string s)
{
    int a = s[0] - '0';
    int b = s[1] - '0';
    return (a*10+b);
}

void bfs(int index)
{
    queue<node> q;
    q.push(Node[index]);
    while(!q.empty())
    {
        node now = q.front();
        q.pop();
        for(int i=0;i<now.child.size();i++)
        {
            int tep = now.child[i];
            Node[tep].depth = now.depth + 1;
            int nowDepth = Node[tep].depth;
            GenNum[nowDepth] ++;
            q.push(Node[tep]);

        }
    }
    return ;
}


int main()
{
    GenNum[1] = 1 ;
    Node[1].depth = 1;
    cin>>N>>M;
    for(int i=0;i<M;i++)
    {
        int t;
        cin>>s1>>t;
        par = trans(s1);
        for(int j=0;j<t;j++)
        {
            cin>>str;
            chil = trans(str);
            Node[par].child.push_back(chil);
        }
    }
    bfs(1);
    for(int i=1;i<=N;i++)
    {
        if(GenNum[i]>maxGen)
        {
            maxGen = GenNum[i];
            ans = i;
        }
    }
    cout<<maxGen<<' '<<ans;
    return 0;
}
//1013
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

const int N = 1111;
int n,m,k  ;
vector<int> G[N];
bool vis[N];
int currentPoint;
void dfs(int v)
{
    if(v == currentPoint) return ;
    vis[v] = true;
    for(int i=0;i<G[v].size();i++)
    {
        if(vis[G[v][i]]==false)
            dfs(G[v][i]);
    }
}


int main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    for(int query = 0;query < k;query++)
    {
        cin>>currentPoint;
        memset(vis,false,sizeof(vis));
        int block = 0;
        for(int i=1;i<=n;i++)
        {
            if(i!=currentPoint&&vis[i]==false)
            {
                dfs(i);
                block++;
            }
        }
        cout<<block-1<<endl;
    }

    return 0;
}
                     PTA1053 Path of Equal Weight (30)
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
const int maxn = 110;
int path[maxn];
int n,m,s;
struct node
{
    int weight;
    vector<int> child;
}Node[maxn];
bool cmp(int a,int b)
{
    return Node[a].weight>Node[b].weight;
}
void dfs(int root,int sum,int step)
{
    path[step] = root;
    if(sum>s) return ;
    if(sum == s)
    {
        if(Node[root].child.size()!=0)
            return;
        for(int i=0;i<step;i++)
        {
            int t = path[i];
            cout<<Node[t].weight<<' ';
        }
        cout<<Node[path[step]].weight;
        cout<<endl;
        return;
    }
    for(int i=0;i<Node[root].child.size();i++)
    {
        int next = Node[root].child[i];
        dfs(next,sum+Node[next].weight,step+1);
    }
}
int main()
{
    cin>>n>>m>>s;
    for(int i=0;i<n;i++)
        cin>>Node[i].weight;
    for(int i=0;i<m;i++)
    {
        int r1,num1,t1;
        cin>>r1>>num1;
        for(int j=0;j<num1;j++)
        {
            cin>>t1;
            Node[r1].child.push_back(t1);
        }
        sort(Node[r1].child.begin(),Node[r1].child.end(),cmp);
    }

    dfs(0,Node[0].weight,0);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值