基础算法之vector、回溯
一.vector
vector介绍
vector 是C++ STL的一个重要成员,使用它时需要包含头文件:#include ,我对它的理解就是一个动态数组,如果你需要一个数组,又不知道它的长度是多少,vector是一个好的数据结构。
- 特点
1.顺序序列
2.动态数组
3.内存分配
vector有两个函数:
capacity(),返回对象缓冲区(vector维护的内存空间)实际申请的空间大小。
size(),返回当前对象缓冲区存储数据的个数。
对于vector来说,capacity是永远大于等于size的,capacity和size相等时,vector就会扩容,capacity变大。
vector的常用方法
1.初始化
-
定义一个向量:
vector< typename>name;
Typename可以是int,char,double,也可以是vector型vector<vector >name;//> >之间要加空格。这个相当于是一个二维数组。 -
(1)vector< int>a;默认初始化
(2)vector< int>a(10);//定义了10个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值,其值是不确定的。
(3)vector< int>a(10,1);//定义了10个整型元素的向量,且给出每个元素的初值为1
(4)vector< int>a(b);//用b向量来创建a向量,整体复制性赋值
(5)vector< int>a(b.begin(),b.begin+3);//定义了a值为b中第0个到第2个(共3个)元素
(6)int b[7]={1,2,3,4,5,9,8};
vector< int>a(b,b+7);//从数组中获得初值 -
函数:begin(),end(),front(),back()
begin() 得到数组头的指针
end() 得到数组的最后一个单元+1的指针
front() 得到数组头的引用
back() 得到数组的最后一个单元的引用
//1.1 默认初始化
vector<int> v;
out_vector(v);
cout << v.back() << endl;
// 1.2 赋予长度,默认是0
vector<int> v(n);
out_vector(v);
// 1.3 初始化长度,和默认值
vector<int> v(n, 1);
out_vector(v);
// 1.4 用另一个向量初始化
vector<int> w;
for(int i=0;i<5;i++){
w.push_back(i);
}
out_vector(w);
vector<int> v(w);
out_vector(v);
w.push_back(1);
cout << "w add a element." <<endl;
out_vector(w);
out_vector(v);
// 1.5 用另一个向量的一部分来初始化
vector<int> w;
for(int i=0;i<5;i++){
w.push_back(i);
}
out_vector(w);
vector<int> v(w.begin(), w.begin() + 3);
out_vector(v);
// 1.6 用数组的一部分来初始化
int a[] = {0,1,2,3,4,5,6,7,8};
vector<int> v(a, a+4);
out_vector(v);
2.模拟栈
- a.push_back(5); //在a的最后一个向量后插入一个元素,其值为5
a.back(); //返回a的最后一个元素
a.pop_back(); //删除a向量的最后一个元素
vector<int> v;
for(int i=0;i<n;i++){
v.push_back(i);
cout << "push:" << i << endl;
}
out_vector(v);
for(int i=0;i<n;i++){
int t = v.back();
v.pop_back();
cout << "pop:" << t << endl;
}
out_vector(v);
3.模拟队列
- a.front(); //返回a的第一个元素
a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+3(不包括它)
vector<int> v;
for(int i=0;i<n;i++){
v.push_back(i);
cout << "push:" << i << endl;
}
out_vector(v);
for(int i=0;i<n;i++){
int t = v.front();
v.erase(v.begin());
cout << "pop:" << t << endl;
}
out_vector(v);
4.遍历
- 1.通过下标读取
vector<int> v;
for(int i=0;i<n;i++){
v.push_back(i);
cout << "push:" << i << endl;
}
out_vector(v);
for(int i=0;i<n;i++){
int t = v.front();
v.erase(v.begin());
cout << "pop:" << t << endl;
}
out_vector(v);
- 2.通过迭代器读取
for(vector<int>::iterator it = v.begin(); it != v.end(); it ++){
cout << *it << " ";
}
cout << endl;
for (vector<int>::reverse_iterator it = v.rbegin(); it!= v.rend(); it++) {
cout << *it << " ";
}
cout <<endl;
5.插入指定元素
- 1.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
- 2.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
- 3.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8,插入元素后为1,4,5,9,2,3,4,5,9,8
int a[] = {1,2,3,4,4,5,6,7};
vector<int> v(a, a+sizeof(a)/sizeof(int));
out_vector(v);
cout << "在第1个元素后面插入10:" <<endl;
v.insert(v.begin()+1, 10);
out_vector(v);
cout << "在倒数第2个元素前插入a[1]~a[5]" << endl;
v.insert(v.end()-2, a+1, a+6);
out_vector(v);
6.删除指定元素
- 1.earse(a.begin()); //删除第一个元素
- 2.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+3(不包括它)
int a[] = {1,2,3,4,4,5,6,7};
vector<int> v(a, a+sizeof(a)/sizeof(int));
out_vector(v);
cout << "删除第3个元素:" <<endl;
v.erase(v.begin()+2);
out_vector(v);
cout << "删除第2~3个元素:" <<endl;
v.erase(v.begin()+1, v.begin()+3);
out_vector(v);
7.交换两个vector
- a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
8.其他几种重要的algorithm库方法
使用时需要包含头文件:#include
- 1.sort(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素进行从小到大排列
- 2.reverse(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素倒置,但不排列,如a中元素为1,3,2,4,倒置后为4,2,3,1
- 3.copy(a.begin(),a.end(),b.begin()+1); //把a中的从a.begin()(包括它)到a.end()(不包括它)的元素复制到b中,从b.begin()+1的位置(包括它)开始,覆盖掉原有元素
- 4.find(a.begin(),a.end(),10); //在a中的从a.begin()(包括它)到a.end()(不包括它)的元素中查找10,若存在返回其在向量中的位置
二.回溯
排列组合数
- 排列,输入一个数组,输出A(n, m)的所有可能
输入的第一行第一个数代表n,第二个数代表m
例如:
输入:
3 2
2 5 6
输出:
2 5
2 6
5 2
5 6
6 2
6 5
- 代码
#include <iostream>
#include <vector>
using namespace std;
/**
* a是原数组,大小是n
* mpt是每次记录的数组,大小是m
* visit是记录是否被访问过,大小是n
* n是a的长度
* m是排列的长度
* cur是mpt遍历到的位置
* pre是上一次mpt[cur]取的位置
* v是输出,记录所有的排列组合
* &v 此处是引用的使用,否则不会改变v
*/
void dfs_rank(int a[], int mpt[], int visit[], int n, int m, int cur, int pre, vector<vector<int> > &v){
// 如果当前遍历到第m个数了,则输出
if (cur == m) {
vector<int> r(m);
for (int i=0;i<m;i++) {
r[i] = a[mpt[i]];
//cout << a[mpt[i]] << " ";
}
//cout << endl;
v.push_back(r);
return ;
}
// 组合,则把遍历的起始位置改成int i = pre,if语句中的pre = i解注释即可
for(int i=0;i<n;i++){
// 如果没有访问过,则使用
if(visit[i] == 0){
// 使用当前可用位置
visit[i] = 1;
// 记录在mpt中
mpt[cur] = i;
// 记录上一次记录在mpt中的位置
pre = i;
// 迭代
dfs_rank(a, mpt, visit, n, m, cur+1, pre, v);
// 当前位置使用完毕,置为可用状态
visit[i] = 0;
}
}
}
int main(){
// 原数组
int a[] = {2,5,6,7,8};
// 数组长度
int n = sizeof(a)/sizeof(int);
// 排列的长度
int m = 3;
// 标记该位置的元素是否被访问过
int visit[n] = {0};
// 存储当前的排列
int mpt[m] = {0};
// 保存所有的排列
vector<vector<int> > v;
// 当前遍历到a中第几个元素了 0<=cur<m
int cur = 0;
// 排列方法中没用,在求组合数时候记录上一次保存的数的位置
// int pre = 0;
// 调用方法,
dfs_rank(a, mpt, visit, n, m, cur, pre, v);
// 打印
for (int i=0;i<v.size();i++) {
for(int j=0;j<v[i].size();j++){
cout << v[i][j] << " ";
}
cout << endl;
}
return 0;
n皇后
- 牛客题目链接:https://www.nowcoder.com/practice/8b5d63163fbe48719f2dfe01fe9f7e54?tpId=8&&tqId=11042&rp=3&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking
任意两个皇后都不能处于同一行、同一列或同一斜线上,请问有多少中摆法。
- 代码
#include <iostream>
#include <cmath>
using namespace std;
/**
* n皇后问题
* 问题描述:任意两个皇后都不能处于同一行、同一列或同一斜线上,请问有多少中摆法。
* 输出可行解的个数
*/
/**
* 判断当前排列是否符合规则
*/
void judge_is_nHuangHou(int mpt[], int n, int &ans){
// f表示符合规则
bool f = true;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
// 如果在同一列或者在同一对角线的元素的表示不符合规则,跳出本层循环
if(mpt[i] == mpt[j] || abs(mpt[i]-mpt[j])==abs(i-j)){
f = false;
break;
}
}
// 不符合规则则跳出循环,跳出外层循环
if(!f)break;
}
// 如果符合规则则计数加一
if(f){
ans ++;
}
}
/**
* 检查当前皇后cur是否可以放在x位置
*/
bool is_place(int mpt[], int cur, int x){
bool f = true;
for(int i=0;i<cur;i++){
if(abs(mpt[i]-x)==abs(cur-i)){
f = false;
break;
}
}
return f;
}
/**
* 递归回溯
* mpt[] 记录每次的记录,表示第i个皇后放在mpt[i]的位置上
* visit[] 表示当前位置是否被访问过
* n为皇后的数量
* cur为当前皇后
* ans为计数器
*/
void dfs_nHuangHou(int mpt[], int visit[], int n, int cur, int &ans){
// 如果皇后全部遍历,则进行判断
if(cur == n){
for(int i=0;i<n;i++){
cout << mpt[i] << " ";
}
cout << endl;
// 判断当前排列是否符合规则
judge_is_nHuangHou(mpt, n, ans);
return ;
}
// 遍历所有的位置
for(int i=0;i<n;i++){
// 如果当前位置还没有被使用
if (visit[i] == 0) {
// 判断当前位置是否符合规则,不符合则跳过当前位置
if(!is_place(mpt, cur, i))continue;
// 使用当前位置
visit[i] = 1;
// cur号皇后放在i位置
mpt[cur] = i;
// 继续查找下一皇后的位置
dfs_nHuangHou(mpt, visit, n, cur+1, ans);
// 使用完成,置为可用
visit[i] = 0;
}
}
}
int main(){
// 皇后的数量
int n = 8;
// 计数器
int ans = 0;
// 记录每次合适的排列位置
int mpt[n] = {0};
// 记录是否访问过
int visit[n] = {0};
// 调用方法
dfs_nHuangHou(mpt, visit, n, 0, ans);
// 输出计数
cout << ans << endl;
return 0;
}
方格填数问题
- 题目描述:
如下的10个格子
填入0~9的数字。要求:连续的两个数字不能相邻。例如1和2不能相邻
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案? - 代码
#include <iostream>
#include <cmath>
using namespace std;
/**
* 方格填数问题
* 问题描述:填入0~9的数字。要求:连续的两个数字不能相邻。(左右、上下、对角都算相邻)
* 输出可行解的个数
*/
/**
* 判断当前排列是否符合规则
*/
void judge_boxfill(int flag[][4], int mpt[][4], int n, int m, int &ans){
// 标记,true为该排列符合规则
bool f = true;
// 4个方向,右,右下,下,左下
int dir[][2] = {{0,1},{1,1},{1,0},{1,-1}};
int len = 4;
// 遍历每个位置
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
// 如果当前位置不可用,则跳过
if(flag[i][j]!=0)continue;
// 遍历每个方向
for(int k=0;k<len;k++){
int dx = i+dir[k][0];
int dy = j+dir[k][1];
// 越界或者位置不可用,则跳过
if(dx<0 || dx>=n)continue;
if(dy<0 || dy>=m)continue;
if(flag[dx][dy] == 1)continue;
// 如果该位置的数和该方向的数相邻,则f置为false不符合规则
if(abs(mpt[dx][dy]-mpt[i][j]) == 1){
f = false;
break;
}
}
// 每层循环都有判断一次,否则会进行多余的循环
if(!f)break;
}
// 每层循环都有判断一次,否则会进行多余的循环
if(!f)break;
}
// 如果符合规则,计数器加一
if(f){
ans++;
// for(int i=0;i<n;i++){
// for(int j=0;j<m;j++){
// cout << mpt[i][j] << " ";
// }
// cout << endl;
// }
// cout << endl;
}
}
/**
* 判断当前位置是否可用
*/
bool is_place_boxfill(int flag[][4], int mpt[][4], int n, int m, int cur, int t){
// 当前位置的行数
int x = cur/m;
// 当前位置的列数
int y = cur%m;
// 四个方向,上右,上,上左,左
int dir[][2] = {{-1,1},{-1,0},{-1,-1},{0,-1}};
int len = 4;
// 遍历每个方向
for(int k=0;k<len;k++){
int dx = x + dir[k][0];
int dy = y + dir[k][1];
// 越界或者位置不可用,则跳过
if(dx<0 || dx>=n)continue;
if(dy<0 || dy>=m)continue;
if(flag[dx][dy] == 1)continue;
// 如果该位置的数和该方向的数相邻,则返回false不可用
if (abs(mpt[dx][dy]-t) == 1) {
return false;
}
}
// 所有方向都可用,则返回true可用
return true;
}
/**
* 递归回溯
* flag[][] 记录位置是否可用 3*4
* visit[] 记录当前的数是否可用,长度为10,例如visit[1] = 0,表示0~9中1是可用的
* mpt[][] 记录当前的排列
* n 为行数
* m 为列数
* cur 当前遍历到第几个位置,从0开始,最多到11,一共有m*n个位置
* ans 计数器
*/
// 这里的二维数组的第二个长度必须给定,否则会报错,为了避免使用指针,这里直接写了字面值
void dfs_boxfill(int flag[][4], int visit[], int mpt[][4], int n, int m, int cur, int &ans){
// 如果cur==12,则mpt已装满
if(cur / m == n){
//judge_boxfill(flag, mpt, n, m, ans);
ans++;
// 打印mpt排列
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cout << mpt[i][j] << " ";
}
cout << endl;
}
cout << endl;
return;
}
// 当前位置的行数
int x = cur/m;
// 当前位置的列数
int y = cur%m;
// 如果该位置可用,则在该位置放0~9中可用的数,否则直接找下一个位置
if (flag[x][y] == 0) {
// 遍历0~9的数
for(int i=0;i<10;i++){
// 如果当前i可用,尝试使用
if(visit[i]==0){
// 剪枝过程,预剪枝
if(!is_place_boxfill(flag, mpt, n, m, cur, i))continue;
// 使用该位置的数,置为不可用状态
visit[i] = 1;
// 存储在排列中
mpt[x][y] = i;
// 递归下一个位置
dfs_boxfill(flag, visit, mpt, n, m, cur+1, ans);
// 该位置使用完毕,置为可用状态
visit[i] = 0;
}
}
}else{
// 不可用,直接跳到下一个位置
dfs_boxfill(flag, visit, mpt, n, m, cur+1, ans);
}
}
int main(){
// 行数
const int n = 3;
// 列数
const int m = 4;
// 标记该位置能否放数
int flag[n][m] = {0};
// 记录该位置的数是否可以使用,1为已使用,0为未使用
int visit[10] = {0};
// 存储每一个可能的排列
int mpt[n][m] = {0};
// 左上角不能用
flag[0][0] = 1;
// 右下角不能用
flag[2][3] = 1;
// 当前遍历到第几个位置,最多到11
int cur = 0;
// 计数器
int ans = 0;
// 调用递归方法
dfs_boxfill(flag, visit, mpt, n, m, cur, ans);
// 输出
cout << ans << endl;
return 0;
}
三.笔试题练习
- (1)求解递推方程的时间复杂度
- (2)
答案:BCD
-
自己定义的数组最大不能超过2M(因为栈的大小为2M)如果非要定义很大的数组 则要分配在堆上,采用malloc函数或者new
可存储数组大小: -
栈:略小于1M大小。
const int nStackSize = 249036; // 这是0.95M
int b[nStackSize]; -
静态存储区(全局变量):全局变量数组大小是2G
const int nGlobalArraySize = 456340275; // 这是1.7G
int arrayG[nGlobalArraySize ]; -
堆:32位程序可以申请的堆大小最大是2G。实际上只能小于2G。而64位程序,如果没有虚拟内存(硬盘)的支持,则可以使用128G的内存(比如说,你有8G内存,就可以使用8G内存)。
-
总结一下,在默认情况下,栈只能得到1M大小的内存,全局静态储存可以得到2G,而在32位和64位下的堆则可以得到2G和无限内存(一般不会用到16T)。
- (3)
- (4)
- (5)
- (6)