讓你的代碼更C++一點(前綴樹示例)

文章和資源同步更新至微信公衆號:算法工程師之路

不知道各位寫C++代碼的童鞋們,有沒有發現一個現象,自己寫的CPP代碼怎麼那麼像C代碼呢?筆者也深有感觸,但是自從C++11標準出現以後,CPP的代碼就開始精簡很多了,風格也極大的發生了變化,今天筆者就開始整理一些C++的新特性,並展示如何在實際應用中使用!讓你的代碼更Cpp些!

1. nullptr

nullptr是爲了補充並替代NULL的,由於之前老版本的NULL定義一般爲0,但有時候又被編譯器定義爲((void*)0)。這樣就會出現混亂,特別是進行函數重載的時候,就會讓編譯器搞不清楚NULL的具體類型,因此,引入nullptr可以更好的區分0和空指針,因此,在新版中,儘量使用nullptr代表空指針進行初始化。

2.初始化列表

使用初始化列表的方式可以極大的簡化構造函數的代碼量,使得程序更加簡潔。

struct TrieNode
    {
        int path, end;
        vector<TrieNode*> children_;
        TrieNode() : path(0), end(0), children_(26, nullptr){}
    };
TrieNode();

3.auto、decltype類型

在C++中最煩的就算是各種類型聲明的編寫,太多字母了,而且有時候也會忘記,由於他們的類型定義太多太亂了!因此C++11中使用auto對數據類型進行自動推倒。新版中,已經棄用了之前有類似功能的register關鍵字,變得更加強大,比如下面例子:

for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)
// 可以改寫爲
for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);

是不是可以方便很多了,但是auto類型有個缺陷的地方,如果我們想要根據某個數或者表達式的類型去定義一個變量,而不進行初始化,那我們使用auto就不行了,所以C++引入了另外一個關鍵字decltype。

int a = 0;
decltype(a) b;  // b的類型爲int
decltype((a)) b = 10; // 多加一層括號,表示一個表達式,此時b類型爲int&, 必須賦初值
decltype(f()) b = 2;  // b的類型爲函數返回值類型,注意函數不運行,編譯器只是經過推理得到其返回值類型

4.範圍for語句

相信學過python的同學都很清楚,在python中經常使用的for語句是for…in…,十分的方便,而在C中for循環是又醜又長,C++標準爲了簡化代碼量,提供了新的範圍for語句:for(auto c : str);

// C風格
for(std::vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) {
    std::cout << *i << std::endl;
}
// C++11
for(auto &i : arr){
	cout << i << " ";  // 加上引用可以爲左值,用於修改
}

是不是瞬間覺得C++好多了,沒有指針了,也沒有很長的類型聲明瞭,這纔是擼代碼的感覺啊!!!

5.智能指針(shared_ptr和make_shared)

我在刷題的時候,由於是參考了JAVA版的,在JAVA中可以靠JVM的垃圾回收機制,特別是考慮到大數據問題,在棧區建立一個鏈表或者樹結構可能會導致空間不夠,因此一般會在堆區進行建立,但是釋放問題是真的很繁瑣!即使new和delete已經比C中的分配內存方便多了,但還是繁瑣,因此我們可以使用智能指針來讓程序自動維護開闢的空間!以防止由於我們不當操作出現內存泄露和野指針的問題!

在C++11中,智能指針包含在< memory >中,分爲shared_ptr、unique_ptr、weak_ptr,其中shared_ptr允許多個指針指向同一個對象,而unique_ptr爲獨佔式的佔有一個對象。最後的weak_ptr爲一個弱引用,指向shared_ptr所管理的對象!

shared_ptr採用引用計數的方式管理所指向的對象。當有一個新的shared_ptr指向同一個對象時(複製shared_ptr等),引用計數加1。當shared_ptr離開作用域時,引用計數減1。當引用計數爲0時,釋放所管理的內存。

由於shared_ptr是一個類模板,因此不可以直接使用指針對其進行賦值!但**一般不建議使用new方法對智能指針初始化,這樣會造成閱讀代碼的困惑!建議使用make_shared函數進行初始化!**當然爲了代碼簡潔,我們可以使用auto類型進行類型推倒!

shared_ptr<int>p = new int(5);  //錯誤
shared_ptr<int>p(new int(5));
shared_ptr<int>p = make_shared<int>(5); //建議
auto p = make_shared<int>(5);

題目:前綴樹

實現一個 Trie (前綴樹),包含 insert, search, 和 startsWith 這三個操作。
示例:
Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true

這次的題目是簡單的實現一個前綴樹的功能,筆者實現了兩個版本的(簡單和複雜),參考了LeetCode中大佬的答案,將代碼優化的更加的CPP,簡單版的題目如上面所示,僅僅實現插入和查找兩個功能!而複雜版可以記錄前綴爲str的字符串的個數,並且支持插入和刪除字符串的操作!主要目的是瞭解如何更加CPP的寫代碼,不再C風格!

具體的前綴樹的操作原理自行百度,很簡單的,就是如何定義每個節點,怎麼進行查找判斷!

class TrieTree{
public:
    TrieTree() : root_(make_shared<TrieNode>()){}

    void insert(string word_){
        auto res = root_;
        for(auto c : word_){  // 範圍for語句,auto類型推導
            if (res->children_[c-'a'] == nullptr){
                res->children_[c-'a'] = make_shared<TrieNode>();  // 智能指針分配內存
            }
            res = res->children_[c-'a'];
        }
        res->isWord_ = true;
    }

    bool search(string word){
        shared_ptr<TrieNode> res = find(word);
        return res != nullptr && res->isWord_ == true;
    }

    bool startsWith(string prefix){
        return find(prefix) != nullptr;
    }

private:
    struct TrieNode
    {
        bool isWord_;
        vector<shared_ptr<TrieNode>> children_;  // 孩子節點的集合
        TrieNode() : isWord_(false), children_(26, nullptr){}  // 初始化列表
    };

    shared_ptr<TrieNode> find(string& prefix){
        auto res = root_;
        for(int i = 0;i < prefix.size() && res != nullptr; ++i){
            res = res->children_[prefix[i] - 'a'];
        }
        return res;
    }
    shared_ptr<TrieNode> root_;  // 智能指針
};

資源分享

以上完整代碼文件(C++版),文件名爲:前綴樹(簡單OR複雜),請關注我的個人公衆號 (算法工程師之路),回覆"左神算法基礎CPP"即可獲得,並實時更新!希望大家多多支持哦~

公衆號簡介:分享算法工程師必備技能,談談那些有深度有意思的算法,主要範圍:C++數據結構與算法/深度學習(CV),立志成爲Offer收割機!堅持分享算法題目和解題思路(Day By Day)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章