牛客網C++面經 容器和算法

原文網址

參考網址

  • C語言中文網

請你來說一下map和set有什么區別,分別又是怎么實現的?

  • map和set都是C++的關聯容器,其底層實現都是紅黑樹(RB-Tree)。由于 map 和set所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 map 和set的操作行為,都只是轉調 RB-tree 的操作行為。

map和set區別在于

  • (1)map中的元素是key-value(關鍵字—值)對:關鍵字起到索引的作用,值則表示與索引相關聯的數據Set與之相對就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字
  • (2)set的迭代器是const的,不允許修改元素的值map允許修改value,但不允許修改key。其原因是因為map和set是根據關鍵字排序來保證其有序性的,如果允許修改key的話,那么首先需要刪除該鍵,然后調節平衡,再插入修改后的鍵值,調節平衡,如此一來,嚴重破壞了map和set的結構,導致iterator失效,不知道應該指向改變前的位置,還是指向改變后的位置。所以STL中將set的迭代器設置成const,不允許修改迭代器的值;而map的迭代器則不允許修改key值,允許修改value值。
  • (3)map支持下標操作,set不支持下標操作。map可以用key做下標,map的下標運算符[ ]將關鍵碼作為下標去執行查找,如果關鍵碼不存在,則插入一個具有該關鍵碼和mapped_type類型默認值的元素至map中,因此下標運算符[ ]在map應用中需要慎用,const_map不能用,只希望確定某一個關鍵值是否存在而不希望插入元素時也不應該使用,mapped_type類型沒有默認值也不應該使用。如果find能解決需要,盡可能用find

請你來介紹一下STL的allocaotr??

  • STL的分配器用于封裝STL容器在內存管理上的底層細節。在C++中,其內存配置和釋放如下:
  • new運算分兩個階段:(1)調用::operator new配置內存;(2)調用對象構造函數構造對象內容
  • delete運算分兩個階段:(1)調用對象析構函數;(2)調用::operator delete釋放內存
  • 為了精密分工,STL allocator將兩個階段操作區分開來:內存配置有alloc::allocate()負責,內存釋放由alloc::deallocate()負責;對象構造由::construct()負責,對象析構由::destroy()負責
  • 同時為了提升內存管理的效率,減少申請小內存造成的內存碎片問題,SGI STL采用了兩級配置器,當分配的空間大小超過128B時,會使用第一級空間配置器;當分配的空間大小小于128B時,將使用第二級空間配置器。第一級空間配置器直接使用malloc()、realloc()、free()函數進行內存空間的分配和釋放,而第二級空間配置器采用了內存池技術,通過空閑鏈表來管理內存

參考鏈接

  • 標準庫 STL :Allocator能做什么?
  • C++中std::allocator的使用
  • 第10篇:C++ 堆內存管理器-allocator

?請你來說一說STL迭代器刪除元素

  • 這個主要考察的是迭代器失效的問題。
  • 1.對于序列容器vector,deque來說,使用erase(itertor)后,后邊的每個元素的迭代器都會失效,但是后邊每個元素都會往前移動一個位置,但是erase會返回下一個有效的迭代器;
  • 2.對于關聯容器map set來說,使用了erase(iterator)后,當前元素的迭代器失效,但是其結構是紅黑樹,刪除當前元素的,不會影響到下一個元素的迭代器,所以在調用erase之前,記錄下一個元素的迭代器即可。
  • 3.對于list來說,它使用了不連續分配的內存,并且它的erase方法也會返回下一個有效的iterator,因此上面兩種正確的方法都可以使用。

請你說一說STL中MAP數據存放形式

  • 紅黑樹。unordered map底層結構是哈希表

請你講講STL有什么基本組成

  • 容器、算法、迭代器、函數對象、適配器、內存分配器這 6 部分構成,其中后面 4 部分是為前 2 部分服務的
STL的組成含義
容器一些封裝數據結構的模板類,例如 vector 向量容器、list 列表容器等。
算法STL 提供了非常多(大約 100 個)的數據結構算法,它們都被設計成一個個的模板函數,這些算法在 std 命名空間中定義,其中大部分算法都包含在頭文件 <algorithm> 中,少部分位于頭文件 <numeric> 中。
迭代器在?C++?STL 中,對容器中數據的讀和寫,是通過迭代器完成的,扮演著容器和算法之間的膠合劑。
函數對象如果一個類將 () 運算符重載為成員函數,這個類就稱為函數對象類,這個類的對象就是函數對象(又稱仿函數)。
適配器可以使一個類的接口(模板的參數)適配成用戶指定的形式,從而讓原本不能在一起工作的兩個類工作在一起。值得一提的是,容器、迭代器和函數都有適配器。
內存分配器為容器類模板提供自定義的內存申請和釋放功能,由于往往只有高級用戶才有改變內存分配策略的需求,因此內存分配器對于一般用戶來說,并不常用。

頭文件

  • <iterator> <functional> <vector> <deque> <list> <queue> <stack> <set> <map> <algorithm> <numeric> <memory> <utility>?

注意事項

  • 分配器給容器分配存儲空間
  • 算法通過迭代器獲取容器中的內容
  • 仿函數可以協助算法完成各種操作
  • 配接器用來套接適配仿函數

參考鏈接

  • C++ STL基本組成(6大組件+13個頭文件)

請你說說STL中map與unordered_map

Map映射

  • map 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。不允許鍵值重復
  • 底層實現:紅黑樹
  • 適用場景:有序鍵值對不重復映射

Multimap

  • 多重映射。multimap 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。允許鍵值重復
  • 底層實現:紅黑樹
  • 適用場景:有序鍵值對可重復映射

請你說一說vector和list的區別,應用,越詳細越好

Vector

  • 連續存儲的容器,動態數組,在堆上分配空間,本質是鏈表
  • 底層實現:數組
  • 兩倍容量增長:
  • vector 增加(插入)新元素時,如果未超過當時的容量,則還有剩余空間,那么直接添加到最后(插入指定位置),然后調整迭代器。
  • 如果沒有剩余空間了,則會重新配置原有元素個數的兩倍空間,然后將原空間元素通過復制的方式初始化新空間,再向新空間增加元素,最后析構并釋放原空間,之前的迭代器會失效。

性能:

  • 訪問:O(1)
  • 插入:在最后插入(空間夠):很快
  • 在最后插入(空間不夠):需要內存申請和釋放,以及對之前數據進行拷貝。
  • 在中間插入(空間夠):內存拷貝
  • 在中間插入(空間不夠):需要內存申請和釋放,以及對之前數據進行拷貝。
  • 刪除:在最后刪除:很快
  • 在中間刪除:內存拷貝
  • 適用場景:經常隨機訪問,且不經常對非尾節點進行插入刪除

List

  • 動態鏈表,在堆上分配空間,每插入一個元數都會分配空間,每刪除一個元素都會釋放空間。
  • 底層:雙向鏈表

性能

  • 訪問:隨機訪問性能很差,只能快速訪問頭尾節點。
  • 插入:很快,一般是常數開銷
  • 刪除:很快,一般是常數開銷
  • 適用場景:經常插入刪除大量數據

區別

  • 1)vector底層實現是數組;list是雙向 鏈表。
  • 2)vector支持隨機訪問,list不支持。
  • 3)vector是順序內存,list不是。
  • 4)vector在中間節點進行插入刪除會導致內存拷貝,list不會。
  • 5)vector一次性分配好內存,不夠時才進行2倍擴容list每次插入新節點都會進行內存申請
  • 6)vector隨機訪問性能好,插入刪除性能差;list隨機訪問性能差,插入刪除性能好。

應用

  • vector擁有一段連續的內存空間,因此支持隨機訪問,如果需要高效的隨即訪問,而不在乎插入和刪除的效率,使用vector。
  • list擁有一段不連續的內存空間,如果需要高效的插入和刪除,而不關心隨機訪問,則應使用list。

請你來說一下STL中迭代器的作用,有指針為何還要迭代器

1、迭代器

  • Iterator(迭代器)模式又稱Cursor(游標)模式,用于提供一種方法順序訪問一個聚合對象中各個元素, 而又不需暴露該對象的內部表示。或者這樣說可能更容易理解:Iterator模式是運用于聚合對象的一種模式,通過運用該模式,使得我們可以在不知道對象內部表示的情況下,按照一定順序(由iterator提供的方法)訪問聚合對象中的各個元素。
  • 由于Iterator模式的以上特性:與聚合對象耦合,在一定程度上限制了它的廣泛運用,一般僅用于底層聚合支持類,如STL的list、vector、stack等容器類及ostream_iterator等擴展iterator。

2、迭代器和指針的區別

  • 迭代器不是指針,是類模板,表現的像指針。他只是模擬了指針的一些功能,通過重載了指針的一些操作符,->、*、++、--等。迭代器封裝了指針,是一個“可遍歷STL( Standard Template Library)容器內全部或部分元素”的對象,?本質是封裝了原生指針,是指針概念的一種提升(lift),提供了比指針更高級的行為,相當于一種智能指針,他可以根據不同類型的數據結構來實現不同的++,--等操作。
  • 迭代器返回的是對象引用而不是對象的值,所以cout只能輸出迭代器使用*取值后的值而不能直接輸出其自身。

3、迭代器產生原因

  • Iterator類的訪問方式就是把不同集合類的訪問邏輯抽象出來,使得不用暴露集合內部的結構而達到循環遍歷集合的效果。

請你說一說epoll原理

  • 調用順序:
  • int epoll_create(int size);
  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
  • 首先創建一個epoll對象,然后使用epoll_ctl對這個對象進行操作,把需要監控的描述添加進去,這些描述如將會以epoll_event結構體的形式組成一顆紅黑樹,接著阻塞在epoll_wait,進入大循環,當某個fd上有事件發生時,內核將會把其對應的結構體放入到一個鏈表中,返回有事件發生的鏈表。

參考鏈接

  • epoll使用詳解

?n個整數的無序數組,找到每個元素后面比它大的第一個數,要求時間復雜度為O(N)

#include <iostream>
#include <vector>
#include <stack>
using namespace std;//n個整數的無序數組,找到每個元素后面比它大的第一個數,要求時間復雜度為O(N)
vector<int> find(vector<int> &num)
{int len = num.size();//空數組,返回空if(len == 0)return {};stack<int> notFind;//棧:num中還未找到符合條件的元素索引vector<int> res(len, -1);//返回結果:初始化-1,表示未找到int i = 0;while(i < len)//遍歷數組{//如果棧空或者當前num元素不大于棧頂,將當前元素壓棧,索引后移if(notFind.empty() || num[notFind.top()] >= num[i])notFind.push(i++);else//有待處理元素,且num當前元素大于棧頂索引元素,符合條件,更新結果數組中該索引的值,棧頂出棧。{res[notFind.top()] = num[i];notFind.pop();}}return res;
}int main()
{vector<int> num = {1, 3, 2, 4, 99, 101, 5, 8};// vector<int> num = {1, 1, 1, 1, 1, 1, 1};// vector<int> num = {};vector<int> res = find(num);for(auto i : res)cout << i << "  ";return 0;
}

請你回答一下STL里resize和reserve的區別

  • resize():改變當前容器內含有元素的數量(size()),eg: vector<int>v; v.resize(len);v的size變為len,如果原來v的size小于len,那么容器新增(len-size)個元素,元素的值為默認為0.當v.push_back(3);之后,則是3是放在了v的末尾,即下標為len,此時容器是size為len+1;
  • reserve():改變當前容器的最大容量(capacity),它不會生成元素,只是確定這個容器允許放入多少對象,如果reserve(len)的值大于當前的capacity(),那么會重新分配一塊能存len個對象的空間,然后把之前v.size()個對象通過copy construtor復制過來,銷毀之前的內存;
  • vector 的reserve增加了vector的capacity,但是它的size沒有改變!而resize改變了vector的capacity同時也增加了它的size!
  • 原因如下: ?reserve是容器預留空間,但在空間內不真正創建元素對象,所以在沒有添加新的對象之前,不能引用容器內的元素。加入新的元素時,要調用push_back()/insert()函數。
  • ?resize是改變容器的大小,且在創建對象,因此,調用這個函數之后,就可以引用容器內的對象了,因此當加入新的元素時,用operator[]操作符,或者用迭代器來引用元素對象。此時再調用push_back()函數,是加在這個新的空間后面的。
  • resize增加大的容量超過reserve,會將capacity和size一樣
  • resize減少容量 不會影響到capacity
  • 通過 shrink_to_fit 將capacity保持和size一致的大小

參考鏈接

  • vector::shrink_to_fit()

測試代碼

#include <iostream>
#include <vector>
using namespace std;
int main() {vector<int> a;a.reserve(100);a.resize(50);cout<<a.size()<<"  "<<a.capacity()<<endl;//50  100a.resize(150);cout<<a.size()<<"  "<<a.capacity()<<endl;//150  150a.reserve(50);cout<<a.size()<<"  "<<a.capacity()<<endl;//150 150a.reserve(200);cout<<a.size()<<"  "<<a.capacity()<<endl;//150  200a.resize(50);cout<<a.size()<<"  "<<a.capacity()<<endl;//50  200a.shrink_to_fit();cout<<a.size()<<"  "<<a.capacity()<<endl;//50  50
}

請你說一說stl里面set和map怎么實現的

  • 集合,所有元素都會根據元素的值自動被排序,且不允許重復。
  • 底層實現:紅黑樹
  • set 底層是通過紅黑樹(RB-tree)來實現的,由于紅黑樹是一種平衡二叉搜索樹,自動排序的效果很不錯,所以標準的 STL 的 set 即以 RB-Tree 為底層機制。又由于 set 所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 set 操作行為,都只有轉調用 RB-tree 的操作行為而已。
  • 適用場景:有序不重復集合
  • map映射。map 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。不允許鍵值重復。
  • 底層:紅黑樹
  • 適用場景:有序鍵值對不重復映射

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

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

相關文章

C語言指針-字符指針整型指針char*s int*a

案例代碼 #include<stdio.h> #include<stdlib.h> #include<string.h> int main() {//字符指針char *pstr"good dog ww";printf("字符指針指向的字符串內容:%s\n",pstr);printf("字符指針本身的地址:%p\n",&pstr);printf…

C++ primer 第10章 泛型算法

文章目錄概述findcount初識泛型算法只讀算法只讀算法accumulate只讀算法equal寫容器元素的算法算法fill算法fill_nback_inserter算法copy算法replace replace_copy重排容器元素的算法sortuniqueunique_copy定制操作向算法傳遞函數謂詞算法stable_sort算法partitionlambda表達式…

C語言常用字符串函數

概括 代碼 #include<stdlib.h> #include<stdio.h> #include<string.h> int main() {//常用字符串函數char a[]"abcSDFbnm";char b[]"SD";printf("a的字符串長度:%d\n",strlen(a));printf("b的字符串長度:%d\n",str…

C++ primer 第11章 關聯容器

文章目錄使用關聯容器map示例關聯容器概述定義關聯容器關聯容器值初始化multimap和multiset關鍵字類型的要求pair類型pair上的操作關聯容器操作關聯容器額外的類型別名關聯容器迭代器map迭代器set迭代器關聯容器和算法添加元素向map添加元素檢測insert的返回值使用insert代替下…

C++ primer 第12章 動態內存

文章目錄前言動態內存與智能指針shared_ptr類shared_ptr和unique_ptr都支持的操作shared_ptr獨有的操作make_shared 函數shared_ptr的拷貝和賦值shared_ptr自動銷毀所管理的對象shared_ptr還會自動釋放相關聯的內存程序使用動態內存出于以下原因直接管理內存使用new動態分配和初…

C語言順序查找二分查找

介紹 順序查找 按照順序一個個查找 #include<stdio.h> //順序查找 int search(int arr[],int len,int aim) {int i;for(i0;i<len;i){if(arr[i]aim){return i;//返回下標 }}return -1;//表示未查詢到} int main() {int arr[]{13,355,256,65,234,-1,35,-6,-3,-4,0};…

C++ primer 第12章 12.3 使用標準庫:文本查詢程序

文章目錄使用標準庫&#xff1a;文本查詢程序文本查詢程序設計數據結構在類之間共享數據自己的文本查詢程序書中的文本查詢程序使用標準庫&#xff1a;文本查詢程序 我們將實現一個簡單的文本查詢程序&#xff0c;作為標準庫相關內容學習的總結。 我們的程序允許用戶在一個給…

C語言二維數組 int arr[2][3]

基礎使用 先遍歷行再遍歷列 #include<stdio.h> //二維數組的基本使用 int main() {//二維數組的初始化int arr1[2][2]{{2,2},{0,0}};int arr2[2][3]{2,2,2,8,8,8};int arr3[6][9];int i,j;for(i0;i<6;i){for(j0;j<9;j){arr3[i][j]1;}}arr3[2][5]0;//打印printf(&…

牛客網C++面經 類和數據抽象

請你來說一下C中struct和class的區別 在C中&#xff0c;可以用struct和class定義類&#xff0c;都可以繼承。區別在于&#xff1a;structural的默認繼承權限和默認訪問權限是public&#xff0c;而class的默認繼承權限和默認訪問權限是private。另外&#xff0c;class還可以定義…

C++ primer 第13章 拷貝控制

文章目錄前言拷貝、賦值與銷毀拷貝構造函數合成拷貝構造函數拷貝初始化和直接初始化拷貝初始化的發生&#xff1a;參數和返回值拷貝初始化的限制拷貝賦值運算符重載賦值運算符合成拷貝賦值運算符析構函數析構函數完成的工作什么時候會調用析構函數合成析構函數代碼片段調用幾次…

C語言 指針自增自減加減運算 p++ p+i

介紹 自增自減代碼 #include<stdio.h> #include<string.h> //指針自增--short void increase(short *arr,int len) {int i;arr&arr[0];for(i0;i<len;i){printf("arr[%d]%d,address%p\n",i,*arr,arr);arr;} }//指針自減--char void decrease(char…

C++ 編譯與底層

原文鏈接 編譯與底層請你來說一下一個C源文件從文本到可執行文件經歷的過程&#xff1f; 對于C源文件&#xff0c;從文本到可執行文件一般需要四個過程&#xff1a;預處理階段&#xff1a;對源代碼文件中文件包含關系&#xff08;頭文件&#xff09;、預編譯語句&#xff08;…

C語言 指針數組-字符指針數組整型指針數組 char*s[3] int*a[5] 數組指針int(*p)[4]

基本介紹 1.指針數組:由n個指向整型元素的指針而組成,里面存放指針 Int *ptr[3]; 2.地址: ptr[i]:元素地址 &ptr[i]:指針地址 圖示 代碼: 內存布局: 代碼 #include<stdio.h> #include<string.h> //指針數組--int void pointer(int *arr,int len) {int …

uninitialized_copy測試代碼示例

原測試代碼如下&#xff1a; int main() {vector<int>v1{1,3,5,7,9,2,4,6,8};allocator<int>alloc;auto data alloc.allocate(9);uninitialized_copy(v1.begin(),v1.end(), data);auto end data 9;while(data!end) {cout << *data <<" "…

C語言的地址 內存

取地址在CPU的寄存器產生&#xff0c;不占據內存地址由計算器總線&#xff0c;地址作為常量不消耗內存指針 存儲不同的地址&#xff0c;間接賦值空類型指針 void* 類型指針 不可以取數據 或者修改數據 需要進行強制類型轉換int num 10;void *p &num;std::cout << …

C語言 多重指針--整型字符字符串 int**pp

介紹 多重指針:一個指針指向另一個指針 離值越近的指針級別越大:一級 內存布局 代碼 圖示: 多重指針–整型 #include<stdio.h> #include<string.h> //多重指針--整型//二級指針 void two() {printf("二級指針:\n");int a896;int *p&a,**pp&…

C++ primer 第13章 拷貝控制

文章目錄前言拷貝、賦值與銷毀拷貝構造函數合成拷貝構造函數拷貝初始化和直接初始化拷貝初始化的發生&#xff1a;參數和返回值拷貝初始化的限制拷貝賦值運算符重載賦值運算符合成拷貝賦值運算符析構函數析構函數完成的工作什么時候會調用析構函數合成析構函數代碼片段調用幾次…

牛客網C++面經 C++11

請問C11有哪些新特性&#xff1f; auto關鍵字&#xff1a;編譯器可以根據初始值自動推導出類型。但是不能用于函數傳參以及數組類型的推導nullptr關鍵字&#xff1a;nullptr是一種特殊類型的字面值&#xff0c;它可以被轉換成任意其它的指針類型&#xff1b;而NULL一般被宏定義…

C語言 返回指針的函數--指針函數 int* max(int a)

定義 strlong示例代碼 代碼1: #include<stdio.h> #include<string.h> //返回指針的函數//比較兩個字符串,返回更長的字符串 char *strlong(char* a,char* b) {char *p1&a[0];char *p2&b[0];while(true){if(*p1\0){return b;}else if(*p2\0){return a;}p1…

第2、3講 圖像的存儲格式

本圖像處理系列筆記是基于B站楊淑瑩老師的課程進行學習整理的。 文章目錄黑白圖像8位灰度索引圖像8位偽彩色索引圖像24位真彩色圖像圖像文件格式BMP文件存儲格式BMP文件頭位圖信息頭顏色表位圖信息——BITMAPINFO結構BMP位圖文件匯總按照顏色深度分類&#xff0c;常用圖像文件&…