【考研机试刷题】5 线性数据结构

5 线性数据结构

包括向量,队列和栈

1 向量

普通数组的限制:创建时需要确定大小,不适用于数据量不确定的问题,申请太大会浪费内存,而申请太小可能会访问越界。

向量vector,是可以改变大小的线性序列容器,可以理解为“变长数组”,适用于数据量未知的情况;其内部实现方法为动态分配数组,每次重新分配时会多分配额外的空间,以便适应未来可能的数据增长。

例题5.1 完数和盈数(清华大学复试上机)

题目

题目地址

描述
一个数如果恰好等于它的各因子(该数本身除外)子和,如:6=3+2+1。则称其为“完数”;若因子之和大于该数,则称其为“盈数”。 求出2到60之间所有“完数”和“盈数”。

输入描述:
题目没有任何输入。

输出描述:
输出2到60之间所有“完数”和“盈数”,并以如下形式输出: E: e1 e2 e3 …(ei为完数) G: g1 g2 g3 …(gi为盈数) 其中两个数之间要有空格,行尾不加空格。

示例1
输入:

输出:
按题目要求进行输出即可。

题解
#include <iostream>
#include <vector>
#include <cstdio>
#include <string>
using namespace std;

int main() {
    //由于要存储到最后用来输出的“数组”是动态增长的,所以用vector
    vector<int> Evec;   //完数
    vector<int> Gvec;   //盈数
    for(int num = 2;num<=60;num++){
        int sum = 0;
        for(int i = 1;i<num;i++){   //注意这里因子1仍要算进去,题目只排除了该数本身
            if(num%i == 0){
                sum += i;
            }
        }
        if(sum == num){
            Evec.push_back(num);    //push_back()的使用
        }
        else if(sum > num){
            Gvec.push_back(num);
        }
    }

    //注意输出格式
    printf("E:");
    //使用迭代器(可以理解为指针)访问,输出时要加*
    //访问vector元素也可以使用下标vector[i]和vector.size()
    for(vector<int>::iterator it1 = Evec.begin();it1 != Evec.end();it1++){
        printf(" %d",*it1);
    }
    printf("\n");

    printf("G:");
    for(vector<int>::iterator it2 = Gvec.begin();it2 != Gvec.end();it2++){
        printf(" %d",*it2);
    }
    printf("\n");
}
笔记
  1. 题目数据量未知,因此使用向量vector
  2. vector的初始化、插入删除、下标访问、迭代器访问
  3. 解题细节:1这个因子也算进去;输出细节:注意在哪空格、换行

2 队列

队列的特点是先进先出,适用场景为公平的等待,比如广度优先遍历

例题5.1 约瑟夫问题No.2

题目

n个小孩围一圈,按顺时针编号1,2,…,n,从编号为p开始顺时针报数,从1报到m时,报m的小孩出去,然后下一个小孩再从1开始报数,报到m时再出去,直到所有小孩都从圈中出去,请按出去的先后顺序输出小孩的编号

输入:
每一行:第1个是n,第二个是p,第三个是m。
最后1行:0 0 0。

输出:
按出圈的顺序输出编号,之间以逗号间隔

题解
#include <cstdio>
#include <queue>
using namespace std;

int main(){
    int n,p,m;  //n是小孩数量,p是报数起始位置,m是总共报多少个数截止
    while(scanf("%d%d%d",&n,&p,&m)!=EOF){
        if(n == 0 && p == 0 && m == 0){
            break;
        }
        //队列记录的是报数顺序
        queue<int> childBaoshu;
        //把第一轮的报数顺序排好队
        for(int i = 0,j = 0;j<n;j++){
            //i用来遍历孩子的编号,j用来记录已经遍历的孩子的数量

            childBaoshu.push(i);
            ++i;    //p->p+1->p+2->...->n->1->...->(共n次后为止)
            
            if(i>n){
                i = 1;  //用重置i=1的方法,实现循环遍历
            }
        }

        //接下来开始报数的过程
        int baoshu = 1;//将要报的数,区分学生编号
        while(true){
            int cur = childBaoshu.front();//cur是队首孩子的编号,区分报的数
            childBaoshu.pop();//报完数,队首就出队

            if(baoshu = m){//检查一下刚刚报的是不是m
                baoshu = 1;//如果是,那么下一个孩子报的数重置为1,并且报数的孩子也不需要归队了

                if(childBaoshu.empty()){//只有报数为m时才会减少一个孩子,才需要检查检查是不是最后一个孩子出队
                    printf("%d\n",cur);
                    break;
                }
                else{
                    //接下来还会有孩子继续报数
                    printf("%d ",cur);
                }
            }
            //如果报的数不是m,则报数出队后还要归队
            else{
                baoshu += 1;
                childBaoshu.push(cur);
            }
        }
    }
}
笔记
  1. “公平”的等待,用队列queue
  2. 队列的初始化、增删、访问
  3. 本题用队列记录“报数情况”,而不是简单地按顺序记录小孩编号,并用先出队再入队实现了循环队列

例题5.2 猫狗收容所

题目

只收留猫和狗,收养方式有两种:
第一种是直接收养所有动物中最早进入收容所的。
第二种是选择收养类型,并收养该种动物中最早进入收容所的。

#include <cstdio>
#include <queue>
using namespace std;

struct Animal{
    int num;
    int seq;    //记录动物次序
};

int main(){
    queue<Animal> catQue;
    queue<Animal> dogQue;
    int seq = 0;
    int n;
    scanf("%d",&n);
    for(int i = 0; i<n;i++){
        int method,para;    //para是动物编号
        scanf("%d%d",&method,&para);
        if(method == 1){
            //入队
            if(para > 0){
                //处理狗队列
                Animal dog; //新声明一个dog类,记录参数和序列后push到队列中保存
                dog.num = para;
                dog.seq = seq;
                ++seq;
                dogQue.push(dog);
            }
            else{
                //处理猫队列
                Animal cat;
                cat.num = para;
                cat.seq = seq;
                ++seq;
                catQue.push(cat);
            }
        }
        else{
            //method为2时收养,出队

            if(para==0){
                //采用第一种收养方式,不区分猫狗

                //情况1,无猫无狗
                if(dogQue.empty() && catQue.empty()){
                    continue;//跳过本次循环剩余代码
                }

                //情况2,收养狗
                //2.a 狗非空 猫为空
                //2.b 狗非空 猫非空 狗队首的序列比猫队首的序列小
                else if(catQue.empty()||
                !dogQue.empty() & !catQue.empty() && dogQue.front().seq < catQue.front().seq){
                    printf("%d ",dogQue.front().num);
                    dogQue.pop();
                }
                else{
                    //剩下的情况即为,收养猫
                    printf("%d ",catQue.front().num);
                    catQue.pop();
                }
            }

            else if(para == 1){
                //当para为1时,明确指定领养狗
                if(dogQue.empty()){
                    continue;
                }
                printf("%d ",dogQue.front().num);
                dogQue.pop();
            }

            else{
                //最后剩的情况为:para==-1,明确指定领养猫
                if(cat.empty()){
                    continue;
                }
                printf("%d ",catQue.front().num);
                catQue.pop();
            }
        }
    }
    printf("\n");
}
笔记
  1. 根据题目描述的先进先出,想到队列,且只维护1个队列不易实现所有情况,所以维护两个队列
  2. 队列元素可以为struct,方便加一些需要的标志
  3. 多种情况分支,可以先写出选择分支的大框架,并用注释标上每一种情况,再分别实现

队列适用公平的等待,而栈适用于优先级差别的等待

例题5.4 zero-complexity transposition(上海交大复试上机)

题目

题目地址

简而言之就是用栈实现逆序输出

题解
#include <cstdio>
#include <stack>
using namespace std;

int main(){
    int n;
    while(scanf("%d",&n)!=EOF){
        stack<long long> myStack;
        long long num;  //根据题中数的范围,读取longlong类型的十进制数

        for(int i = 0;i<n;i++){
            scanf("%lld",&num);
            myStack.push(num);
        }
        while(!myStack.empty()){
            printf("%lld ",myStack.top());
            myStack.pop();
        }
        printf("\n");
    }
}
笔记
  1. 栈的应用:逆序输出;栈的使用:初始化、增删、访问,其中只用访问top()与队列不同
  2. 不过本题还要注意,数的范围超过了int,要用64位整型longlong来表示,%lld或%I64d,或用c++的cin进行输入可以无视系统的差异

例题5.5 括号匹配问题

题目

基本思想:左括号压栈,右括号弹栈;
步骤在数据结构里学过,读代码熟悉思路和细节

题解
#include <cstdio>
#include <stack>
#include <string>
#include <cstring>
using namespace std;

int main(){
    char buf[200];
    while(fgets(buf,200,stdin)!=NULL){
        //fgets配合while实现不确定数量的多行读取,cin可能超时

        //用string将输入的一行中最后的换行pop掉
        string str = buf;
        str.pop_back();

        stack<int> indexStack;//记录了左括号的下标
        string result;//保存输出的结果
        for(int i = 0;i<str.size();i++){
            if(str[i] == '('){
                indexStack.push(i);//如果是左括号,则下标入栈
                result.push_back('$');//暂且认为这个左括号是非法的,后面再改
            }
            else if(str[i] == ')'){
                //如果是右括号,则检查左括号下标栈是否为空,若空则该右括号非法
                if(indexStack.empty()){
                    result.push_back('?');
                }
                else{
                    //不空则匹配,将该右括号的判断位置赋为空格,并挽救一个左括号的清白
                    result.push_back(' ');
                    result[indexStack.top()] = ' ';
                    indexStack.pop();//匹配并挽救完1个左括号,将其弹出
                }
            }
            //如果不是括号,则正常赋值空格
            result.push_back(' ');
        }
    }
    printf("%s\n%s\n",str.c_str(),result.c_str());
}
笔记
  1. fgets()的使用,配合while实现不确定数量的多行读取,用getline/ cin可能超时
  2. 左括号压栈,指的是左括号在原字符串中的下标压栈
  3. “暂且认为左括号非法”之后再挽救的思想,先赋值$之后再赋回空格
  4. 字符串string也可以用向量vector的函数push_back()来添加字符

习题5.1 堆栈的使用

题目

题目地址

题解
#include <stack>
#include <cstdio>
#include <iostream>
using namespace std;

int main(){
    int n;
    while(cin>>n){
        stack<int> myStack;
        for(int i = 0;i<n;i++){
            char input;
            int inputnum;
            cin>>input; //消停用cin吧,
            if(input == 'P'){
                cin>>inputnum;  //cin会自动略过第一个分隔符
                myStack.push(inputnum);
            }

            else if(input == 'O'){
                if(!myStack.empty()){
                    myStack.pop();
                }
                else{
                    continue;
                }
            }

            else if(input == 'A'){
                if(myStack.empty()){
                    printf("E\n");
                }
                else{
                    printf("%d\n",myStack.top());
                }
            }
        }
    }
    return 0;
}
笔记
  1. cin的使用,忽略开头的分隔符,并且不对结尾的换行做处理,因此在本题中能保证当一行输入为“P 3”时,能略过3前面的空格和3后面的换行,也就避免了scanf向字符input输入时要同时处理前后的分隔符的麻烦
  2. 其他,栈的使用、分支判断倒是不难
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值