面試題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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章