好久好久么写了,看的书不少却很难落实记录下来,难得浮生半日闲,实验室项目暂告一段,最近阿里和中行面试终于基本完了,终于可以再捧起侯捷大神的STL,Mark一下,督促自己多书多记录,希望2019自己能在项目,毕设和找工作之间游刃有余。
目录
1、临时对象的产生—一种无名对象,不在程序预期之下(passer by value会产生临时对象,负担)
priority_queue和functional 的oush_heap pop_heap
push_heap和priority_queue的基本操作代码
语法两则:
语法:
1、临时对象的产生—一种无名对象,不在程序预期之下(passer by value会产生临时对象,负担)
有时刻意制造临时对象,程序更加清楚----在类型后面加()且可指定初值,int(8),shape(3,5)
仿函数中的应用:
Template<typename T>
Class print
{
public:void operator()(const T&element) const
{
Cout<<element<<””;
}
}
for_each(iv.begin(),iv.end(),print<int>()); //请求的临时对象
2、静态常量整数成员在class内部可直接初始化
Class{
Static const int a = 10;
Static const long b = 20;
Static const char c = ‘c’;
}
线性容器:
空间配置器
SGI标准空间配置器 std::allocator 仅仅是对new和delete的包装而已;效率低,不用。
SGI STL 每个容器的缺省配置为alloc
new ->operator new配置内存->构造函数
delete->调用析构函数->delete释放内存
内存配置器:
向heap申请空间,
考虑多线程状态,
考虑内存不足的应变措施,
考虑过多的小型区块可能造成的内存碎片问题
为了解决内存碎片问题alloc祭出双层配置器:
一级配置器:allocateà调用mallocà调用不成功调用oom_malloc
Realloc->调用realloc->调用不成功调用oom_realloc
二级配置器:(好像有点类似于linux内存分配的伙伴系统)
小额区块导致内存碎片,且配置时需要额外负担(需要一部分内存交给系统进行管理,交税,内存越小越浪费)。小于128字节则以内存池管理,维护与之对应的自由链表(free_list)
。分配内存则从freelist中找,释放小额内存则回收。
内存池维护16个free_list 8 16 24……128字节,为了方便管理内存,将内存碎片调整到8的倍数。内存节点使用共用体,当内存没有分配时,节点填充有指向节点的指针,维持其在链表中的位置,当节点分配后,填充客户数据,降低指针对内存的浪费。
allocate:
分配空间大于128字节则调用一级配置器,否则检查对应的free_list,如果有则拿来用,没有则调用refill为free_list蓄水。
refill chunk_alloc 内存池:
当free_list没有内存时,refill在内存池取多个(默认20)的相应内存给链表,其底层调用chunk_alloc函数,chunk_alloc以end_free – start_free判断内存池水位,水量充足则调出20个内存单位给free_list,其中一个给用户,19个给free_list维护。如果不足,但够一个内存单元则分配数目以引用的参数返回,如果内存池不够一个内存单元则malloc从heap中配置,引入新的活水,新水量为需求量的2倍(40)+附加量(正相关配置次数)。
deallocate():
该函数先判断区块是否大于128字节,是则交给一级配置器去回收,否则搜索对应的free_list进行回收。
Vector
Vector的迭代器就是普通的指针。
空间配置器:alloc 动态内存分配,扩容会以原来两倍的空间拷贝赋值,原空间释放,。连续空间,任何引起内存重新配置操作到会导致迭代器失效。
erase pop_back等删除函数只能改变size不能改变capacity 根据源代码很容易推断出vector的capacity只增不减。
clear ->调用erase也只是改变size
Insert若空间不足会2倍扩容,迭代器失效。
list
底层是双向环状链表,一个指针即可完成所有操作,如下图,刻意增加一个空节点(尾后节点),便于头尾操作,其迭代器是封装节点类型的指针,重载* -> ++ --等操作符。配置器使用alloc作为配置器,封装了一个list_node_allocator便于节点的配置。由于链表的数据结构特性,除了操作的迭代器,其他迭代器不会失效。
List的成员函数操作:
Insert->返回迭代器在插入哨兵元素迭代器之前。
push_back push_front调用insert
erase->删除哨兵节点之后的节点
pop_front pop_back调用erase remove进行循环调用erase;
clear->以尾后节点开始,尾后节点结束进行循环删除;最后置node为空节点。
Protected方法:transfer(position,first,last)将同类(一)链表的某一段移到链表的某个位置前面。
List.splice(iter,list2) 将list2拼接到iter之前à调用内部方法transfer
List.merge(list2) 合并有序链表->在某一链表结束后transfer剩余节点
Reverse()->调用transfer
Sort算法,由于STL算法里的sort必须支持Random Access Iterator所以只能用成员函数list.sort();
deque:
允许双端插入,常数时间,vector头插效率奇差(大量移动);dequeue没有容量概念,提供随机访问,其迭代器设计比较复杂,运算效率差,如对于sort算法先拷贝到vector在sort在拷贝回来。能不用尽量少用。
Deque的数据结构:一个中控器存储指针,各个指针指向各个buffer(默认512字节)。即一个个连续的小空间被中控器连接起,相比于vector扩容避免了复制等操作高效,但是迭代器很复杂,内存维护也复杂。
迭代器:迭代器需要维护关于当前buffer的指针,first,last,cur和所在buffer在中控器中的位置node。
++操作时,3判定cur==last,否则cur++,否则,set_node下一个节点,cur指向下一个缓冲头位置。--操作类似。
push_back和push_front:
map会维护start和finish两个迭代器,表示node的范围,create_map_and_node进行deque结构安排时,会根据元素个数和缓冲区大小分配node个数,num_nodes num_elements/buffer_size() + 1,多分配一个node。map的start和finish也会放在map的中间,便于两边的插入。
当缓冲区不够时进行map扩容,map扩容不需要复制拷贝,在原来的基础上进行扩充,需要复杂的迭代器操作。
成员函数:
pop_back():最后缓冲区有元素则cur指针前移,析构后面。没有则释放最后一个缓冲区,调整finish状态到前一个缓冲区最后一个位置,析构该元素。
pop_front():类似。
clear():将缓冲区析构释放掉,留一个缓冲区(初始状态)。
erase(position): 首先判断当前位置,if(index > size>>1)若在缓冲区前半段,将前面元素进行移位操作,将最前面冗余的元素去掉。否则后面的元素移动,去除最后的冗余元素。
Insert(position,value):在position之前插入value。若position.cur == start.cur即最前端则调用push_front,若在最后端则调用push_back。否则交给insert_aux操作,关于insert_aux也是首先判断index>size()/2,若前面元素少,则在最前端加入一个与第一元素相同的元素,然后向前逐个移动,否则在最后加一个,向后逐个移动。
stack:
适配器:默认以deque为底层数据结构,双向端口封闭头端,不能遍历,没有迭代器。
Stack<int,list<int>>istack; 以list为底层容器,双向端口封闭头端。一般不用vector,扩容效率太差。
queue
适配器:默认以deque为底层数据结构,不能遍历,没有迭代器。
Queue<int,list<int>>iqueue;以list为底层容器。一般不用vector,扩容效率差。
heap
二叉堆,完全二叉树,默认为大顶堆。以连续空间表述,父子关系为i/2 2i 2i+1;所以底层一般用vector进行实现(可动态改变大小且连续空间)。
算法里面有make_heap push_heap pop_heap sort_heap 等STL组件有priority_queue实现,入队以任何顺序进入,出队以权值大小出队。Heap不提供迭代器,不支持遍历。
push_heap(first,last)算法:末端加空节点,以50比较,向上浮动,浮不动填入即可。被调用时,新元素必须在底层vector的尾端,所以v.push_back() + push_heap(begin,end)。
pop_heap算法:堆顶置空,用68和子节点较大的比较,将空节点进行下沉,沉到底为止。
调用时弹出原素在底层vector的尾端,并未删除,所以pop_heap(begin,end)+v.pop_back()。
priority_queue和functional 的oush_heap pop_heap
make_heap(first,last):将一段现有的数据转化为heap顺序。
sort_heap(first,last):将一个堆的数据进行排序(持续调用调用pop_heap将最大的放到vector最后面,弹完所有元素则vector已经排好序),破坏堆的特性。
priority_queue:优先级队列,默认Maxheap 底层封装vector+push_heap+pop_heap算法,适配器,没有迭代器,不能遍历。
priority_queue<int>ipq(first,last);ipq.top();ipq.pop();ipq.push(val);
push_heap和priority_queue的基本操作代码
#include"iostream"
#include"queue"
#include"functional"
using namespace std;
//使用方法 priority(Type,Conteiner,Function)
//std::priority_queue<T, std::vector<T>, greater<T>> pq;
//不加后面两个参数的话默认为 container 默认为vector function默认为less 大顶堆
//小顶堆 基本类型 priority_queue<int, vector<int>, greater<int> >q3;
//自定义型 则重载<
//函数greater在<functional>
class Teacher_08
{
public:
Teacher_08()
{
age = 0;
strcpy(name, "NULL");
}
Teacher_08(int age,char*pname)
{
this->age = age;
strcpy(name,pname);
}
bool operator<(const Teacher_08 &a) const
{
return (this->age < a.age);
}
~Teacher_08()
{}
void print()
{
cout << name << '\t' << age << endl;
}
public:
char name[64];
int age;
};
void baseOpt_priqueue()
{
priority_queue<int> q1;
//默认是最大值优先队列 q1等价于q2
priority_queue<int, vector<int>, less<int>>q2;
priority_queue<int, vector<int>, greater<int> >q3;
q2.push(1);
q2.push(3);
q2.push(4);
q2.push(6);
cout << "q2.size" << q2.size() << endl;
while (!q2.empty())
{
cout << q2.top() << ' ';
q2.pop();
}
cout << endl;
q3.push(6);
q3.push(3);
q3.push(2);
q3.push(1);
while (!q3.empty())
{
cout << q3.top() << ' ';
q3.pop();
}
cout << endl;
Teacher_08 v[]= { {39,"zhangsan"},{20,"lisi"},{28,"wangwu"} };
priority_queue<Teacher_08>q4(v,v+3);
while (!q4.empty())
{
Teacher_08 temp = q4.top();
temp.print();
q4.pop();
}
cout << endl;
}
//stl heap算法
void heap_opt()
{
//include<algorithm> "functional"
int myints[] = { 10,20,30,5,15 };
vector<int> v(myints, myints + 5);
vector<int>::iterator it;
//将[begin, end)范围进行堆排序,默认使用less<int>, 即最大元素放在第一个。
make_heap(v.begin(), v.end());
cout << "初始堆堆顶 : " << v.front() << endl;
//将begin移动到end的前部,同时将剩下的元素堆排序为一个新的heap
//配合vector的pop_back进行出堆操作
pop_heap(v.begin(), v.end()); v.pop_back();//pop_heap将弹出的元素放在vector最后面,vector在弹出
cout << "max heap after pop : " << v.front() << endl;
//刚插入的(尾部)元素做堆排序
v.push_back(100); push_heap(v.begin(), v.end());//vector先插入在push_heap进行调整
cout << "max heap after push: " << v.front() << endl;
//将一个堆做排序,最终成为一个有序的系列,可以看到sort_heap时,必须先是一个堆
//(两个特性:1、最大元素在第一个 2、添加或者删除元素以对数时间),因此必须先做一次make_heap.
sort_heap(v.begin(), v.end());//破坏堆的堆属性进行排序
cout << "最终堆顺序:";
for (unsigned i = 0; i<v.size(); i++) cout << " " << v[i];
cout << endl;
}
void main()
{
baseOpt_priqueue();
heap_opt();
system("pause");
}
运行结果:
两个堆的应用示例:中位数和合并k个链表
中位数(牛客)
题目描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路:维护两个堆,大顶堆和小顶堆,使得两个堆的大小相同;
代码:
class Solution {
public:
void Insert(int num)
{
if (((min.size() + max.size()) & 1) == 0)//目前总共偶数个数字
{
if (max.size()>0 && num<max[0])//偶数个应该放在最小堆,但是数字比大顶堆最大值小
{
//vector.push_back push_back() 数字进堆
//调整,先进大顶堆,将大顶堆最大放入最小堆保持元素差1
max.push_back(num);
//将front移动到end的前部,同时将剩下的元素重新构造成(堆排序)一个新的heap
push_heap(max.begin(), max.end(), less<int>());
num = max[0];
//pop_heap vector.pop_back数字出堆
//将[first, last)范围进行堆排序,默认使用less<int>, 即最大元素放在第一个。
pop_heap(max.begin(), max.end(), less<int>());
max.pop_back();
}
min.push_back(num);
push_heap(min.begin(), min.end(), greater<int>());
}
else
{
if (min.size()>0 && min[0]<num)//本来要插入大顶堆,结果num小顶堆最小值小
{
min.push_back(num);
push_heap(min.begin(), min.end(), greater<int>());
num = min[0];
pop_heap(min.begin(), min.end(), greater<int>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(), max.end(), less<int>());
}
}
double GetMedian()
{
int size = min.size() + max.size();
double median = 0;
if (size & 1 == 1)
median = min[0];
else
median = (min[0] + max[0]) / 2.0;//必须写2.0否则会舍弃不对
return median;
}
private:
vector<int>min;//小顶堆 后半部分 保持大小堆差一个,偶数个新来的放最小堆即后面
vector<int>max;//大顶堆 前半部分 奇数个新来的放最大堆
};
运行结果:
合并K个链表(力扣23)
题目描述:
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入: [ 1->4->5, 1->3->4, 2->]
思路:维护一个小顶堆,大小为k,现将k个链表的头节点入堆,进行出堆操作,堆顶出堆连接到新链表尾部,直到堆为空;
代码实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
struct greater_node
{
bool operator()(ListNode*a, ListNode*b)
{
return (a->val > b->val);
}
};
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*,vector<ListNode*>,greater_node> pq;
ListNode*ret = nullptr;
ListNode*cur = nullptr;
for (auto node : lists)
{
if (node != nullptr)
pq.push(node);
}
while (!pq.empty())
{
ListNode*node = pq.top();
pq.pop();
if (nullptr == ret)
{
ret = node;
cur = node;
}
else
{
cur->next = node;
cur = cur->next;
}
if (nullptr != node->next) {
pq.push(node->next);
}
}
return ret;
}
};
运行结果: