C++進階-map的應用

目錄

1.預備知識

2.map的補充知識

2.1map的插入方式

2.2訪問鍵和值

2.3map::operator[]的補充

2.4另外一些map的成員函數的補充

3.map的應用實踐-力扣刷題-前k個高頻單詞

3.1解法1

3.2解法2

3.3解法3

4.map的應用實踐-力扣刷題-隨機鏈表的復制

4.1C語言解法

4.2C++解法

5.總結



1.預備知識

該講需要用到map的知識,可以見我之前的博客:

C++進階-map-CSDN博客文章瀏覽閱讀484次,點贊13次,收藏7次。我們要更深入了解map就要了解一下pair,pair翻譯成中文就是:一對,的意思,我們在C++官網中可以搜索pair來了解一下:翻譯一下就能知道其大概的意思:簡單來說就是將T1和T2存儲在一起,也就是說map相當于set的區別就是能多存儲一個值,相對于原來的只有T適應的情況就更多了,但是也相對更難學了一些。這講的map是一些比較重要的map的成員函數,還有一部分補充的知識點將會在下講繼續進行講解,好了,這講就到這里,喜歡的可以一鍵三連哦,下講再見! https://blog.csdn.net/2401_86446710/article/details/149344636?spm=1011.2415.3001.10575&sharefrom=mp_manage_link

2.map的補充知識

2.1map的插入方式

上講我講解了一個插入方式就是用emplace_hint的方式進行插入,我們實踐中不可能完全用那種方式進行插入,所以這里就有以下的方式:

#include<iostream>
#include<map>
#include<string>
using namespace std;
//map的插入方式
int main()
{map<string, int> m;//C++98//插入方式1//有名對象的插入//比較常用pair<string,int> p("China", 1);m.insert(p);//插入方式2//構造匿名對象插入m.insert(pair<string, int>("Hunan", 2));//插入方式3//自動推導類型進行插入m.insert(make_pair("Zhuzhou", 3));//C++11//插入方式4//最常用m.insert({ "Tianyuan",4 });//插入方式5//原地構造m.emplace("Hut", 5);//插入方式6//建議使用auto c = m.find("China");m.emplace_hint(c, "Base", 6);//插入方式7//建議使用//使用{}進行插入m.insert({ {"Dictroy",7},{"Phone",8} });return 0;
}

插入方式還有:

用迭代器區間進行初始化,這種方式用得實在是不多,需要用一段map的迭代器區間來進行初始化,這種方式不太常見,建議用我推薦的三種方式。

注意:initializer_list不是和之前一樣直接進行插入了,還是經過了隱式類型轉化的,在一般的題目中知道initializer_list的插入和insert({})的插入和emplace(……,……)的插入即可。

2.2訪問鍵和值

一般情況下我們是用這種方式來訪問鍵和值的:

#include<iostream>
#include<map>
#include<string>
using namespace std;
//map訪問鍵和值
int main()
{map<string, int> m({ {"Apple",1} });//訪問鍵auto c = m.find("Apple");cout << c->first << ' ';//訪問值cout << c->second << endl;return 0;
}

但是有些人可能就想問了,為什么我們不能用m->first進行訪問?

實際上,這個c指的是一個結點,也就是一個鍵值對,這個c是pair類型的我之前講過pair,這個pair里面有first和second用于訪問我們的鍵和值,所以一般情況下我們訪問一個鍵和一個值在map里面最簡便的方式只有這個,而且這個c->first實際上是c.operator->()->first。

那么我們還有沒有其他方式來訪問鍵和值?

以下這個方式有點難理解,不過也可以用來完成目的:

#include<iostream>
#include<map>
#include<string>
using namespace std;
//map訪問鍵和值
int main()
{map<string, int> m({ {"China",1},{"Hunan",2} });auto it = m.begin();while (it != m.end()){cout << (*it).first << ':' << (*it).second << endl;++it;}cout << endl;return 0;
}

這種(*it).first和(*it).second的方式會讓人有點難理解,主要是解引用的問題,我們只要知道如何訪問的即可。

2.3map::operator[]的補充

上講我們講解過:在正常情況下,我們如果傳遞的參數就是key,在這個key存在在map時會返回這個key對應的value(即返回:值),如果不存在就用默認構造,但是這個時候又可以修改,這樣很繞啊。

我在這里可以舉例一下,不然真的很難懂這個函數的行為:

#include<iostream>
#include<map>
#include<string>
using namespace std;
//map::operator[]的行為
int main()
{map<string, int> m({ {"China",2},{"Hunan",3},{"Zhuzhou",4} });//返回:值auto a = m["China"];//插入+默認構造這個鍵的值為0auto b = m["Tianyuan"];//插入+把這個鍵的值置為5m["Hut"] = 6;auto c = m["Hut"];cout << "China:" << a << endl;cout << "Tianyuan:" << b << endl;cout << "Hut:" << c << endl;return 0;
}

那么這樣的運行結果為:

還有一些公司在面試的時候要求講解出這個map::operator[]的實現,我在這里簡單寫一下:

#include<iostream>
#include<map>
#include<string>
using namespace std;
//map::operator[]的模擬實現
namespace td
{template<class Key,class T,class Compare=less<T>>class map{T& operator[](const Key& key){pair<map<string, int>::iterator, bool> ret = insert({ key,V() });return ret.first->second;}};
}

這個bool是判斷鍵是否已經在map對象里面的,然后最后的ret.first->second,其中ret.first就是map<string,int>,然后ret.first->second就是返回對應的鍵的值,要理解指向還需要更深層次的對底層結構的理解。

2.4另外一些map的成員函數的補充

這些就是需要注意的成員函數,首先find,這個是傳入一個key(即鍵),找到對應的鍵所對應的迭代器,否則返回map::end,即返回為nullptr。這個函數日常生活用得也比較多,只是需要注意:如果沒找到的行為是什么。

第二個是count,它接收一個鍵,然后找這個鍵在map對象的數量,在map里面當然是只能取0或1,但是在multimap里面就可以取非負整數,這個也要注意;

后面三個:lower_bound,這個是返回一個迭代器,該迭代器指向鍵不小于?k?的第一個元素;而upper_bound,這個也是返回一個迭代器,該迭代器指向鍵大于?k?的第一個元素;equal_range則是返回一個范圍的邊界,該范圍包括容器中具有等效?k?的所有元素。

這三個函數用得也不是很多,所以這里就不做演示了。

3.map的應用實踐-力扣刷題-前k個高頻單詞

題目鏈接:

692. 前K個高頻單詞 - 力扣(LeetCode)

題目描述:

3.1解法1

這個題目的綜合難度比較高,但是我們可以用map和multimap的性質完成該題:

首先,map可以先實例化為<string,int>類型,之后就直接進行插入,但是插入之前要先判斷是否插入成功,如果沒插入成功還要判斷,但是我們很容易發現如果這種形式,那么解決就很麻煩,直接用operator[]就可以完成上述操作了啊!因為它會判斷是否有,有的話就返回值,沒有的話就插入,插入后還會默認構造,這個時候我們遇到一個就++即可,然后再構造一個multimap對象,這個對象實例化為:<int,string,greater<int>>類型,因為我們需要排升序,所以需要這樣,最后再一個while循環輸出前k個高頻詞即可。

所以最終代碼如下:

//前k個高頻詞解法1
class Solution
{
public:vector<string> topKFrequent(vector<string>& words, int k){map<string, int> countMap;for (auto& str : words){countMap[str]++;}multimap<int, string, greater<int>> sortMap;for (auto& kv : countMap){sortMap.insert({ kv.second,kv.first });}vector<string> v;auto it = sortMap.begin();while (k--){v.push_back(it->second);++it;}return v;}
};

這個解法循環次數太多了,主要是我們無法同時進行兩個map的同時完成,要遍歷兩次所有數據,很麻煩。

3.2解法2

有些人可能會覺得:如果用一個vector<string>進行排序不就可以了。

這個想法確實沒問題,但是主要是我們要知道:算法庫里面的快速排序的原理是進行交換的,所以如果這樣的話,那么就會導致相對次序發生改變,所以我們需要用一個比較穩定的排序算法進行排序,在算法課里面有一個stable_sort:即穩定排序,這個排序剛好可以,但是又出現了新的問題:

如果我們直接用vector<string>的話,沒有vector<string>的比較函數,如果自己寫比較函數就很麻煩,所以我們不能直接在vecor<string>上進行操作,因為無法進行排序,所以我們最好還是用另外一個結構:vector<pair<string,int>>進行排序操作,但是這樣的那個仿函數怎么寫?

我們都知道:這個只要比較每個pair的值就可以了,只是看起來很麻煩而已,所以我們可以這樣寫:

class kvCompare
{
public:bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2){return kv1.second > kv2.second;}
};

最后拷貝這個v[i].first到最后的返回的那個vector<string>對象即可,所以最終代碼如下:

//前k個高頻詞解法2
class kvCompare
{
public:bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2){return kv1.second > kv2.second;}
};
class Solution
{
public:vector<string> topKFrequent(vector<string>& words, int k){map<string, int> m;for (auto& a : words){m[a]++;}vector<pair<string, int>> v(m.begin(), m.end());stable_sort(v.begin(), v.end(), kvCompare());vector<string> r;for (int i = 0; i < k; i++){r.push_back(v[i].first);}return r;}
};

這個代碼的運行時間很快,但是有沒有另外一種不用stable_sort的寫法呢?

當然有!

3.3解法3

我們可以在寫仿函數的時候多判斷一個條件,因為string在定義的時候其實是有重載<的運算符的,也就是我們可以在原來的繼承上加以限制,當kv1.first<kv2.first并且kv1.second==kv1.second的時候再加上||(kv1.second>kv2.second)即可,這樣就能穩定的輸出結果,而且我們只要用sort函數即可:

//前k個高頻詞解法3
class kvCompare
{
public:bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2){return (kv1.second > kv2.second) || (kv1.second == kv2.second && kv1.first < kv2.first);}
};
class Solution
{
public:vector<string> topKFrequent(vector<string>& words, int k){map<string, int> m;for (auto& a : words){m[a]++;}vector<pair<string, int>> v(m.begin(), m.end());sort(v.begin(), v.end(), kvCompare());vector<string> r;for (int i = 0; i < k; i++){r.push_back(v[i].first);}return r;}
};

解法三的運行時間和解法1差不多,但是消耗內存小了很多,喜歡哪個就用哪個吧!

4.map的應用實踐-力扣刷題-隨機鏈表的復制

題目鏈接:

138. 隨機鏈表的復制 - 力扣(LeetCode)

題目描述:

4.1C語言解法

在C語言階段,我講解過這個題目,當時代碼很長,代碼如下:

typedef struct Node Node;
//申請新結點空間
Node* buyNode(int x)
{Node* node = (Node*)malloc(sizeof(Node));node->val = x;node->next = node->random = NULL;return node;
}
//在原鏈表上進行新結點的連接
void AddNode(Node* head)
{Node* pcur = head;while (pcur){//先把原鏈表下一個結點存儲到一個臨時變量里Node* next = pcur->next;Node* newnode = buyNode(pcur->val);//改變鏈表的結點指向newnode->next = next;pcur->next = newnode;pcur = next;}
}
//置random指針
void setRandom(Node* head)
{Node* pcur = head;while (pcur){Node* copy = pcur->next;if (pcur->random)//如果random指針指向為空,我們不需要賦值{copy->random = pcur->random->next;//這個的意思是原結點的random指針指向的結點的地址}//我們需要把pcur一直在原鏈表上pcur = copy->next;}
}
//斷開連接
struct Node* copyRandomList(struct Node* head)
{if (head == NULL){return head;}Node* pcur = head;AddNode(head);setRandom(head);Node* newHead, * newTail;newHead = newTail = pcur->next;while (newTail->next){pcur = newTail->next;newTail->next = pcur->next;newTail = newTail->next;}return newHead;
}

4.2C++解法

這個題目我們也可以用map進行解答,先實例化出一個map<Node*,Node*> c對象,之后我們可以先把原鏈表拷貝一份,就是把每個結點的所有指針和val都拷貝到新鏈表去,要完成這個步驟,我們需要用一個指針為cur,在原鏈表遍歷;在新鏈表上有兩個指針,一個指針是指向尾結點的copytail指針,另外一個指針是新指向的鏈表的頭結點copyhead,然后只要存儲一下每個copytail的地址到c里面,這就完成了第一個步驟:

class Solution 
{
public:Node* copyRandomList(Node* head) {map<Node*, Node*> c;Node* copyhead = nullptr;Node* copytail = nullptr;Node* cur = head;while (cur){if (copytail == nullptr){copyhead = copytail = new Node(cur->val);}else{copytail->next = new Node(cur->val);copytail = copytail->next;}c[cur] = copytail;cur = cur->next;}}
};

后面我們還要處理random指針,我們需要先把cur重新置為head,這個時候我們再讓Node* copy = copyhead;這個時候我們直接再次遍歷,我們需要這樣寫:copy->random=c[cur->random]這樣就解決了,如果cur->random是nullptr,那么這個時候我們就不能copy->random=c[cur->random];了,因為這個傳入nullptr會報錯,所以這個時候就要直接用copy->random=nullptr即可,最后再讓cur=cur->next;copy=copy->next;即可完成。

我們要注意的一點是一定要理解這個:copy->random=c[cur->random];

我們用這題的例子來說明一下:

這里我們的第一步是:c[cur]=copytail;,假設這個時候cur在第一個結點,那么這個時候我們的第一個結點所對應的value就是7這個結點,也就是說把<cur,cur>這種鍵值對存放起來了。(也就相當于把另外一個新鏈表的結點指針作為cur存放起來了)

第二步是copy->random=c[cur->random];這一步相對于之前就好理解多了,直接相當于拷貝一下,把原有結點的random指針再次存放到新的對應的結點上去,所以這個思路就是先拷貝過去,再拷貝回來這種,相對于之前C語言的解法簡單很多,所以最終代碼如下:

class Solution 
{
public:Node* copyRandomList(Node* head) {map<Node*, Node*> c;Node* copyhead = nullptr;Node* copytail = nullptr;Node* cur = head;while (cur){if (copytail == nullptr){copyhead = copytail = new Node(cur->val);}else{copytail->next = new Node(cur->val);copytail = copytail->next;}c[cur] = copytail;cur = cur->next;}cur = head;Node* copy = copyhead;while (cur){if (cur->random == nullptr){copy->random = nullptr;}else{copy->random = c[cur->random];}cur = cur->next;copy = copy->next;}return copyhead;}
};

5.總結

這講內容主要是補充map的使用和定義等等,日后的手動模擬實現map和set都基本上要用到這兩個容器的相關知識,所以學會map和set,之后的很多題目也會變簡單一些。

好了,這講內容就到這里,下講的內容:C++-AVL(平衡二叉查找樹)(難度較高),AVL樹和后面學到的紅黑樹難度會非常之高,也是面試中常考的兩個樹,建議好好的聽(可能我自己也有一些地方不能完全的用口述出來,所以下講內容的圖片會非常多!),喜歡的可以一鍵三連哦,下講再見!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91311.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91311.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91311.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【三維重建工具】NeRFStudio、3D GaussianSplatting、Colmap安裝與使用指南

目錄 一、NeRFStudio安裝1.安裝&#xff08;ubuntu系統&#xff09;2.安裝&#xff08;windows系統&#xff09; 二、安裝tinycudann三、Colmap安裝與使用1. 安裝依賴2. 安裝colmap3.使用colmap3.1 可視化界面使用3.2 Nerfstudio命令行調用Colmap3.3 colmap結果不準時的修復3.4…

Mybatis05-動態sql

一、應用場景MyBatis 的 動態 SQL 是指根據不同的條件動態拼接生成 SQL 語句的能力。它的最大優勢是&#xff1a;避免寫多個 XML 映射語句、避免 SQL 冗余、提升代碼復用性和可維護性。示例1&#xff1a;用戶可以通過勾選框&#xff0c;勾選不同的數據進行批量刪除&#xff0c;…

VSCODE 選中多行 需要同時按住alt鍵才可以

在 VS Code 中&#xff0c;如果你發現 必須按住 Alt 鍵才能選中多行&#xff08;即“列選擇”或“塊選擇”模式&#xff09;&#xff0c;而直接拖動鼠標無法多選&#xff0c;可能是由于以下原因導致的&#xff1a;1. 檢查是否啟用了“列選擇模式”VS Code 默認情況下&#xff1…

2025前端面試真題以及答案-不斷整理中,問題來源于牛客真題

一、 項目內存泄露react與vue的渲染機制有哪些不同react fiber架構vue2與3&#xff0c;為什么用proxy代替defineproperty性能優化有哪些三欄布局實現方式重排與重繪一個對話聊天框如何減少重排&#xff08;我回答的是絕對定位&#xff0c;將聊天框定位在下面&#xff0c;類似于…

雷軍的 IP 革命:人格化力量如何重塑商業規則|創客匠人

小米 YU7 發布會 3 分鐘售罄 20 萬臺的奇跡&#xff0c;撕開了一個時代真相&#xff1a;當商業競爭進入深水區&#xff0c;決定勝負的不再是產品參數&#xff0c;而是創始人 IP 的人格穿透力。雷軍僅憑個人影響力撬動數十億級交易&#xff0c;這絕非偶然&#xff0c;而是人格化…

SpringBoot3:應對C10K并發挑戰的優化指南

嘿&#xff0c;哥們&#xff01;還在為服務的并發量上不去而頭疼嗎&#xff1f;用戶量一上來&#xff0c;CPU、內存就告急&#xff0c;接口響應慢得像蝸牛&#xff1f;別慌&#xff0c;今天我們就來盤一盤&#xff0c;怎么用最新的Spring Boot 3&#xff0c;把服務性能調教到極…

響應式編程入門教程第三節:ReactiveCommand 與 UI 交互

響應式編程入門教程第一節&#xff1a;揭秘 UniRx 核心 - ReactiveProperty - 讓你的數據動起來&#xff01; 響應式編程入門教程第二節&#xff1a;構建 ObservableProperty&#xff1c;T&#xff1e; — 封裝 ReactiveProperty 的高級用法 響應式編程入門教程第三節&#x…

500+技術棧覆蓋:Web測試平臺TestComplete的對象識別技術解析

在用戶界面&#xff08;UI&#xff09;測試領域&#xff0c;傳統的測試工具往往依賴于XPath或CSS選擇器來定位頁面元素。然而&#xff0c;在面對動態變化的界面、多語言支持或是跨越多種技術框架的應用時&#xff0c;這些傳統方法常導致腳本失效&#xff0c;增加了維護成本。 …

研究人員利用提示注入漏洞繞過Meta的Llama防火墻防護

Trendyol應用安全團隊發現了一系列繞過技術&#xff0c;使得Meta的Llama防火墻在面對復雜的提示注入攻擊時防護失效。這一發現引發了人們對現有大語言模型&#xff08;LLM&#xff09;安全措施準備情況的擔憂&#xff0c;并凸顯出在企業日益將大語言模型嵌入工作流程時&#xf…

Shell 腳本系統學習 · 第5篇:多命令順序執行的三種方式詳解(`;`、``、`||`)

在日常的 Linux 運維與腳本編寫中&#xff0c;我們經常需要依次執行多條命令。本篇將帶你徹底搞懂三種命令順序執行方式&#xff1a;;、&& 和 ||&#xff0c;并通過實用示例掌握它們的區別與應用場景。一、為什么要了解多命令執行方式&#xff1f; 在實際運維或腳本編寫…

K8s存儲系統(通俗易懂版)

Kubernetes中存儲中有四個重要的概念&#xff1a;Volume、PersistentVolume PV、PersistentVolumeClaim PVC、StorageClass一、存儲系統核心概念Volume&#xff08;卷&#xff09;定義&#xff1a;Kubernetes 中最基礎的存儲單元&#xff0c;用于將外部存儲掛載到 Pod 中的容器…

小白學Python,標準庫篇——隨機庫、正則表達式庫

一、隨機庫1.隨機生成數值在random庫中可以隨機生成數值的方法有uniform()、random()、randint()、randrange()等。&#xff08;1&#xff09;uniform()方法uniform(參數1, 參數2)方法用于生成參數1到參數2之間的隨機小數&#xff0c;其中參數的類型都為數值類型。示例代碼&…

Qt窗口:菜單欄

目錄 一、窗口預覽 二、菜單欄 快捷鍵 子菜單 分割線 圖標 內存泄露 一、窗口預覽 在前面幾篇文章中&#xff0c;或者說&#xff0c;Qt初學階段&#xff0c;接觸到的都是QWidget&#xff0c;QWidget指控件&#xff0c;往往作為一個窗口的一部分出現。所謂的窗口&#x…

STM32裸機開發(中斷,輪詢,狀態機)與freeRTOS

裸機&#xff1a;沒有操作系統&#xff0c;程序是單流程的&#xff08;比如一個大循環里依次執行各個功能&#xff0c;或者用中斷嵌套處理事件&#xff09;。優點是資源占用極少&#xff08;幾乎不占 RAM/Flash&#xff09;、執行流程直觀&#xff1b;但復雜項目里&#xff0c;…

電腦上如何查看WiFi密碼

打開控制面板>點擊網絡和Internet在查看網絡和共享中心找到網絡狀態和任務點擊進去點擊連接的WLAN在WLAN狀態中點擊無線屬性在無線網絡屬性中點擊安全&#xff0c;點擊顯示字符&#xff08;H&#xff09;就可以顯示密碼了

文心一言4.5企業級部署實戰:多模態能力與Docker容器化測評

隨著大語言模型在企業服務中的應用日益廣泛&#xff0c;如何選擇一款既能滿足多模態創作需求&#xff0c;又具備良好企業級適配性的AI模型成為了關鍵問題。文心一言4.5作為百度最新開源的大模型&#xff0c;不僅在傳統的文本處理上表現出色&#xff0c;更是在多模態理解和企業級…

VUE Promise基礎語法

目錄 異步和同步 異步的問題 new Promise語法 promise的狀態 promise.then() Promise.resolve() Promise.reject() Promise.all() Promise.race() Promise.catch() Promise.finally() 異步和同步 同步模式下&#xff0c;代碼按順序執行&#xff0c;前一條執行完畢后…

用TensorFlow進行邏輯回歸(六)

import tensorflow as tfimport numpy as npfrom tensorflow.keras.datasets import mnistimport time# MNIST數據集參數num_classes 10 # 數字0到9, 10類num_features 784 # 28*28# 訓練參數learning_rate 0.01training_steps 1000batch_size 256display_step 50# 預處…

【HTTP版本演變】

在瀏覽器中輸入URL并按回車之后會發生什么1. 輸入URL并解析輸入URL后&#xff0c;瀏覽器會解析出協議、主機、端口、路徑等信息&#xff0c;并構造一個HTTP請求&#xff08;瀏覽器會根據請求頭判斷是否又HTTP緩存&#xff0c;并根據是否有緩存決定從服務器獲取資源還是使用緩存…

Android 16系統源碼_窗口動畫(一)窗口過渡動畫層級圖分析

一 窗口過渡動畫 1.1 案例效果圖1.2 案例源碼 1.2.1 添加權限 (AndroidManifest.xml) <!-- 系統懸浮窗權限&#xff08;Android 6.0需動態請求&#xff09; --> <uses-permission android:name"android.permission.SYSTEM_ALERT_WINDOW" />1.2.2 窗口顯示…