数据结构、算法题联系(BFS广度优先、动态规划、并查集、一维前缀和、二分查找)

算法

BFS广度优先算法

  • 广度优先搜索解决迷宫问题

在这里插入图片描述

#include <iostream>
#include <queue>

using namespace std;

int a[100][100], v[100][100];//全局数组,未初始化时默认值都是0

//方向数组 搞清哪个是x轴y轴
int dx[] = { 0, 1, 0, -1 };//右下左上
int dy[] = { 1, 0, -1, 0 };//右下左上

struct point
{
	int x;
	int y;
	int step;
};

queue<point> r;

//示例一
/*
6 5
1 1 1 1 1
1 1 1 2 1
1 1 1 1 1
1 1 1 2 1
1 1 2 1 1
1 1 1 1 2
1 1 4 3
*/

//示例二
/*
5 4
1 1 2 1
1 1 1 1
1 1 2 1
1 2 1 1
1 1 1 2
1 1 4 3
*/

int main()
{
	//输入
	int n, m, startx, starty, p, q;
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			cin >> a[i][j];//1表示能通过	2表示不能通过
		}
	}
	cin >> startx >> starty >> p >> q;//起点坐标和终点坐标

	//BFS
	//memset(v, 0, sizeof(v));
	point start;
	start.x = startx;
	start.y = starty;
	start.step = 0;
	r.push(start);
	v[startx][starty] = 1;//设置为已经访问

	int flag = 0;
	while (!r.empty())//只要队列不为空就继续访问
	{
		//将队首元素取出判断
		int x = r.front().x;
		int y = r.front().y;
		if (x == p && y == q)
		{
			flag = 1;
			cout << r.front().step;
			break;//找到终点就break
		}
		
		for (int k = 0; k < 3; k++)
		{
			int tx;
			int ty;
			tx = x + dx[k];
			ty = y + dy[k];
			if (a[tx][ty] == 1 && v[tx][ty] == 0)
			{
				point temp;
				temp.x = tx;
				temp.y = ty;
				temp.step = r.front().step + 1;
				r.push(temp);
				v[tx][ty] = 1;
			}
		}

		r.pop();//拓展完了 需要对队首元素出队
	}

	if (flag == 0)
	{
		cout << "no answer!";
	}

	return 0;
}

小结:最开始用示例二的时候结果为no answer!经过调试之后发现是数组v的索引出了问题,因为数组下标是从0开始的,所以应当也要对第0行和第0列进行初始化,如示例一。

全局变量的数组默认初始化为0 ,所以数组v默认都初始化为0,v数组相当于用来记录已经搜索过的位置

该算法流程大致如下:先将初始位置记录下来入队(队列元素包括三要素:起点坐标x,y和步骤,所以这里定义了一个结构体,然后queue容器以该结构体为模板),循环结束的标志是队列为空或者找到该最短步骤


动态规划

  • 最长回文字串

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

代码样例

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();//记录输入串的长度
        vector<vector<int>> dp(n, vector<int>(n));//理清如何用开辟一个n*n的数组

        if (n < 2)
        {
            return s;
        }
        
        //初始化对角线的值 因为单个字符它是回文串 所以先将它们都赋值为true
        for (int i = 0; i < n; i++)
        {
            dp[i][i] = true;
        }

        int begin = 0;
        int length = 1;
        
        //填表 L代表字串长度,所以先遍历所有长度为2的情况
        //可知 填表方式是斜着填的
        for (int L = 2; L <= n; L++)
        {
            //i为左边界 j为右边界
            for (int i = 0; i < n; i++)
            {
                int j = L + i - 1;//j的表达式很关键 建立变量之间的关系的能力需要加强
                
                //当j超过有边界的情况表明L长度的字串情况都已经遍历完
                if (j >= n)
                {
                    break;
                }

                //如果两边字符相等
                if (s[i] == s[j])//这里是s而不是dp
                {
                    //当长度小于3的情况下满足两边相等 就将该置为true
                    if (j - i + 1 <= 3)
                    {
                        dp[i][j] = true;
                    }
                    else//大于三的情况下
                    {
                        //将去掉两头的字串的值赋给当前子串
                        //absabd中的absa 因为bs是false 那么absa也是false
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                else//两头字符不相等 那么直接置为false
                {
                    dp[i][j] = false;
                }

                //字串中只要输出第一次就行j-i+1 > length
                if (dp[i][j] && j-i+1 > length)
                {
                    begin = i;
                    length = j - i + 1;
                }

            }
        }
        return s.substr(begin, length);
    }
};

如何用到了动态规划? absabd中的absa 因为bs是false 那么absa也是false,就是利用字串bs为false直接推出absa为false,而不用再重新判断其是否为回文串。

在这里插入图片描述

按照斜线的顺序填好表格

//填表 L代表字串长度,所以先遍历所有长度为2的情况
for (int L = 2; L <= n; L++)
    //i为左边界 j为右边界
    for (int i = 0; i < n; i++)
        //j的表达式很关键 建立变量之间的关系的能力需要加强
        int j = L + i - 1;`

个人认为这三行很关键,也是需要学习的点。在涉及到字串的问题的时候可以考虑

其他:

vector<vector> dp(n, vector(n));//理清如何用开辟一个n*n的数组

if (dp[i][j] && j-i+1 > length)中j-i+1 > length是让其输出第一个最长字串的关键,在这里出现过疑点

return s.substr(begin, length),string中的substr函数以s = string为例 s.substr(1,3)为tri

并查集

在这里插入图片描述

代码实现(递归法)

#include <iostream>
#include <vector>

using namespace std;

int v[100];


void init(int n)
{
	for (int i = 1; i <= n; i++)
	{
		v[i] = i;
	}
}

int find(int x)
{
	if (v[x] == x)
	{
		return x;
	}
	else
	{
		v[x] = find(v[x]);//压缩路径

		return v[x];
	}
}

void Union(int i, int j)
{
	int f_i = find(i);
	int f_j = find(j);
	v[f_i] = f_j;

}

int main()
{
	int n, m, q;
	int x, y;
	cin >> n >> m;

	init(n);

	for (int i = 0; i < m; i++)
	{
		cin >> x >> y;
		Union(x, y);
	}

	cin >> q;
	for (int i = 1; i <= q; i++)
	{
		cin >> x >> y;
		if (find(x) == find(y))
		{
			cout << "Yes" << endl;
		}
		else
		{
			cout << "No" << endl;
		}
	}


	return 0;
}

/*
10 7
2 4
5 7
1 3
8 9
1 2
5 6
2 3
3
3 4
7 10
8 9
*/

书上给出的实现(循环法,初始化方式也不同)

#include <iostream>

using namespace std;

struct ElemetType
{
	char data;//假定并查集的元素为字符
	int parent;//游标,该元素的双亲在数组中的下标
};

const int MaxSize = 100;

class UnionFind
{
public:
	UnionFind(char ch[], int n);//每个元素构成一个子集
	~UnionFind();
	int Find(char x);//查找元素x所在子树的根结点
	void Union(char x, char y);//合并元素x和y所在子集

private:
	ElemetType elem[MaxSize];
	int length;//集合的元素个数
};

UnionFind::UnionFind(char ch[], int n)
{
	for (int i = 0; i < n; i++)
	{
		elem[i].data = ch[i];
		elem[i].parent = -1;
	}

	length = n;
}

UnionFind::~UnionFind()
{

}

int UnionFind::Find(char x)
{
	int i;
	for (i = 0; i < length; i++)
	{
		if (elem[i].data == x)
		{
			break;
		}
	}

	while (elem[i].parent != -1)
	{
		i = elem[i].parent;//向上找祖先
	}

	return i;//返回下标
}

void UnionFind::Union(char x, char y)//将y连接到x上
{
	int vex1 = Find(x);
	int vex2 = Find(y);
	if (vex1 != vex2)
	{
		elem[vex2].parent = vex1;
	}
}

int main()
{
	char str[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	int flag[6];
	UnionFind UF{ str, 6 };
	UF.Union('a', 'b');
	UF.Union('a', 'c');
	UF.Union('d', 'e');

	for (int i = 0; i < 6; i++)
	{
		flag[i] = UF.Find(str[i]);
		cout << flag[i] << ":" << str[i] << endl;
	}

	return 0;
}

这表示元素a、b和c属于同一个集合,根节点为0;元素d和e属于同一个集合,根节点为3;元素f自己独立成为一个集合,根节点为5。


一维前缀和

输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:

5 3
2 1 3 6 4
1 2
1 3
2 4

输出样例:

3
6
10

代码实现

#include <iostream>
#include <vector>

using namespace std;

int a[100], s[100];

int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		int temp;
		cin >> temp;
		a[i] = temp;
	}

	for (int j = 1; j <= n; j++)
	{
		s[j] = s[j - 1] + a[j];//初始化前缀和
	}

	for (int k = 0; k < m; k++)
	{
		int r, l;
		cin >> r >> l;
		cout << s[l] - s[r - 1] << endl;
	}

	return 0;
}

二分查找

704.二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size()-1 , middle = (left+right)/2;
        while(left <= right)
        {
            if(nums[middle] < target)
            {
                left = middle + 1;
                middle = (right+left)/2;
            }
            else if(nums[middle] > target)
            {
                right = middle - 1;
                middle = (right+left)/2;
            }
            else
            {
                return middle;
            }
        }
        return -1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值