==============================================================================
==============================================================================
含min函数的栈
struct MinStackElement
{
int data;
int min;
};
struct MinStack
{
MinStackElement* data;
int size;
int top;
};
MinStack MinStackInit(int maxSize)
{
MinStack stack;
stack.size = maxSize;
stack.data = (MinStackElement*)intmalloc(sizeof(MinStackElement) * maxSize);
stack.top = 0;
return stack;
}
void MinStackFree(MinStack stack)
{
free(stack.data);
}
void MinStackPush(MinStack stack, int d)
{
if(stack.top == stack.size)
error("Out of stack space!");
MinStackElement* p =stack.data[stack.top];
p->data = d;
p->min = (stack.top == 0 ?d:stack.data[top-1]);
if(p->min > d )
p->min = d;
top++;
}
int MinStackPop(MinStack stack)
{
if(stack.top == 0)
error("Stack is empty");
return stack.data[--stack.top].data;
}
int MinStackMin(MinStack stack)
{
if(stack.top == 0)
error("Stack is empty");
return stack.data[stack.top - 1].data;
}
==============================================================================
==============================================================================
两个栈实现一个队列
==============================================================================
==============================================================================
堆和栈解析 |
堆与栈解析 C++
C++中堆和栈的完全解析
内存分配方面:
堆: 操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码 中的delete语句才能正确的释放本内存空间。我们常说的内存泄露,最常见的就是堆泄露(还有资源泄露),它是指程序在运行中出现泄露,如果程序被关闭掉的话,操作系统会帮助释放泄露的内存。
栈:在函数调用时第一个进栈的主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈,然后是函数中的局部变量。
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
有些说法,把3,4合在一起,也有的把3分成自由存储区(malloc/free)和全局/静态存储区。
这与编译器和操作系统有关。
——————————————————————————————————
在进行C/C++编程时需要程序员对内存的了解比较精确,经常使用到的内存有以下几种:
栈:由编译器自动分配和释放,存放函数的参数值、局部变量的值,操作方式类似于数据结构中的栈
堆:一般由程序员分配和释放,与数据结构中的堆是两码事,操作方式类似于链表
全局区(静态区):全局变量和静态变量的存储时放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量在一块区域,程序结束后由系统释放
文字常量区:常量字符串
程序代码区:程序的二进制代码
用代码解析:
[cpp] view plaincopy
- <span style="font-size:24px;">int a=0;//全局初始化区
- char *p1;//全局未初始化区
- main()
- {
- int b;//栈
- char s[]="aaa";//栈
- char *p2;//栈
- char *p3="bbb";//p3在栈,“bbb”在常量区
- static int c=0;//全局初始化区
- p1=(char*)malloc(10);//堆
- strcpy(p1,"123");//123 在常量区
- }
- </span>
区别:
1,栈由系统自动分配和释放,堆由程序员申请并释放
2,只要栈的剩余空间大于所申请的空间,系统就分配,否则,报异常
对于堆,操作系统有一个记录空闲内存地址的链表,当系统收到申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲区链表中删除,将该节点的空间分配给程序,若是找到的节点地址空间大于申请的大小,系统会把剩余的节点空间重新添加到内存空闲区链表中
3,对于栈,在window下,栈是向低地址扩展的数据结构,是一块连续的区域,栈的大小是2MB,如果申请的空间超过栈的剩余空间,将提示栈溢出
对与堆,是向高地址扩展的数据结构,且不连续,堆的大小受限于计算机系统的虚拟内存,堆获得的空间比较大,也比较灵活
4,申请效率,栈由系统分配,速度快,程序员无法控制。堆由程序员分配,速度慢,容易产生碎片,用起来方便
5,内容,栈在函数调用时,参数由右往左入栈,然后是局部变量,静态变量不入栈 出栈时,局部变量先出栈,然后是参数。
堆一般用堆的头部用一个字节存放堆的大小,便于delete或者free
对于如下函数:
[cpp] view plaincopy
- void fun(int param1,int param2,int param3)
- {
- int var1=param1;
- int var2=param2;
- int var3=param3;
- {
- other code;
- }
- }
当调用函数时,栈是从高地址向低地址分布,EBP是栈底指针,ESP是栈顶指针,参数从右往左入栈,先压param3,然后是param2,最后是param3,然后是函数的返回地址入栈,
进入函数后函数地址入栈,EBP入栈,然后把ESP值给EBP,再接着是局部变量入栈,即var1入栈,var2入栈,var3入栈,按照声明的顺序存放在EBP-4,EBP-8,EBP-12的位置
函数调用:调用一个函数时,先将堆栈原先的基址(EBP)入栈,以保存之前任务的信息。然后将栈顶指针的值赋给EBP,将之前的栈顶作为新的基址(栈底),
然后在这个基址上,开辟相应的空间用作被调用函数的堆栈。函数返回后,从EBP中可取出之前的ESP值,使栈顶恢复函数调用前的位置;再从恢复后的栈顶可弹出之前的EBP值(已入栈),因为这个值在函数调用前一步被压入堆栈。这样,EBP和ESP就都恢复了调用前的位置,堆栈恢复函数调用前的状态。
==============================================================================
==============================================================================
32 - 两个队列实现一个栈 |
两个队列实现一个栈
题目:
说明如何用两个队列来实现一个栈,并分析有关栈操作的运行时间。
解法:
1.有两个队列q1和q2,先往q1内插入a,b,c,这做的都是栈的push操作。
2.现在要做pop操作,即要得到c,这时可以将q1中的a,b两个元素全部dequeue并存入q2中,这时q2中元素为a,b,对q1再做一次dequeue操作即可得到c。
3.如果继续做push操作,比如插入d,f,则把d,f插入到q2中,
4.此时若要做pop操作,则做步骤2
5.以此类推,就实现了用两个队列来实现一个栈的目的。
注意在此过程中,新push进来的元素总是插入到非空队列中,空队列则用来保存pop操作之后的那些元素,那么此时空队列不为空了,原来的非空队列变为空了,总是这样循环。
对于push和pop操作,其时间为O(n).
#include<iostream>
#include <stack>
#include <assert.h>
using namespace std;
// 两个队列实现一个栈
template<typename T> class CStack
{
public:
CStack() {}
~CStack() {}
void mypush(constT& element);
void mypop();
private:
deque <T> m_queue1;
deque <T> m_queue2;
};
template<typenameT> void CStack<T>::mypop()
{
if (m_queue1.size() == 0)
{
while (m_queue2.size() > 1)
{
T& data = m_queue2.front();
m_queue2.pop_front();
m_queue1.push_back(data);
}
assert(m_queue2.size() == 1); //确保队列2内有一个元素
T& result = m_queue2.front();
m_queue2.pop_front();
cout << result << endl;
}
else if(m_queue2.size() == 0)
{
while (m_queue1.size() > 1)
{
T& data = m_queue1.front();
m_queue1.pop_front();
m_queue2.push_back(data);
}
assert(m_queue1.size() == 1); //确保队列1内有一个元素
T& result = m_queue1.front();
m_queue1.pop_front();
cout << result << endl;
}
}
template<typename T> void CStack<T>::mypush(const T& element)
{
if (m_queue1.size() > 0)
{
m_queue1.push_back(element);
}
else if (m_queue2.size() > 0)
{
m_queue2.push_back(element);
}
else
{
m_queue1.push_back(element);
}
}
int main()
{
CStack<int> myStack;
myStack.mypush(1);
myStack.mypush(2);
myStack.mypush(3);
myStack.mypop();
myStack.mypush(4);
myStack.mypop();
cout <<"Hello world!" << endl;
return 0;
}
==============================================================================
==============================================================================
32 - 两个栈实现队列 |
———————————————————————————————————————
出这道题,主要考察3点:
1. 在短时间内,能不能找到解决这道题的足够清晰的思路(思维是否敏捷、清晰)。
2. 能不能在单向表述中,清楚地描述自己的思路和想法(表述能力是否达到要求)。
3. 对于某些具体细节,能不能考虑到(是否足够细致)。
- 有一个细节是可以优化一下的。即:在出队时,将s1的元素逐个“倒入”s2时,原在s1栈底的元素,不用“倒入”s2(即只“倒”s1.Count()-1个),可直接弹出作为出队元素返回。这样可以减少一次压栈的操作。
- 真正性能较高的,其实是另一个变种。即:
入队时,将元素压入s1。
出队时,判断s2是否为空,如不为空,则直接弹出顶元素;如为空,则将s1的元素逐个“倒入”s2,把最后一个元素弹出并出队。
这个思路,避免了反复“倒”栈,仅在需要时才“倒”一次。但在实际面试中很少有人说出,可能是时间较少的缘故吧。
以上几个思路乍看没什么问题了,但其实还是有个细节要考虑的。其实无论什么方法和情况,都要考虑没有元素可供出队时的处理(2个栈都为空的时候,出队操作一定会引起异常)。在实际写代码时,忽略这些判断或异常处理,程序会出现问题。所以,能不能考虑到这些细节,也体现了个人的素养。
个人感觉,这道题确实有助于我鉴别应聘的人。但对于面试,毕竟还是要看面试者的综合素质,一道(或几道)题定生死不可取。
———————————————————————————————————————
解法
准备两个栈。一个是input, 一个是output。
1、入栈:直接用input.push()
2、出栈:如果output非空。则利用output出栈。
如果output为空。则把input中的元素全部pop至output栈中。再利用output出栈。
说得简单一点就是从input栈进去,从ouput栈出去。明白为什么叫input,output没。
实际上就是利用output栈把出去的顺序颠倒一下。
略微可以优化的,就是在pop的时候,如果要把input全部倒到output中,可以把最后一个元素,留在input中。最后从input那里pop。
代码 stas.h
[cpp]view plaincopyprint?
- #ifndef _STACK_AS_QUEUE_H
- #define _STACK_AS_QUEUE_H
- #ifndef _NAME_SPACE_BEGIN_
- #define _NAME_SPACE_BEGIN_ namespace stas {
- #define _NAME_SPACE_END_ }
- #endif
- #include <iostream>
- #include <stack>
- _NAME_SPACE_BEGIN_
- template<class T>
- class queue {
- private:
- std::stack<T> input;
- std::stack<T> output;
- private:
- void _pop_input_into_output(const int leave = 0);
- public:
- queue() {}
- ~queue(){}
- const bool empty() const;
- void push(const T &v);
- void pop();
- T &top();
- };
- template<class T>
- void queue<T>::_pop_input_into_output(const int leave) {
- while (input.size() > leave) {
- output.push(input.top());
- input.pop();
- }
- }
- template<class T>
- const bool queue<T>::empty() const {
- return input.empty() && output.empty();
- }
- template<class T>
- void queue<T>::push(const T &v) {
- input.push(v);
- }
- template<class T>
- void queue<T>::pop() {
- if (!output.empty()) output.pop();
- else {
- this->_pop_input_into_output(1);
- output.pop();
- }
- }
- template<class T>
- T &queue<T>::top() {
- if (!output.empty()) return output.top();
- else {
- this->_pop_input_into_output();
- return output.top();
- }
- }
- _NAME_SPACE_END_
- #endif /*end of _STACK_AS_QUEUE_H */
再写个测试的主程序 main.cpp
输入1 x表示把x入队列
输出0表示把队首元素出队。
[cpp]view plaincopyprint?
- #include <iostream>
- #include <string>
- #include <fstream>
- #include "stas.h"
- using namespace std;
- using namespace stas;
- int main(int argc, char *argv[]) {
- stas::queue<int> q;
- while (1) {
- int a, b;
- cin >> a;
- if (a%2) {
- cin >> b;
- q.push(b);
- } else {
- cout << q.top() << endl;
- q.pop();
- }
- }
- return 0;
- }
==============================================================================
==============================================================================
==============================================================================
==============================================================================
==============================================================================
==============================================================================
==============================================================================
==============================================================================