[STL] Implement "vector", ”deque“ and "list"

vector


“可增的”数组

vector是一块连续分配的内存,从数据安排的角度来说,和数组极其类似。html

不一样的地方就是:node

(1) 数组是静态分配空间,一旦分配了空间的大小,就不可再改变了;linux

(2) vector是动态分配空间,随着元素的不断插入,它会按照自身的一套机制不断扩充自身的容量。面试

 

内存模型

vector发现本身的空间不够了,因而申请新的内存空间(自增一倍),并将前面已有数据复制到新空间的前部。算法

 

Comment

自增一倍,主要是“位移”运算。数组

对于vector增长新元素的时候,有可能很快完成,也有可能要进行扩容,效率降低;dom

删除末尾元素效率很高,删除中间元素效率低;函数

 

 

 

deque


双端队列

deque是双端队列,在接口上和vector很是类似,在许多操做的地方能够直接替换。post

与vector不一样的是,deque不能保证全部的元素存储在连续的空间中,在deque中经过指针加偏移量方式访问元素可能会致使非法的操做。性能

 

内存模型

Ref: C++ STL学习之三:容器deque深刻学习(转)

除了在频繁在头部或尾部进行插入和删除操做外,deque比list和forward_list的性能更差。

deque是一种优化了的对序列两端元素进行添加和删除操做的基本序列容器。

一般由一些独立的区块组成,第一区块朝某方向扩展,最后一个区块朝另外一方向扩展。

它容许较为快速地随机访问但它不像vector同样把全部对象保存在一个连续的内存块,而是多个连续的内存块。而且在一个映射结构中保存对这些块以及顺序的跟踪。

 

Ref: STL源码剖析---deque

deque采用一块所谓的map(注意,不是STL的map容器)做为主控。这里所谓map是一小块连续空间,其中每一个元素(此处称为一个节点,node)都是指针,指向另外一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。

SGI STL 容许咱们指定缓冲区大小,默认值0表示将使用512 bytes 缓冲区。 

deque的迭代器

让咱们思考一下,deque的迭代器应该具有什么结构,首先,

  1. 它必须可以指出分段连续空间(亦即缓冲区)在哪里;
  2. 其次它必须可以判断本身是否已经处于其所在缓冲区的边缘,若是是,一旦前进或后退就必须跳跃至下一个或上一个缓冲区。

为了可以正确跳跃,deque必须随时掌握管控中心(map)。因此在迭代器中须要定义:当前元素的指针,当前元素所在缓冲区的起始指针,当前元素所在缓冲区的尾指针,指向map中指向所在缓区地址的指针。

 

 

List


内核链表 

From: 深刻分析 Linux 内核链表

struct list_head { struct list_head *next, *prev; }; #define list_entry(ptr, type, member) container_of(ptr, type, member)
// container_of宏定义在[include/linux/kernel.h]中 #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
// offsetof宏定义在[include
/linux/stddef.h]中 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

 

链表问答

Ref: [算法总结] 一文搞懂面试链表题

在 O(1) 时间删除链表节点 - 单向链表

作一次复制便可。

 

反转链表 O(n)

用三个临时指针 prev、cur、next 在链表上循环一遍便可。

 

旋转单链表,以及删除单链表倒数第 n 个节点

快指针结合慢指针,相同步幅走

 

求单链表的中间节点

快指针结合慢指针,不一样步幅走

 

链表划分,大于x的排后面;小于x的排前面

初始化两个链表leftList, rightList,遍历原链表。

 

在O(nlogn)时间内对链表进行排序

快排或者并归 --> Goto: 排序章节

 

合并两个排序的链表

同步指针法

 

删除链表中重复的结点

利用两个指针,相似冒泡排序

 

判断单链表是否存在环

快慢指针,慢指针每次移动一步,快指针每次移动两步,若是存在环,那么两个指针必定会在环内相遇。

 

找到环的入口点

咱们设置两个指针,一个是快指针fast,一个是慢指针slow,fast一次走两步,slow一次走一步,若是单链表有环那么当两个指针相遇时必定在环内。

此时将一个指针指到链表头部,另外一个不变,两者同时每次向前移一格,当两个指针再次相遇时即为环的入口节点。若是fast走到null则无环。

 

判断两个无环单链表是否相交

尾巴必然同样;或者用”有环“的思想去解题。

 

求两个无环单链表的第一个相交点

 妙哉,四种方法四个角度:

    • 方法一 [倒转链表能够用”栈“] 若是两个链表存在公共结点,那么它们从公共结点开始一直到链表的结尾都是同样的,所以咱们只须要从链表的结尾开始,往前搜索,找到最后一个相同的结点便可。可是题目给出的单向链表,咱们只能从前向后搜索,这时,咱们就能够借助栈来完成。先把两个链表依次装到两个栈中,而后比较两个栈的栈顶结点是否相同,若是相同则出栈,若是不一样,那最后相同的结点就是咱们要的返回值。
    • 方法二 [利用尾部共享] 先找出2个链表的长度,而后让长的先走两个链表的长度差,而后再一块儿走,直到找到第一个公共结点。
    • 方法三 [环的入口点] 因为2个链表都没有环,咱们能够把第二个链表接在第一个链表后面,这样就把问题转化为求环的入口节点问题。
    • 方法四 [互补长度一致策略] 两个指针p1和p2分别指向链表A和链表B,它们同时向前走,当走到尾节点时,转向另外一个链表,好比p1走到链表 A 的尾节点时,下一步就走到链表B,p2走到链表 B 的尾节点时,下一步就走到链表 A,当p1==p2 时,就是链表的相交点

 

判断两个有环单链表是否相交

若是两个有环单链表相交,那么它们必定共有一个环,即环上的任意一个节点都存在于两个链表上。

 

复杂链表的复制

题目描述:输入一个复杂链表(每一个节点中有节点值,以及两个指针,一个指向下一个节点,另外一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,不然判题程序会直接返回空)

第二个指针如何复制的问题?

奇偶复制。这样,random指针指向的结点天然知道”本身的copy"的位置。

 

 

 

Understand the cons and pros


 

 

 

 

 

数组 & 单链表


std::array

What is the difference between std::array and std::vector? When do you use one over other? [duplicate]

std::array is just a class version of the classic C array. That means its size is fixed at compile time and it will be allocated as a single chunk (e.g. taking space on the stack). The advantage it has is slightly better performance because there is no indirection between the object and the arrayed data. 

 

std::forward_list

forward_list 容器以单链表的形式存储元素。forward_list 的模板定义在头文件 forward_list 中。fdrward_list 和 list 最主要的区别是:它不能反向遍历元素;只能从头至尾遍历。
forward_list 的单向连接性也意味着它会有一些其余的特性:

  1. 没法使用反向迭代器。只能从它获得const或non-const前向迭代器,这些迭代器都不能解引用,只能自增;
  2. 没有能够返回最后一个元素引用的成员函数back();只有成员函数front();
  3. 由于只能经过自增前面元素的迭代器来到达序列的终点,因此push_back()、pop_back()、emplace_back()也没法使用。

forward_list 的操做比 list 容器还要快,并且占用的内存更少,尽管它在使用上有不少限制,但仅这一点也足以让咱们满意了。

 

End.

相关文章
相关标签/搜索