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大数。 (节省空间)
对于规模较大的数据,百万、千万、亿级别,不能用内存,要用外存。。
单链表,反转, (自己设计测试用例。。)
100w数字中找最大的100个。。(用堆排序,取前100数字作为小顶堆,后面挨着去比较调整,,最终剩下的100数字,就是最大的100个数字)
字符串将某个字符出现多于几次的替换为另一个字符
4亿个数字找不重复的数字 (去重,1)用哈希结构,key不能重复,有序的话,就比较前后。。)
[1, 2018]找出能被2,3,5整除的数字的个数
trie树, 又称单词查找树,前缀树,字典树,是一种有序树,键key通常是字符串(也可以是一串数字或者形状、位元),常用于统计、排序和保存大量字符串。(优点:利用字符串的公共前缀,来减少查询时间,减少无谓的字符串比较,查找效率比哈希树高) 常用于搜索提示。字符串查找(要从根节点到叶子节点)。 (空间换时间)
性质:根节点不包含字符;根节点之外的其它节点都包含1个字符;根节点到某个节点经过的字符连起来,就是该位置对应的字符串,
哈夫曼树(Huffman Tree), 又叫做最优二叉树,带权路径长度最短的二叉树(树中所有叶节点的权值,乘上其到根节点的路径长度, 哈弗曼树的带权路径长度和(WPL, weight path length)是最小的)。
哈夫曼编码是哈夫曼树的一个应用。 是一种无前缀编码,用于数据压缩,加密解密。
链表和数组的区别,使用场景:
数组是一块连续的内存空间,每个元素内存都是一样的,可以用下标快速访问,但是插入删除比较麻烦,要移动所有元素位置。(数组适合场景: 查询频繁、增删操作比较少。)
链表:每个元素使用指针互相链接,内存空间不一定连续,每个元素类型可以不一样,大小也可以不一样,访问时必须通过链表从前往后扫描,无法通过下标访问;插入删除操作很方便(只需要改变下链表指针的指向即可。。)(链表适合增删频繁的场景,不适合查询频繁的场景。。)
vector:(向量容器),底层实现是用的数组,占用的是连续的内存空间,是一个动态的数组,vector大小随着元素的不断变多而变大,有一个size(当前存放了多少个元素) 和 一个capacity(当前总共能存多少个元素),一般情况下 capacity > size.
当capacity==size时,会重新申请原内存2倍的空间,将旧内存的数据拷贝到新内存中,释放旧内存。 (使用vector频繁执行增加操作,效率很低;建议提前预估好大小,使用reverse一次申请足够的内存)
排序算法整理
—————
堆排序和快速排序:
数据量少的时候,排序效率差不多;数据量很大时,快速排序的效率会高很多
(数据交换次序,堆排序交换次序明显多于快速排序)
时间复杂度: 堆排序时间复杂度很稳定,稳定在nlogN;快速排序时间复杂度不稳定,最差n*n, 但是快速排序的排序性能很高。
快速排序流程:1.找到一个基准(一般第一个元素),然后 两个指针low和high从两端扫描,右指针high发现比基准值小的数,就将high位置的值赋值给low位置。左指针发现大于基准数据的值,将low位置赋值给high。 low=high结束。(底层是冒泡排序,交换位置)
堆排序:用堆这个数据结构,设计的一种排序算法。(底层是选择排序,也是不稳定排序)
堆(C++一般用vector实现,通过下标来维系节点关系)
每个节点值,大于等于左右子节点的值,叫做大顶堆; 每个节点的值,小于等于左右节点的值,叫做小顶堆。
升序 采用大顶堆(堆顶最大); 降序采用小顶堆(堆顶最小)
堆调整:从末尾节点开始调整,上移;(保证每个节点满足大顶堆的要求)
继续交换堆顶和末尾元素,得到第二大元素。
每一次调整完(第n次),堆顶就是第n大元素。
调整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),(对有序链表!!!的优化。链表操作必须从头遍历,跳跃表通过增加索引,来提高链表操作效率;多级索引,类似于二分查找思想;链表越长 效率提升越明显)
在原有有序链表基础上,增加了多级索引,通过索引实现快速查询。(可以提高搜索性能,也能提高插入 删除操作性能)
查找时间复杂度:O(logN),查找效率提升 建立在多级索引之上,时间换空间。
跳跃表 插入 删除操作:要动态维护索引更新:
跳跃表通过随机函数 来维护 平衡性。
跳跃表性质: 由很多层结构组成,level通过一定概率随机产生。
每一层都是一个有序链表,默认升序,
最底层 level=1 链表包含所有元素,
一个元素出现在level=i之中,则在之下的所有链表层都会出现。
每个节点 包含两个指针,一个指向同一链表的下一个元素,一个指向下面一层的元素。
原理 实现很简单,目前用于 redis、leveldb里面,(实现简单,性能好,但是耗内存(不过可以调参来减小内存消耗))
、、、、
红黑树,是一种特殊的平衡二叉树,C++ STL中广泛使用,map 和 set。底层是红黑树(保证了有序), logN
红黑树始终在维护一个平衡的过程,要管理大量的节点,性能不如跳跃表。(跳跃表只用管理局部)
unordered_map和 set底层是hash(不在乎顺序,O(1)更快)
epoll:用红黑树管理 事件块
nginx:用红黑树管理 timer。
红黑树:平衡的二叉搜索树(每个节点要么是红,要么是黑;根节点是黑的;如果一个节点是红,它的两个孩子都是黑;叶子节点是黑;对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点。
红黑树的优点:保证内部有序, 旋转开销小, 整体相对平衡。
B树、B+树:文件系统,数据库索引中用的比较多,比如 mysql的索引; 磁盘的文件组织。
排序算法的稳定性:
两个值相同的元素,在排序前后是否有位置变化。位置变化的话,则排序算法是不稳定的。
数据交换,导致的不稳定。
要实现稳定的方法:用额外的空间b来存储要交换的数据。原数组a存储不需要交换位置的元素。
扫描结束时,整合2个数组,b的数组接到a数组指针里面。
(整个过程没有交换,而是直接先放一边,最后再拼接)
memcpy:(一定复制完n个字节,不会遇到/0结束,strcpy就是拷贝字符串,memcpy拷贝固定长度。。
memmove:两个区域重叠 也能正常运行
链表,有哪几种链表?讲解一下。
单链表(只指向下一个), 双向链表(可以指向上一个 也可以指向下一个), 循环链表(指针的尾部指向头部第一个结点)
List问题(数据结构和算法问题)。一个链表,有两个线程同时对它进行分别操作插入和遍历。如果不同同步和互斥怎么实现,如果不用信号量怎么实现?
主要是有没有写操作,1.两个线程同时添加,2.一个线程添加 另一个线程读。(添加节点 在链表里 只是一个指针的赋值操作,next)
写操作 最好加个锁(互斥锁
1.vector
底层是数组,连续内存空间,当内存不够时重新申请2倍的空间,原来数据复制过去,原空间清空
用红黑树实现 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进行堆操作的函数,
make_heap,建立堆(大顶堆 less或者 小顶堆greater)
push_heap,在堆中添加元素
pop_heap,在堆中删除元素
sort_heap,堆排序。