面试题4---用两个栈实现队列详解

1.题目

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。

2.笔者解答

大概思路如下图所示:
在这里插入图片描述实现代码如下:

template<typename T>class CQueue
{
 public:
        CQueue(void);
        ~CQueue(void);
        void appendTail(const T& node);
        T deleteHead();
 private:
        stack<T> stack1;
        stack<T> stack2;
}
template<typename T>
void CQueue<T>::appendTail(const T& node)
{
    stack1.push(node);
}
T CQueue<T>::deleteHead();
{
  T temp;
  while(stack1.size>0)
  {
   stack2.push(stack1.top());
   stack1.pop();
  }
  temp=stack2.pop();
  while(stack2.size>0)
  {
    stack1.push(stack2.top());
    stack2.pop();
  }
  return temp;
}

3.涉及知识点

(一)泛型

重载帮助我们实现了同一函数可以实现不同的功能,也可以接收不同类型的参数。比如当我们想写一个swap()交换函数时,通常这样写:

void swap(int& a,int&b)
{
  int temp=a;
  a=b;
  b=temp;
}

这个函数仅仅只能支持int类型,当然,为了让这个函数能够接收更多类型的参数,我们也可以手动重载,但这样耗时耗力,有时可能因为重载不合理而出错。而通过函数模板和类模板来实现泛型编程,便可解决这一问题。
泛型编程是一种语言机制,通过它可以实现一个标准的容器库。像类一样,泛型也是一种抽象数据类型,但是泛型不属于面向对象,它是面向对象的补充和发展。在面向对象编程中,当算法与数据类型有关时,面向对象在对算法的抽象描述方面存在一些缺陷。比如对栈的描述:

class stack
{
  push(参数类型)//入栈算法
  pop(参数类型)//出栈算法
}

如果把上面的伪代码看作算法描述,没问题,因为算法与参数类型无关。但是如果把它写成可编译的源代码,就必须指明是什么类型,否则是无法通过编译的。使用重载来来解决这个问题,即对N种不同的参数类型写N个push和pop算法,这样是很麻烦的,代码也无法通用。那么这里我们可能会想能不能让参数的类型也成为参数呢?显然可以的,那就是我们接下来要讲的泛型。
若对上面的描述进行改造如下:
首先指定一种通用类型T,不具体指明是哪一种类型。
class stack<参数模板>
{
push(T);//入栈算法
pop(T);//出栈算法
}
这是我们可以称class stack<参数模板T>是类的类,通过它可以生成具体参数类型不同的类。这里的参数模板T相当于一个占位符,又或者说是一个类型参数,当我们实例化类stack时,T会被具体的数据类型替换掉。
泛型在C++中的应用
泛型在C++中的主要通过函数模板和类模板来实现泛型编程。
1.函数模板

  • 一种特殊的函数,可通过不同类型进行调用
  • 函数模板是C++中重要的代码复用方式
  • 通过template关键字来声明使用模板
  • 通过typename关键字来定义模板类型
    比如:
template<typename T>//声明使用模板,并定义是一个模板类型
void swap(T& a,T& b)
{
  T c=a;
  a=b;
  b=c;
}

当我们使用int类型参数来调用上面的Swap()时,则T就会自动转换为int类型
函数模板的使用
分为自动调用和显示调用
例如,我们写了一个Swap函数模板,然后在main()函数里写入:

int a=0;
int b=1;
Swap(a,b);//自动调用,编译器根据a和b的类型来推导
float c=0;
float d=1;
Swap<float>(c,d);//显示调用,告诉编译器,调用的参数是float类型

深入理解函数模板

  • 其实编译器对函数模板进行了两次编译
  • 第一次编译时,首先去检查函数本身有没有语法错误
  • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正函数。
  • 所以函数模板其实只是一个模具,当我们调用它是,编译器就会给我们生成真正的函数。
    多参数函数模板
    在我们之前小节学的函数模板都是单参数的,其实函数模板可以定义任意多个不同的类型参数,例如:
template<typename T1,typename T2,typename T3>
T1 Add(T2 a,T3 b)
{
  return static_cast<T1>(a+b);
}

注意

  • 工程中一般都将返回值参数作为第一个模板类型
  • 如果返回值参数作为了模板类型,则必须需要指定返回值模板类型。因为编译器无法推导出返回值类型
  • 可以从左向右部分指定类型参数
//T1=int ,T2=double,T3=double
int r1=Add<int>(0.5,0.8);
//T1=int,T2=float,T3=double
int r2=Add<int,float>(0.5,0.8);
//T1=int,T2=float,T3=float
int r3=Add<int,float,float>(0.5,0.8);

2.类模板
和函数模板一样,将泛型思想应用于类
编译器对类模板处理方式和函数模板相同,都是进行两次编译
类模板通常应用于数据结构方面,使得类的实现不在关注数据元素的具体类型,而只关注需要实现的功能。
比如:数组类,链表类,Queue类,Stack类等。
使用方法
通过template关键字来声明,然后通过typename关键字来定义模板类型,如下图所示:

template<typename T>
class Operator
{
   public:
       T op(T a,T b);
};

类模板的使用
定义对象时,必须指定类模板类型,因为编译器无法推导类型
使用具体类型来定义对象
如下例所示:

Operator<int>op1;
Operator<string> op2;
int i=op1.op(1,2);
string s=op2.op("D.T.","Software");

类模板的工程应用

  • 类模板必须在.h头文件中定义
  • 类模板的成员函数不能分开在不同的文件中实现
  • 类模板外部定义的成员函数,和模板函数一样,还需要加上模板template声明,以及结构体声明。
    参考链接

(二)栈与队列

栈与队列详解链接

4.就提论题

本题思路比较简单,直接上参考答案

template<typename T>void CQUeue<T>::appendTail(const T& element)
{
   stack1.push(element);
}
template<typename T> T CQueue<T>::deleteHead()
{
 if(stack2.size()<=0)
 {
   while(stack1.size()>0)
   {
     T &data=stack1.top();
     stack1.pop();
     stack2.push(data);
   }.
 }
 if(stack2.size()==0)
   throw new exception("queue is empty");
  T head=stack2.top();
  stack2.pop();
  return head;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路的苟狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值