一、二分算法初步学习
简单理解部分(引入性题目)
给出一个已经排序完成的数组,从中寻找一个元素,找到后终结(解析置于代码中)
1.初等实现
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
int main()
{
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
int n=10,target;
int low, height,middle;
cin >> target;//输入需要查找的元素
low = a[0];
height = a[10];//注意初始化位置,在循环之外
while(low<=height)
//当循环到这一步之前,使得low==height==middle
//终止条件是只剩下这一个数,也就是直到low>hetght表明只剩下一个数,此时也是最大的循环次数
{
int middle = (low + height) / 2;
if (target == middle)
return 1;
//后两步骤是二分算法的精髓,不断划分区间从而找到元素
if (target > middle)low = middle + 1;
//如果目标元素大于中间值,说明位于中间值与最大值之间,更新最小值范围
if (target < middle)height = middle - 1;
//如果目标元素小于中间值,说明位于中间值与最小值之间,更新最大值范围
}
}
对算法精髓部分进行优化:
if (target > middle)low = *upper_bound(a, a + 10, middle);
if (target < middle)height = *lower_bound(a, a + n, middle);
//注意,这里的upper_bound返回的是地址值,用*,意味着取地址,能够返回相应地址所指代的数值
2.查找被查找元素在数组中的位置,输出相应的下标
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
int main()
{
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
int n = 10, target;
int low, height, middle;
cin >> target;//输入需要查找的元素
low = 0;
height = 9;
while (low <= height)
{
int middle = (a[low] +a[height]) / 2;//下标表示
if (target == middle)
cout << middle;
if (target > middle)low = *upper_bound(a, a + 10, middle);
if (target < middle)height = *lower_bound(a, a + n, middle);
}
}
拓展:
如果对于乱序的数组,想输出相应位置的下标,则可以用结构体先对相应位置进行标记,之后排序,按照二分算法进行查找
struct array
{
int x;
int id;
//按照之前的方法进行排序元素,找到后输出相应的id数值
}a[10];
二、数字金字塔(动态规划简单入门)
思路过程(模拟的过程):题目所问的是第一层元素的最大值,一开始我们是不知道如何下手的,因为没有一个明显的思路求最大值,但是,如果我们知道第二层元素的最大值,就可以求解第一层元素的最大值,同理,第二层的最大值如何去求呢?要看第三层的每个元素的最大值,第三层怎么求呢?等等如此,逐层推理下去,到最后一步时,递归到第一层元素的最大值,到达了一个已知的部分,则可以求解,具体实现请看代码
#include<iostream>
using namespace std;
int main()
{
int i, j;
int a[10][10];
int n;
cin >> n;
for (i = 1; i <= n; i++)
for (j = 1; j <= i; j++)cin >> a[i][j];
for (i = n; i >1; i--)//每一行进行递增,使得最大值逐步向上递增,最终使得获得最大值
//(for循环中循环条件的设置,是能否继续执行的条件)
{
for (j = 1; j < i; j++)//让底层开始比较,对上一层进行加和处理,使上一层获得最大值
a[i - 1][j] += max(a[i][j], a[i][j + 1]);
//动态规划递归状态方程(用一个式子简化了if语句的书写)(用max简化了方程的存储内容)
}
cout << a[1][1];
}
**这也引出了动态规划的核心思路:**从上到下地思考问题,从下到上地书写代码(拿此题为例:使第一层元素最大,就要使第二层最大,逐层延展,在写代码时,从下到上地书写)
三、最长上升子序列个数(记忆化搜索)
将以每一个元素为结尾的最长上升子序列进行记录,记录数值(提前算出,直接保留,大大降低时间复杂度以及代码复杂度)
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int a[10] = {0}, length[10] = {0};
int n;
cin >> n;
for (int i = 1; i <= n; i++)cin >> a[i];
//记录当前元素为终点的最长值
for (int i = 1; i <= n; i++)
{
length[i] = 1;
for (int j = 1; j < i; j++)
if (a[j] < a[i])length[i] = max(length[j]+1, length[i]);
//length[j]表示以当前元素为终点的最大子序列
//已经在之前动态规划,进行记忆化数据后,不用再次求,时间复杂度大大降低
//为什么不直接+1呢,因为遇到小的数值重置时,length[i]能够对之前较大的数据进行保留
}
sort(length + 1, length + n + 1);
cout << length[n];
}
四、最长公共子序列
(学习此部分内容参考自:https://blog.csdn.net/hrn1216/article/details/51534607?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161682377516780274181204%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=161682377516780274181204&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-2-51534607.pc_search_result_cache&utm_term=%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D)
LCS性质特征(求长度用动态规划缩短范围的核心原理):设A=“a0,a1,…,am”,B=“b0,b1,…,bn”,且Z=“z0,z1,…,zk”为它们的最长公共子序列。
如果am=bn,则zk=am=bn,且“z0,z1,…,z(k-1)”是“a0,a1,…,a(m-1)”和“b0,b1,…,b(n-1)”的一个最长公共子序列;
如果am!=bn,则若zk!=am,蕴涵“z0,z1,…,zk”是“a0,a1,…,a(m-1)”和“b0,b1,…,bn”的一个最长公共子序列;
如果am!=bn,则若zk!=bn,蕴涵“z0,z1,…,zk”是“a0,a1,…,am”和“b0,b1,…,b(n-1)”的一个最长公共子序列。
用递归方程表示:
求最长长度代码:
//最长公共子序列
//思路来源:(性质定理)如果am=bn,则同时去掉后,z(k-1)是最后一个元素
//如果am!=bn,则同时去掉后取最大值即可(一定不是公共部分)
#include<iostream>
using namespace std;
int main()
{
int a[10] = { 1,3,4,7 }, b[10] = {3,4,1,5}, dp[10][10] = { 0 };
for (int i = 0; i < 3; i++) //以a数组元素为起点逐个判定
{
for (int j = 0; j < 3; j++)
{
if (a[i] == b[j])dp[i+1][j+1] = dp[i][j]+1;
else dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);//两种情况合并书写
}
}
cout << dp[3][3];
}
动态规划思想体现:1.在这里将一个大问题缩短成无限个小问题,每一个小问题之间互相关联(求公共子序列最长长度,看看将之缩短会怎样)也就是动态规划核心特点:从大到小
2.在列出递归方程后,从小到大地编写实现程序,逆序编写程序