基础算法之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皇后

#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

  1. 自己定义的数组最大不能超过2M(因为栈的大小为2M)如果非要定义很大的数组 则要分配在堆上,采用malloc函数或者new
    可存储数组大小:

  2. 栈:略小于1M大小。
    const int nStackSize = 249036; // 这是0.95M
    int b[nStackSize];

  3. 静态存储区(全局变量):全局变量数组大小是2G
    const int nGlobalArraySize = 456340275; // 这是1.7G
    int arrayG[nGlobalArraySize ];

  4. 堆:32位程序可以申请的堆大小最大是2G。实际上只能小于2G。而64位程序,如果没有虚拟内存(硬盘)的支持,则可以使用128G的内存(比如说,你有8G内存,就可以使用8G内存)。

  5. 总结一下,在默认情况下,栈只能得到1M大小的内存,全局静态储存可以得到2G,而在32位和64位下的堆则可以得到2G和无限内存(一般不会用到16T)。

  • (3)
    在这里插入图片描述
  • (4)
    在这里插入图片描述
  • (5)

在这里插入图片描述

  • (6)
    在这里插入图片描述
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值