408—栈,队列和数组

stl里面的栈中没有清空的函数, 需要自己编写函数清空(while循环返回pop), 更常见的做法是重新定义一个栈,这样的时间复杂度就是O(1)

栈的基本操作,出栈入栈判空和求栈的长度,和取栈顶元素以及清空

出栈和取栈顶元素是需要判空的,否则可能会出现段错误

要清楚的知道栈和队列顶指针在哪里, 以及队尾指针又在哪里;

队列队首指针front指向队首元素的前一个位置(如果为空,栈也是会先指向外面)(不然出队的时候,出队次数和元素个数不等,出队次数会比元素个数少一,且对内元素的个数刚好等于两指针之差),和一个队尾指针rear指向队尾元素(和栈一样都是先++再放)

因此取队首元素的时候实际上是return q[front+1];而取队尾元素则是return q[rear];

栈和队列一样,取元素和出元素的时候必须先判断空,否则会出现段错误

栈和队列一样,stl中没有清空函数,需要自己定义

选择题:

1.

选B

数据结构包括三方面 :逻辑结构,存储结构和数据的运算

什么是逻辑结构?

逻辑结构指数据元素之间的逻辑关系,分为线性结构和非线性结构

什么是线性结构和非线性结构?

线性结构是一个有序数据元素集合

1.集合中必存在唯一的一个"第一个元素";

2.集合中必存在唯一的一个"最后的元素";

3.除最后元素之外,其它数据元素均有唯一的"后继";

4.除第一元素之外,其它数据元素均有唯一的"前驱"。

数据结构中线性结构指的是数据元素之间存在着“一对一”的线性关系的数据结构。

如(a0,a1,a2,.....,an),a0为第一个元素,an为最后一个元素,此集合即为一个线性结构的集合。

相对应于线性结构,非线性结构的逻辑特征是一个结点元素可能对应多个直接前驱和多个后继

最典型的线性结构是线性表,集合,树和图都是典型的非线性结构

线性结构又可以分成一般线性表,受限线性表(栈和队列以及串),线性表的推广(数组)

因此可以说栈和队列具有相同的逻辑结构,都属于受限线性表,也都属于逻辑结构中的一种,线性结构,顺便一说数组是逻辑结构而不是存储结构。

什么是存储结构?

存储结构是数据结构在计算机中的表示

数据的存储结构是数据的逻辑结构在计算机的表示,数据的存储结构分为顺序存储和链式存储,以及索引存储和散列存储

什么是顺序存储,链式存储和索引存储以及散列存储?

顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元,元素与元素之间的关系通过存储单元的邻接来体现

链式存储:不要求逻辑上相邻在物理位置上也相邻,借助元素存储地址的指针来表示元素之间的逻辑关系

索引存储:存储元素信息的同时,建立附加的索引表,通过索引表找到元素

散列存储:通过元素的关键词直接计算出元素的存储地址,又称哈希存储

什么是数据的运算?

数据的运算包括运算的定义和实现,运算定义针对逻辑结构,指出运算的功能,运算的实现针对存储结构,指出运算的具体操作步骤

因此虽然栈和队列均属于同一个逻辑结构(线性结构中的受限线性表),但是它们运算的功能和运算的步骤都不相同,即数据的运算不同。

出自王道P2

2.

栈是限制存储点的线性结构

3.

选C

因为ABD都差不多,都可以指到表头,且删除表尾结点,就是说更适合而已,其实都可以作为链栈

4.

选C

若P3 = 1,则输入序列为P1, P2, 1 ,P4, ..., Pn, 由输出序列可知,p1,p2,p3先进,

  1. p3出,然后p2p1可以接着出,那么p1的值可以是3,所以D错误

  1. 若p1 = 2,p1, p2先进,p3出,p3接下来的输出序列就是2了,p2堵住了p1, 所以p1不可能为2

题目给出了讨论点p3,应该根据p3的左右两侧进行讨论,明显的,只有p3的左右两侧的值可能是2

5.

选C

C语言标识符的要求就是只能英文字母,数字和下划线组成,但是首字符不能是数字,可以是字母或者下划线

根据公式可以得知,3个字符可以得到栈的5种输出结果,分别是

  1. n1_(连续进出三次)

  1. 1n_(进进出出进出)

  1. _1n(进进进出出出)

  1. 1_n(进进出进出出)

  1. n_1(进出进进出出)

其中2和4均属于非法的标识符,即只有三种

6.

选C

给出入栈顺序,和出栈顺序(入队顺序是先入先出,因此入队顺序就是出栈顺序)

7.

选C

从讨论点的两侧进行讨论

若p1 = 2,即3旁边的2先出,则此时栈里面只有1,此时可以入栈4到n,可以选择出栈1,或者4

到n的任何一个出栈,因此,p3可以等于1,或者4到n

若p1 = 4,即3旁边的4先出,那么p3可以为2,即432

综上所述,p3可以为除了3以为的任何数

8.

采用非递归的方法重写递归程序时,必须使用栈

错误

深度优先搜索的题也可以用广度优先搜索来做呀

栈内存:栈内存在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配

可以使用栈模拟递归,可以避免程序因栈内存溢出而程序崩溃,但是模拟递归不一定要用栈

大题:

1.

思路:

利用栈的性质,入栈顺序与出栈顺序相反且对称,如果该链表中心对称,则前半部分的顺序相反应该等于后半部分,因此可以将前半部分序列入栈,然后出栈和后半部分的序列对比,如果都相等则说明中心对称。

代码:

#include<iostream>
#include<stack>
using namespace std;

struct node{
    char data;
    node *next;
};


node* Create(char a[], int n) {
    node *head = new node;
    node *p = head;
    for(int i = 0; i < n; i++) {
        node *q = new node;
        q->data = a[i];
        p->next = q;
        p = q;
    }
    p->next = NULL;
    return head;
} 

void Print(node *head) {
    node *p = head->next;
    while(p != NULL) {
        if(p  != head->next)
            cout << " ";
        cout << p->data;
        p = p->next;
    }
    cout << "\n";
}



bool CentralSymmetry(node *head, int n) {
    stack<char > st;
    node *p = head->next;
    int cnt = n / 2;
    for(int i = 0; i < cnt; i++) {//将左侧的值都放入栈中; 
        st.push(p->data);
        p = p->next;
    }
    if(n % 2 != 0 && p != NULL)//如果序列是奇数序列的话,中间哪个数不算,先走一步; 
        p = p->next;
    while(p != NULL) {
        if(p->data == st.top()) {//出栈顺序和入栈顺序相反且对称; 
            p = p->next;
            st.pop();
        } else 
            return false;
    }
    return true;
}
 
int main() {
    int n = 6;
    char a[n] = {"xyxxyx"};
    node *head = Create(a, n);
    Print(head);
    if(CentralSymmetry(head, n) == true)
        cout << "Yes";
    else 
        cout << "No";
    return 0;
}

队列

选择题:

1.

选D

因为n不是队列的最大元素个数,n+1才是,这个就是坑点,且要保持循环不要超过边界,所以(rear+1) mod (n+1)。

因此一定要仔细看n指的是什么!!!

2.

选B

要作为队列最基本的是要对第一个元素和最后一个元素进行操作,不选A的原因是因为A的效果和B一样,但是A还得保持循环的结构,更麻烦一些,因此不选A。

3.

选D

坑点:如果队列只剩一个元素的时候,对队列进行删除操作则头尾指针都有可能会被修改。

if(front->next == NULL) {
    front = front->next;
    rear = NULL;
}

4.

选A

插入是可以直接插入,因为只设置了头指针,是没有头结点和尾指针的,因此node->next = head即可,O(n)是因为要保持循环的结构要找到链表的最后一个元素,rear->next = node, 这个过程是O(n),

因此时间复杂度是O(n)。

5.

选B

题目已经说了一般情况下rear指向队尾元素,front指向队首元素

因为循环队列入队需要经过一个 rear = (rear+1) mod n, 所以就得出rear的初始值应该为n - 1

因为入队和front没啥关系,现在是什么值原来就是什么值,因此front的初始值为0。

6.

选C

这一题是已知输出序列,求多队列的入队顺序

要点:

高序号应该放到低序号后面,没有比他低的就自己占一条轨道线,这样保证低序号先出队。

同理如果想得到987654321的输出顺序,保证的点相反过来

低序号放在高序号后面,没有比他高的就自己先占一个轨,保证高序号先输出

轨道:

A : 8421

B : 53

C : 96

D : 7

可以得到递减的输出序列

大题:

代码:

#include<iostream>

using namespace std;
struct node{
    node *next;
    int data;
};

//这里要用指针的引用,因为传入函数的rear和front的实参本身是局部变量,在函数中的改变会影响指针指向的内容吗,但是不会影响指针的指向; 
void Push(int x, node *head, node *&rear, node *&front) {
    if(rear->next != front) {//队列没有满; 
        rear->data = x;
        rear = rear->next;
    } else {//队列满了就创造空间; 
        node *temp = new node;//作为新的判断依据; 
        rear->data = x;
        rear->next = temp;//插入判断依据; 
        temp->next = front;
        rear = temp;//一直处于满的状态:
        cout << "现在队列已经满了,且创造了新的结点用于存放" << "\n"; 
    }
}

void Pop(node *head, node *&rear, node *&front) {
    if(front != rear) {
        cout << front->data << " ";
        front = front->next;
    } else {
        cout << "\n" <<  "队列已经空了" << "\n"; 
    }
}

int main() {
    int n = 5;
    node *head = new node; 
    node *p = new node;//设p为第一个元素,然后在后面不断地插入和输出; 
    p->data = -1; 
    head->next = p;
    p->next = head;
    node *front = p;
    node *rear = head;
    //只有一个元素时候,此时head作为评判依据; 
    for(int i = 0; i < n; i++)
        Push(i, head, rear, front);
    for(int i = 0; i < n; i++) 
        Pop(head, rear, front);
    Pop(head, rear, front);//原队列一共6个元素; 
    Pop(head, rear, front);//输出队列为空,说明判空成功;
    return 0;
} 

这一题对照着答案和代码看就好了,理解循环链表作为队列的时候如何插入和删除。

栈和队列的应用

选择题:

1.

选D

这里主要是想总结一下

栈常见的四种应用:

  1. 括号匹配

  1. 表达式求值

  1. 递归算法(递归栈和模拟递归站)

  1. 进制转换

这里接受一下栈如何做到实现进制转换

代码:

#include<iostream>
#include<stack>

using namespace std;
//本质就是利用栈模拟基数连除取余
//(一直除基数,商作基数,余数放到一边,最后再将余数倒过来看) 
void conversion(stack<int > &st, int x, int octal) {
    int num;
    while(x) {
        num = x % octal;
        st.push(num);
        x = x / octal; 
    }    
}
//栈的目的就是放余数,输出也能达到倒过来看的效果; 
void print(stack<int> st) {
    while(!st.empty()) {
        cout <<st.top();
        st.pop();    
    }
}

int main() {
    stack<int > st;
    conversion(st, 36, 2);
    print(st); 
    return 0;
}

2.

选B

着重要学会中缀表达式转前缀(波兰式)或者是后缀表达式(逆波兰式)的方法

方法(括号法):

  1. 在每个运算符的两个操作数外加上括号,如a+b-c-> ((a+b)-c)

  1. 将运算符提到括号外(前缀就提到括号前,后缀就提到括号后)

  1. 提一个运算符就将其外面的括号删掉

注意:加括号不能改变原表达式的优先级

如:a * b - c + d,加括号后应该是(((a * b)- c) +d),其优先级没有改变与原来运算顺序相同

而不能是(a * b)- (c - d)改变了原有的运算顺序

3.

选B

选出这题的原因是这一题是前缀表达式转后缀表达式,求出操作数栈的大小,这种题就要使用括号法

括号法就是在

4.

选A

这题是前缀表达式转后缀表达式,求操作符栈的大小,这种题需要使用逐项扫描法

逐项扫描法求后缀表达式,并了解其操作符栈的变化和规则

方法就是:从左到右扫描前缀表达式的每一项,操作数不进入操作码栈,直接输出,操作符按以下规则进行进栈。

操作符的优先级:

(isp是栈内优先级,icp是入栈优先级)

入栈规则:

大于直接进,小于小的进大的弹

1.优先级比栈顶低或者等于才会弹出

2.右括号不会入栈,且会一直弹出,直到遇到左括号和他抵消掉才会停止

3.其他运算符入栈,会将栈顶上优先级比它高的全部弹出,此时栈顶的位置才是该入栈的位置

4.操作数不进入操作符栈,因此直接输出

操作符栈的优先级:

1.乘除不管是入栈还是出栈都大于加减

2.同类操作符栈内优先级大于栈外优先级,因此遇到同类,栈顶弹栈外入

3.左括号无论如何都可以入栈,但是只有右括号可以将其弹出,入栈不会弹人,出栈难

  1. 右括号进来就弹人,直到遇到左括号

5.

选B

这题对操作符栈进行分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值