C++数据结构算法

C++ STL(Standard Template Library,标准模板库),提高程序开发效率和复用性。
STL包含6大组件
容器: 用来管理、存储数据的一些数据结构,也叫做集合。
第1种容器:顺序/序列式容器,元素均有固定位置,插入元素,排列次序和插入顺序一致。
1.vector(向量):和数组类似,比数组更高级,可以动态扩展。拥有一段连续的内存空间,随机访问O(1),用下标直接访问。尾部插入也很方便O(1)。
中间插入、删除操作的话,O(n),效率较低,为了保存相对位置不变,需要所有元素都移动。
适用场景:对象简单,变化较小,需要频繁随机访问的场景。
vector的内存管理: 创建一个空对象时,会分配一个初始化大小的内存空间,为0或者预设大小(16)。维护一个capacity(容器),实时查看已分配的内存空间大小。
扩容方式:分配内存空间不足时,会去申请分配一个更大的内存(2倍),把原有数据拷贝到新空间,释放旧空间。
vector的一些操作: size:容器中的实际元素个数;capacity:容器可以容纳的最多的元素个数。
vector里的元素不能是引用,引用没有地址,vetcor元素要求在空间里顺序排列。
vector.clear(), 元素清空,但是vector申请的存储空间不变。capacity不变。
vector.swap(), 两个vector整体都交换,元素内容全部交换,size、capacity也全部交换。(可以用来解决内存释放问题,用一个空vector对象来交换)
vector使用时,会对内存造成严重浪费,一直在成倍申请,不会释放。要等到vector析构才会释放。
vector.shrink_to_fit(), 用来解决内存释放问题,将capacity置为0.

vector.resize(): 改变当前容器中的size个数,超过的话新增初始为0;小于的话则截断。
vector.reserve(): 改变当前容器的最大容量(capacity), 超过的话,重新分配内存,销毁之前拷贝新建。

2.list(链表):底层是一个双向链表,内存空间不连续。
随机访问:不支持,每次都要从头指针开始遍历,查询效率很低O(n)。查询频繁适合用vector。
增加删除:适合频繁增加删除的场景。 在任意位置插入删除O(1)

3.deque(双端队列,double-ended queue), 由一块一块定量的连续空间构成,块内连续,块和块之间不连续。在头部或者尾部增加新空间时,偏会配置一个固定大小的块连续空间,然后把整个队列的首尾块空间连起来。
插入删除:在头部或者尾部插入删除O(1)
deque的迭代器很复杂,如果不是必须在首尾两端进行操作的需求,不要用deque。
deque中没有capacity,因为deque容器没有容量限制, 可以无限量开辟缓冲区,只需要在中控区添加缓冲区地址用于维护缓冲区空间,所以deque没有容量的限制,可以无限扩展。

stack和queue:容器适配器。
STL中,默认是以deque作为底部容器来实现的。

string(字符串)底层也是自动双倍申请内存。
array,也可以看做序列式容器。

第2种容器:关联式容器,元素位置和插入次序无关,位置取决于元素值和排序准则。(默认字典序排序)
用仿函数,可以改变排序规则。
1.set(集合):由红黑树实现,根据元素值自动排序(默认升序),每个元素只出现一次,不允许重复。
set插入时,会返回插入结果,表示是否插入成功,重复的就不成功。(pair<x, bool>)
关键字即值,只保存关键字的容器。
插入时间复杂度:O(logN), 查找元素时间复杂度:O(logN), 删除元素时间复杂度:O(logN). 不允许修改元素值。

2.multiset(多重集合) 由红黑树实现。
和set唯一区别,可以插入重复数据,插入不检测数据是否重复。
插入:新插入相同数据,被放到相同数的右边,不改变已有数的相对顺序。
删除:如果删除的是重复元素,会默认全部删除ms.earse(x); 不想全部删除的话,传入一个指针即可,只删除当前指针的元素值。ms.erase(ms.lower_bound(x));
插入时间复杂度:O(logN), 查找元素时间复杂度:O(logN), 删除元素时间复杂度:O(logN). 不允许修改元素值。

3.map(映射),由红黑树实现。
容器存储的是pair对象,键值对。键值都可以是任意数据类型。容器自动根据key的大小自动排序(默认升序)。key的值不能重复,也不能修改。
重复插入key时,后面的插入不进来,会失败,所以只保留第一次插入的key-value对。
插入时间复杂度:O(logN), 查找元素时间复杂度:O(logN), 删除元素时间复杂度:O(logN).

4.multimap(多重映射),由红黑树实现。
和map唯一区别,可以插入重复数据,插入不检测数据是否重复。key相同时,自动对value也是按字典序自动排序。
插入时间复杂度:O(logN), 查找元素时间复杂度:O(logN), 删除元素时间复杂度:O(logN).

以上数据结构查询效率不够快,O(logN),为了提高查询效率,C++11推出了unordered_map和unordered_set。
1.unordered_map: (无序的哈希映射),底层使用哈希表来实现, 哈希冲突解决方法是链地址法,冲突元素放到一个桶中,用链表链接起来,链表头存储在哈希表中。
对链表部分的优化:经统计,超过8的概率很小,小于8就用链表;大于8时,用红黑树(将O(N)转换成O(logN))
无序, 不要求顺序,元素不能重复,默认哈希函数std::hash(),计算MD5值。
查找效率,时间复杂度:被均摊成O(1)。 插入时间复杂度O(1), 删除时间复杂度O(1).

!!!不要求有序的话,尽量用unordered_map!!!

使用场景:(1)频繁查询的话,用unordered_map; (2)要求有序的话,用map;(3)需要重复的话,用multimap.

2.unordered_set(无序的集合),底层使用哈希表来实现,哈希冲突解决方法是链地址法,冲突元素放到一个桶中,用链表链接起来,链表头存储在哈希表中。
对链表部分的优化:经统计,超过8的概率很小,小于8就用链表;大于8时,用红黑树(将O(N)转换成O(logN))
无序, 不要求顺序,元素不能重复,默认哈希函数std::hash(),计算MD5值。
查找效率,时间复杂度:被均摊成O(1)。 插入时间复杂度O(1), 删除时间复杂度O(1).

3.unordered_multimap, 和unordered_map类似,只是key可以重复出现。

迭代器
迭代器是一个抽象概念,隔离算法和数据结构,(检查容器内元素,遍历容器内元素的数据类型)
每个容器中都实现了一个迭代器,用于对容器中对象进行访问,封装一下,在用户层面操作一致
迭代器,统一了用户对所有容器访问的方式!!! 提高编程效率。。

算法、 STL封装了一些算法。
<algorithm 库函数里面, numeric库函数包含一些数学运算的函数,functional库函数包含一些类模板。

仿函数:行为类似函数的类,set排序,可以自定义仿函数,实现自定义排序。

适配器:用来修饰容器或仿函数后迭代器的东西。是一种设计模式。

空间配置器:对象构造前的空间配置和对象析构后的空间释放。主要解决空间申请和释放造成的内存碎片问题。
采用双层级配置器。(1)第一级:总结用malloc、free这些分配释放内存;(2)增加了策略:配置区块大于128字节时,调用第一级配置器;小于128字节时,采用内存池(把碎片管理起来);维护16个自由链表,负责小型区块的配置能力。



二叉搜索树,也叫二叉查找树(Binary Search Tree, BST),中根序有序的二叉树,左子树节点值小于根节点值,右子树节点值大于根节点值。
二叉搜索树,节点查询的时间复杂度O(logN),插入O(logN),删除会复杂点,考虑子节点的情况。

二叉搜索树的缺点:有序插入时,二叉树就会变为单支树,时间复杂度就变成O(N)了,所以要对二叉搜索树做高度平衡处理。

平衡二叉树:(又叫AVL树),左右子树都是AVL树,左右子树高度差的绝对值不超过1,(又叫平衡因子),
平衡二叉树的高度为O(logN),搜索时间复杂度O(logN)。

适用场景:保持高效查询、且数据有序的数据结构,数据个数静态,不会变化太大。 结构经常修改变化的话,不太适合。

红黑树:(Red-Black Tree,RB Tree),自平衡的二叉搜索树,移除了AVL树中左右子树高度之差不超过1的限制)。
属于弱平衡二叉树,高度之差可以大于1,但不超过1倍。
性质:
1.每个节点要么是红色,要么是黑色。 2.根节点是黑色,3.每个叶子结点都是黑色,4.每个红色节点的两个子节点一定都是黑色,5.任意一个节点到每个叶子节点的路径都包含相同数量的黑节点。
(总结:根节点黑色,叶子节点黑色,红节点的两个子节点都是黑色,任意一个节点到每个叶子节点路径都包含相同数量的黑节点)
根据红黑树性质得到的推论:
1.树的高度不会超过2*log(N+1); 2.任意节点到每个叶子节点最长路径不超过最短路径的2倍;3.如果一个节点存在黑子节点 ,那么该节点肯定有2个子节点;4.左子树和右子树黑节点层数相等,这叫做黑色完美平衡。
红黑树:插入、删除、查询操作的时间复杂度都是O(logN)

在插入删除时,没有平衡计算的要求,所以综合性能要比AVL树好一些。

红黑树和AVL(自平衡二叉搜索)树的区别:
1.AVL树是严格的平衡二叉树,红黑树是弱AVL树,没有严格平衡;
2.AVL树经过变色操作,可以变成红黑树,红黑树不一定是AVL树;
3.红黑树的自平衡,一般会在3次旋转内解决,平衡二叉树一般要比红黑树复杂多。
4.AVL树更平衡,查找效率会比红黑树高一些;插入、删除效率会低于红黑树。
5.平衡二叉树的插入、删除很容易破坏平衡特性,需要频繁调整;红黑树的插入,删除不容易破坏平衡特性,不需要频繁调整。

**实际使用中,适用的应用场景:**搜索次数远远大于插入、删除次数时,适合选平衡二叉树(AVL树);插入、删除次数比较多时,适合选红黑树。

红黑树的应用场景:
1.C++ STL中广泛使用,map、set、multimap、multiset底层都是红黑树实现;
2.linux中,多路复用epoll,用红黑树管理 事件块,支持快速的增删改查;
3.Linux进程调度,用红黑树来管理进程控制块,进程虚拟内存都存储在一颗红黑树,每个虚拟地址都对应一个树节点。
4.nginx,用红黑树来管理timer,红黑树有序,可以快速得到距离当前最小的定时器。
5.java中的TreeMap的实现。


哈希表(散列表),hash table
通过给定关键字key,来直接访问到具体值的一个数据结构。这个数据结构就是一个哈希表,加快访问速度,一般O(1)。
哈希表,又叫映射表,也叫做散列函数,或者哈希函数,存放记录的数组叫做散列表。
哈希函数实现的方法很多,要考虑的因素:1.关键字的长度、2.哈希表的大小,3.关键字的分布情况,4.记录的查找频率。
常用的哈希函数方法:
1.直接寻址法:取关键字或关键字的某个线性函数值,作为散列地址;(也叫直接定制法,常用的方法)
hash(key) = A*key + B ,将关键字key转换为哈希地址。
优点:简单、均匀,缺点:需要事先知道关键字的分布情况, 适用场景:查找范围比较小且数值连续的情况。
2.除留余数法(也是常用的方法)
散列表中允许的地址数为m,取一个不大于m的数p作为除数,哈希函数:hash(key) = key%p (p <= m),将关键字key转换为哈希地址。
哈希函数设计的越好,产生哈希冲突的可能性就越低,但是完全避免掉哈希冲突是不可能的。
不同的key,通过哈希函数计算,总会得到相同的地址。

哈希冲突解决方法:
1.开放地址法:也叫闭散列,线性探测, 把key存放到冲突位置的下一个空位置中去。
找下一个空位置的方法:挨着往后查找,直到找到空位置即可;如果查找到最后都没找到,总长度取余继续找。
优点:方法简单,缺点:冲突堆积一起,找到空位置会很难,效率很低。
2.再哈希法,也是闭散列,也叫二次探测法,避免冲突堆积在一起,Hash(x) = (hash(x) + i^2) % m. m是表的大小。得到空位置。

3.链地址法,也叫开散列法,拉链法,具有相同地址的key属于同一个集合,也叫一个桶bucket,一个桶的元素通过一个单链表链接起来,链表头存储在哈希表中。(用的最多!!)

C++ std::sort(用的快速排序),默认升序排序,想要改变排序规则时,自己实现cmp函数。
cmp函数返回bool类型,传参时两个同种数据类型的参数a、b,按照先后顺序,里面自己实现比较逻辑,返回值表示a、b的排序是否正确。

C++ STL, 迭代器:底层实际上是一个广义的指针,或者对指针进行了封装,指向容器中的对象。
**迭代器失效是指:**迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
C++ list列表:删除操作时,会导致迭代器失效。

跳跃表和红黑树的对比
1.查询时间复杂度:都是logN。(大体相当) 哈希表在保持较低冲突概率的情况下,查询时间复杂度接近O(1),性能更高。
2.算法实现难度,跳跃表比起红黑树 更简单。
3.空间复杂度,内存占用。跳跃表更灵活,可以参数设置p,跳跃表每个节点包含指针1/(1-p), redis p=1/4, 红黑树每个节点包含2个指针(左右子树),跳跃表更节省内存。
4.插入删除的复杂情况,红黑树会引发子树调整,调整范围较大。跳跃表只需要修改相邻指针即可(局部调整)
5.范围查询:跳跃表范围查询更方便。平衡树不方便。

——-
哈希 冲突解决:
冲突原因:不同的值哈希映射到同一个地址索引位置。
解决方法:
1.开放地址法(再散列法)
所有地址对所有数值开放,(链式法是一种封闭的方式,数值固定位置)
发生冲突时,去寻找下一个空的散列地址(使用某种探测技术形成一个探测序列)比如+1, 或者再散列(继续进行哈希运算)

2.链式地址法 (hashmap用的方法)
方法很简单,链表增加删除即可。
缺点:指针占用较大空间,

一些编程练习题

第k大, top k 问题

最大堆 最小对都可以实现。

第k大数:
1.用最大堆实现时, 对所有数建立最大堆(此时堆顶是最大数),堆顶pop k次,然后堆顶就是第k大数(每次堆都会动态调整,保证堆顶是最大的)

最大堆 浪费空间。 (实际用不了那么多空间,我们只需要关注 top k即可)。

2.用最小堆实现, 维护一个大小为k的堆,堆顶为最小值,剩余数字和堆顶比较,大于堆顶则pop掉堆顶最小值。
遍历完之后,整个最小堆就是top k的值了,堆顶是第k大数。 (节省空间)

对于规模较大的数据,百万、千万、亿级别,不能用内存,要用外存。。

单链表,反转, (自己设计测试用例。。)

  1. 100w数字中找最大的100个。。(用堆排序,取前100数字作为小顶堆,后面挨着去比较调整,,最终剩下的100数字,就是最大的100个数字)

  2. 字符串将某个字符出现多于几次的替换为另一个字符

  3. 4亿个数字找不重复的数字 (去重,1)用哈希结构,key不能重复,有序的话,就比较前后。。)

  4. [1, 2018]找出能被2,3,5整除的数字的个数


trie树, 又称单词查找树,前缀树,字典树,是一种有序树,键key通常是字符串(也可以是一串数字或者形状、位元),常用于统计、排序和保存大量字符串。(优点:利用字符串的公共前缀,来减少查询时间,减少无谓的字符串比较,查找效率比哈希树高) 常用于搜索提示。字符串查找(要从根节点到叶子节点)。 (空间换时间)
性质:根节点不包含字符;根节点之外的其它节点都包含1个字符;根节点到某个节点经过的字符连起来,就是该位置对应的字符串,


哈夫曼树(Huffman Tree), 又叫做最优二叉树,带权路径长度最短的二叉树(树中所有叶节点的权值,乘上其到根节点的路径长度, 哈弗曼树的带权路径长度和(WPL, weight path length)是最小的)。
哈夫曼编码是哈夫曼树的一个应用。 是一种无前缀编码,用于数据压缩,加密解密。

链表和数组的区别,使用场景:
数组是一块连续的内存空间,每个元素内存都是一样的,可以用下标快速访问,但是插入删除比较麻烦,要移动所有元素位置。(数组适合场景: 查询频繁、增删操作比较少。)
链表:每个元素使用指针互相链接,内存空间不一定连续,每个元素类型可以不一样,大小也可以不一样,访问时必须通过链表从前往后扫描,无法通过下标访问;插入删除操作很方便(只需要改变下链表指针的指向即可。。)(链表适合增删频繁的场景,不适合查询频繁的场景。。)


排序算法整理
稳定的排序:a==b,且a在b前面,排序之后,a还在b前面;不稳定的排序之后,a在b后面。

三个基本的简单的排序
(1)选择排序:每次找到最小的元素,然后交换,保证最开始的位置都是最小的。 时间复杂度:O(n^2),空间复杂度:O(1), 非稳定排序

(2)插入排序:从第2个元素开始,去和左边元素挨着比较,遇到比它小的则插入,其余往右移动。时间复杂度:O(n^2), 空间复杂度:O(1), 稳定排序。
(3)冒泡排序:相邻两两挨着比较,然后交换位置,双重循环。(保证右边都是有序的)时间复杂度:O(n^2), 空间复杂度:O(1), 稳定排序。

三个高级排序: (利用了分治的思想)
(1)希尔排序:(插入排序的高级变种) 先对数组进行分组(组数=len/2); 每个组内进行插入排序;然后缩小组数再除以2, 直到为1组。
利用了分治的思想。
时间复杂度:O(n*logn), 空间复杂度:O(1), 非稳定排序。

(2)归并排序: 把数组一直一半一半的划分,最终每个子数组长度为1; 然后再挨着两两合并。
时间复杂度:O(n*logn), 空间复杂度:O(n)(中间需要临时保存数组), 稳定排序。

(3)快速排序: (优点:数据量越大,排序效率会越高)
快速排序流程:1.找到一个基准(一般第一个元素),然后 两个指针low和high从两端扫描,右指针high发现比基准值小的数,就将high位置的值赋值给low位置。左指针发现大于基准数据的值,将low位置赋值给high。 low=high结束。(底层是冒泡排序,交换位置)
时间复杂度:O(nlogn), 最差O(n^2), 空间复杂度:O(nlogn), 非稳定排序。

(4)堆排序: 利用堆结构,来设计的一种排序算法,是一种选择排序,
堆结构: 堆是一个二叉树,
应用场景:优先队列底层实现:用的最大堆,始终维护一个最大堆即可(堆顶即为最大值)
最小堆:任意一个节点都小于左右子节点。
最大堆:任意一个节点都小于左右子节点。
(不要求左孩子和右孩子的关系,所以不是二叉搜索树)
最好、最差、平均时间复杂度都是O(n*logn), 不稳定排序。空间复杂度O(1)

每次堆顶都是最大值,堆顶和最后的元素交换; 然后,剩余元素重复这些操作,最后就有序了。
堆(C++一般用vector实现,通过下标来维系节点关系)

升序 采用大顶堆(堆顶最大); 降序采用小顶堆(堆顶最小)
堆调整:从末尾节点开始调整,上移;(保证每个节点满足大顶堆的要求)
继续交换堆顶和末尾元素,得到第二大元素。
每一次调整完(第n次),堆顶就是第n大元素。
调整n次后(n为元素个数),整体就是有序了。

堆排序和快速排序比较:
数据量少的时候,排序效率差不多;数据量很大时,快速排序的效率会高很多
(数据交换次序,堆排序交换次序明显多于快速排序)
时间复杂度: 堆排序时间复杂度很稳定,稳定在nlogN;快速排序时间复杂度不稳定,最差n*n, 但是快速排序的排序性能很高。

搜索引擎背后的数据结构算法
按流程来
1.搜集数据-爬虫
待爬url,放到redis中持久化,广度优先遍历,
网页去重,二进制位运算 哈希存储。

2.预处理

3.建立索引
分词,倒排索引,排序,

4.查询

Trie树(字典树),常用于统计,排序和保存大量字符串。文本词频统计。 利用字符串的公共前缀 来 减少查询时间。(查询效率 比 哈希树高)

二叉树类别汇总
1.二叉树,
2.满二叉树-每一层节点都是满的
3.完全二叉树-除去最后一层,其它层都是满的,最后一层也是按照从左到右的顺序。

3.平衡二叉树(AVL树),任何节点的2个子树的高度最大差为1。(进行插入删除操作后,会失去平衡,通过旋转等操作来实现自平衡)降低树的深度 来提高查找效率。(但是数据量过大,就只能引出B树)

4.二叉搜索树 (或者叫二叉查找树),中根序遍历是升序有序的。(约等于一个链表的二叉搜索树,操作效率会很低,所以要控制高度)

B+树比起B树的优势:(两个都是多叉 查找树,适合大规模数据存储,树的高度会很低。。)
(1)只有叶子节点存放数据,B+树每一层可以存放更多的key,树的层数就更少了,大大减少了磁盘IO(B+树一般就2-4层)
(2)叶子节点之间所有data数据用指针串起来,遍历叶子节点就能获得全部数据,就能进行区间访问了。(B树不支持这种操作。

平衡二叉树和红黑树,都只是二叉树,大规模数据存储时,高度会很高,查找效率低下。。。

5.B树(又叫B-树),是一个多路查找树(多路搜索树),B是balance 平衡的意思。是二叉搜索树的一般化,可以有两个以上的节点,适合读取和写入数据量很大的数据块的存储系统。(优化外存 磁盘读写 应用)
M阶B树,每个节点最多有m个孩子,根节点和叶子节点外,每个节点至少有m/2个孩子。
B树中 每个节点根据实际情况 可以包含大量关键字信息和分支,(键值key,指针, 数据data)
key:主键 关键字,中根序升序排序,
(每个节点是一个磁盘存储块)

索引查询的性能:主要受限于磁盘IO速度,查询IO次数越少,速度就越快。(对于B树,每个节点看作一次IO读取,树的高度表示最多的IO次数。 每个节点元素越多,高度越低,查询所需IO次数越少)

、、、、
B+树,(主要用于文件系统)
在B树上做了优化,更适合实现外存储索引结构。
B+树:非叶子节点只存储键值信息(key和指针),所有叶子节点之间都有一个链指针,数据记录(data)都存放在叶子节点,
(大大加大每个节点存储的key值数量,降低B+树的高度)
B+树的高度一般2-4层(可以维护10亿条数据)
innodb存储引擎将根节点常驻内存,查找某一键值的行记录最多只需要3次IO操作。

跳跃表(skip list)
背景:普通链表遍历时,必须要从头开始,查找效率很低。

跳跃表: 优化链表查找。 1.链表有序;2.建立多级索引;用二分的方式;
数据量越大,该跳跃表 的优势就体现的越明显。
普通链表的查找时间复杂度O(n),跳跃表 时间复杂度O(logN)。
查找效率提升 建立在多级索引之上,时间换空间。 第i层要建立n/^k个索引节点。

有序链表基础上,
跳跃表(skip list),(对有序链表!!!的优化。链表操作必须从头遍历,跳跃表通过增加索引,来提高链表操作效率;多级索引,类似于二分查找思想;链表越长 效率提升越明显)
在原有有序链表基础上,增加了多级索引,通过索引实现快速查询。(可以提高搜索性能,也能提高插入 删除操作性能)
查找时间复杂度:O(logN),

跳跃表 插入 删除操作:要动态维护索引更新:
跳跃表通过随机函数 来维护 平衡性。

跳跃表性质: 由很多层结构组成,level通过一定概率随机产生。
每一层都是一个有序链表,默认升序,
最底层 level=1 链表包含所有元素,
一个元素出现在level=i之中,则在之下的所有链表层都会出现。
每个节点 包含两个指针,一个指向同一链表的下一个元素,一个指向下面一层的元素。

原理 实现很简单,目前用于 redis、leveldb里面,(实现简单,性能好,但是耗内存(不过可以调参来减小内存消耗))
跳跃表应用场景很多:
1.Lucene,ElasticSearch,里面倒排链,求交使用;
2.Redis里面使用。

、、、、
红黑树,是一种特殊的平衡二叉树,C++ STL中广泛使用,map 和 set。底层是红黑树(保证了有序), logN
红黑树始终在维护一个平衡的过程,要管理大量的节点,性能不如跳跃表。(跳跃表只用管理局部)
unordered_map和 set底层是hash(不在乎顺序,O(1)更快)
epoll:用红黑树管理 事件块
nginx:用红黑树管理 timer。
红黑树:平衡的二叉搜索树(每个节点要么是红,要么是黑;根节点是黑的;如果一个节点是红,它的两个孩子都是黑;叶子节点是黑;对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点。
红黑树的优点:保证内部有序, 旋转开销小, 整体相对平衡。

B树: (Balanced Tree,简称B树,又叫B-树)平衡树的意思。多路平衡查找树。
从二叉树演变而来,

B树、B+树:文件系统,数据库索引中用的比较多,比如 mysql的索引; 磁盘的文件组织。

排序算法的稳定性:
两个值相同的元素,在排序前后是否有位置变化。位置变化的话,则排序算法是不稳定的。
数据交换,导致的不稳定。
要实现稳定的方法:用额外的空间b来存储要交换的数据。原数组a存储不需要交换位置的元素。
扫描结束时,整合2个数组,b的数组接到a数组指针里面。
(整个过程没有交换,而是直接先放一边,最后再拼接)

  1. Strcpy函数,实现?调用出现问题?memmove实现。如果目的地址的内存不够,会出现什么问题。
    (strcpy,目标字符串 必须有足够的空间来容纳源字符串; src和dest内存区域不能重叠。/0
    结尾。 (空间不足时,会越界,可能报错,也可能不报错,不受控制。。)
    strncpy:指明复制的长度。

memcpy:(一定复制完n个字节,不会遇到/0结束,strcpy就是拷贝字符串,memcpy拷贝固定长度。。

memmove:两个区域重叠 也能正常运行

  1. 链表,有哪几种链表?讲解一下。
    单链表(只指向下一个), 双向链表(可以指向上一个 也可以指向下一个), 循环链表(指针的尾部指向头部第一个结点)

  2. List问题(数据结构和算法问题)。一个链表,有两个线程同时对它进行分别操作插入和遍历。如果不同同步和互斥怎么实现,如果不用信号量怎么实现?
    主要是有没有写操作,1.两个线程同时添加,2.一个线程添加 另一个线程读。(添加节点 在链表里 只是一个指针的赋值操作,next)
    写操作 最好加个锁(互斥锁

用红黑树实现 map,set,因为:(1)内部map,set内部有序,红黑树形式存储的键值也是有序的,(2)红黑树可以在logN的时间复杂度内完成插入,删除和查找操作。(红黑树比起二叉搜索树(BST)的优势,在于平衡,所有操作的时间都很稳定。)

unordered_map, unordered_set, 底层用hashmap(哈希表)+bucket(桶)来实现,因为:无序,不要求顺序,hash本身key是无序的,哈希表的查找效率O(1),很快。
底层原理:通过key的哈希,路由到一个桶里(数组,其实是一个链表或者红黑树),
哈希路由映射,会存在冲突(不同的key 通过哈希运算得到相同的结果),每个位置放置一个桶(桶内存放key和value),数量小于8,用链表;数量大于8,用红黑树(将N变成logN)。
!!!不要求有序的话,尽量用unordered_map!!!

2.map multimap
STL的关联容器,map不允许key重复,multimap允许key重复。(key- value)
内部元素有序,红黑树可以自动排序(以key为序排列)
底层原理是使用了 红黑树,O(logN)的查找 插入 删除的速度。

3.unordered_map 和 unordered_multimap
和2中两个对外接口基本一致,底层原理不同,key无序,
底层实现为 hash table,查找效率O(1), 占用内存高。

4.set,multiset
关键字即值,只保存关键字的容器。
set,multiset底层是红黑树,有序存储元素,和map,multimap类似,O(logN),

5.unordered_set,unordered_multiset
底层为hash table

6.priority_queue
优先队列相当于一个有权值的单向队列queue,所有元素按照优先级排列。
priority_queue 根据 堆的规则处理元素间位置,取出最大最小值点时间为O(1),插入删除时间为O(logN)。
底层实现是 heap(堆),最大堆,vector的完全二叉树。
以任何次序将任何元素推入到容器中,取出时一定是按照优先权值最高(或最低)来取的。

8.堆
堆不能算作一种容器(和map vector不同),只能算是组织容器元素的一种特别方式。

大顶堆/小顶堆:每一个节点满足 大于/小于其左右子节点(并非这个堆按照从大到小的顺序排列)

堆排序:堆排序之后的堆,整体才是有序的。

堆:一般用数组 vector来实现的一个具有特殊结构的完全二叉树。(C++ heap用vector实现)

STL封装了在vector进行堆操作的函数,(functional,algorithm头文件里面)
make_heap,建立堆(默认大顶堆, less或者 小顶堆greater)
push_heap,在堆中添加元素
pop_heap,在堆中删除元素
sort_heap,堆排序。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值