正式學習編程也就1年而已,在這1年里,要學習C/C++,Java,C#這些主流語言,還要熟悉JavaScript,HTML,CSS這些前端開發知識,加上一些Android應用軟件,網站站點的開發工作,導致我現在就是一個大雜燴,什么都知道一點,但又什么都不精通。現在又面臨畢業找工作壓力,不知道自己應該找什么工作,畢竟自己好像什么都碰過,心浮氣躁,原本基礎就是薄弱,還要在這段日子頂着壓力,將手頭上的項目努力完成,畢竟開發軟件不難,但維護軟件特別難,像是已經發布的網站,現在面臨服務器被攻擊而無法正常運行的問題。果然還是那句行內的老話:當軟件正式上線運行的時候,真正麻煩的事情才正式開始啊!!
相信這也是現在應屆畢業生的現狀,困擾着是要准備筆試和面試,還是利用這短短的一個學期努力做出作品來。當然,兩者都可以兼顧,可惜我們都不是那種游刃有余的人,尤其是像我們這樣臨時過來的碼農臨時工。。。
我學習的第一門語言是Java,雖然不精通,但算得上熟悉,無奈現在大部分的筆試和面試都是考察C/C++,尤其是指針,畢竟像是數據結構這些東西,很多都是用指針實現的。指針是我們學習C/C++的鬼門關,我剛從Java過來的時候,一看到指針就頭疼,現在依然頭疼,即使知道一些指針的高級技巧,像是一些看似非常強大的一句多重指針的代碼濃縮好幾行語句這類的東西,我是不支持的,也不認為自己能寫好,想起C#的編程宗旨:能夠朗讀的代碼,我就對指針有一種天生的恐懼:自己是否用錯了指針,該指針是否空指針,所指向的內存是否被釋放掉。。。在看一些C/C++的源碼時,我就經常為那些變量名所困擾:大量的宏定義導致我無法從變量名推敲出它的真正意思!經常需要翻閱頭文件查找相關的宏定義才能知道這個變量和類型是什么,也對那些意義不明的英文單詞縮寫的函數名感到頭疼,基本函數庫,像是字符串庫還好,縮寫還是很到位的,但一些第三方庫就不敢恭維了。我情願函數名寫得長一點,也不願為了所謂的短小寫出不精悍的函數名出來。
我是一個學藝不精的碼農,自認自己沒有天賦,包括努力的天賦,可悲的是,努力也是需要天賦的,有些人就是非常懂得努力,他們能夠迅速的掌握努力的技巧並找到持續努力的動力,這些人大部分都是以興趣為起點,但像是我們這類的平庸碼農,可能就是為了一碗飯碗。。。
不管怎樣,我們都已經開始啟動了,是慢跑,還是快跑,都已經不重要,最重要的是,能否堅持到終點,像是一些人,跑得很快,但也很快就沒有體力倒下了,一些人,就算是慢跑,也是跑着跑着就沒了,還在跑的,有哭的,也有笑的,更多的是像我們一樣在死磕着。。。
畢竟,人生其實就是一場怎么也看不到終點的無限期馬拉松,對個人而言,只有死亡才能讓我們從這場比賽中脫身,但有誰知道死后是否還要在另一個世界中繼續跑呢?
言歸正傳,棧和隊列也是非常常見的數據結構,它們本身的特點就非常適合用來解決一些實際的問題。
棧對於學習計算機的人來說,是再熟悉不過的東西了,很多東西都需要用棧來存儲,像是操作系統就會給每個線程創建一個棧用來存儲函數調用時各個函數的參數,返回地址及臨時變量等,函數本身也有一個函數棧用來存儲函數的局部變量等。
棧的特點就是后進先出,需要O(N)的時間才能找到棧中的最值。
隊列和棧剛好相反,是先進先出,表面上和棧是一對矛盾體,但實際上,都可以利用對方來實現自己。
題目一:用兩個棧實現一個隊列,並分別實現在隊列尾部插入結點和在頭部刪除結點的功能。
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& 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; }
題目二:定義棧的數據結構,在該類型中實現一個能夠得到棧的最小元素的min函數,在該棧中,要求調用min,push和pop的時間復雜度都是O(1)。
template <typename T> void StackWithMin<T> :: push(const T& value) { m_data.push(value); if(m_min.size() == 0 || value < m_min.top()) { m_min.push(value); } else { m_min.push(m_min.top()); } } template <typename T> void StackWithMin<T> ::pop() { assert(m_data.size() > 0 && m_min.size() > 0); m_data.pop(); m_min.pop(); } template <typename T> const T& StackWithMin<T> :: min() const { assert(m_data.size() > 0 && m_min.size() > 0) return m_min.top(); }
其中,m_data是數據棧,而m_min就是輔助棧,而assert函數就是斷言。所謂的斷言,就是我們會提出一種假設,像是這樣,就假設數據棧和輔助棧的大小都是大於0,這是用於測試使用,當然,我們也可以使用一般的if語句來代替。
棧的彈出和壓入是棧最重要的兩個基本動作,也是經常要被考察的知識點。
題目三:輸入兩個整數序列,第一個序列表示壓棧順序,判斷第二個序列是否是彈出順序。
bool IsPopOrder(const int* pPush, const int* pPop, int nLength) { bool bPossible = false; if(pPush != NULL && pPop != NULL && nLength > 0) { const int* pNextPush = pPush; const int* pNextPop = pPop; std :: stack<int> stackData; while(pNextPop - pPop < nLength) { while(stackData.empty() || stackData.top() != *pNextPop) { if(pNextPush - pPush == nLength) { break; } stackData.push(*pNextPush); pNextPush++; } if(stackData.top() != *pNextPop) { break; } stackData.pop(); pNextPop++' } if(stackData.empty() && pNextPop - pPop == nLength) { bPossible = true; } } return bPossible; }
考察棧並不一定考察我們是否會編寫關於棧的代碼,由於棧和計算機的內存存儲方式有關,所以也會有關於這些的基本知識。
在計算機為一個程序段分配內存的時候,全局變量和靜態變量分配的內存是連續的,並且是存放在數據段中。對於一個進程的內存空間而言,可以在邏輯上分為3個部分:代碼區,靜態數據區和動態數據區,動態數據區就是我們常說的堆棧。棧和堆是兩種不同的動態數據區,棧是一種線性結構,而堆是一種鏈式結構。進程中的每個線程都有自己的棧,因此即使每個線程的代碼是一樣的,但是它們的局部變量是互不干擾的。