C++STL容器 ---- 顺序容器之deque、list

顺序容器2---deque:双端队列

1、Deque(doubled-ended queue)是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。

       底层数据结构:动态开辟的二维数组,和vector的底层数据结构不同,deque的第二维是按固定大小开辟的,内存扩容时,扩容第一维的数组空间,每次按原空间2倍方式进行扩容,然后把第二维的所有数组调整到第一维数组的中间进行排列,上下预留位置空间,因为deque要支持头部和尾部的增加删除,这样的调整比较方便做头尾数据增删。

2、deque内存不连续,所以较vector可以保存的元素更大,在前面和后面添加元素时都不需要移动其它块的元素。对于deque的排序操作,可以先将deque的数据复制到vector,排序好后再复制回deque。

3、deque两端都能快速插入和删除元素

4、存取元素时,deque内部结构会多一个间接过程,所以元素的存取和迭代器的动作会稍微慢一些。

5、迭代器需要在不同的区块间跳转,所以必须是特殊的智能型指针,非一般指针。

6、deque不支持对容量和内存重分配时机的控制。特别注意,除了头尾两端,在任何地方插入或删除元素,都将导致指向deque元素的任何指针,引用,迭代器失效。不过,deque的内存重分配优于vector,因为其内部结构显示,deque不必在内存重分配时复制所有元素。

7、deque的内存区块不再被使用时,会被释放。

8、deque的数据结构及扩容后的数据结构:

扫描二维码关注公众号,回复: 6105904 查看本文章

Deque扩容先以2倍扩MAP_SIZE,然后将原本的que放在扩容后MAP_SIZE的中间,使数据增长方向可以继续向两端增长。deque底层不连续,段与段之间连续,每小块之间不连续,是一个动态开辟的二维数组。

9、deque常用方法及相应时间复杂度

 push_back/pop_back    O(1)

push_front/pop_front  O(1)

insert/erase          O(n)

 

顺序容器3---list:链表容器

1、带头节点的双向非循环链表,支持快速插入/删除,所以插入删除比vector快,但随即访问不如vector快

2、底层数据结构:底层是一个带头节点的双向链表,节点内存非连续,扩容就是产生新的链表节点。

3、list常用方法及相应时间复杂度

push_back/pop_back    O(1)

push_front/pop_front  O(1)

insert/erase          O(1)

我们可以看出list的常用的增加删除时间复杂度都趋近于O(1),但是如果查找的话,就要从头结点开始查起,其时间复杂度为O(n)

4、splice()函数,从链表里摘除一个或者一堆节点,不涉及任何内存的开辟释放,效率高。

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int main()
{
	// 定义两个list容器
	list<int> l1;
	list<int> l2;

	for (int i = 0; i < 10; ++i)
	{
		l1.push_back(i);
	}
	for (int i = 10; i < 20; ++i)
	{
		l2.push_back(i);
	}

	// 打印两个list容器的元素值
	for_each(l1.begin(), l1.end(), [](int val){cout << val << " "; });
	cout << endl;
	for_each(l2.begin(), l2.end(), [](int val) {cout << val << " "; });
	cout << endl;
	
	// 把list1容器的首元素0移动到末尾,只涉及指针域的改变,没有做任何的内存和数据拷贝操作
	l1.splice(l1.end(), l1, l1.begin());
	for_each(l1.begin(), l1.end(), [](int val) {cout << val << " "; });
	cout << endl;

	// 把l1容器的首元素移动到l2容器的末尾
	l2.splice(l2.end(), l1, l1.begin());
	for_each(l2.begin(), l2.end(), [](int val) {cout << val << " "; });
	cout << endl;

	// 把l1容器的所有元素移动到l2容器的末尾
	l2.splice(l2.end(), l1, l1.begin(), l1.end());
	for_each(l2.begin(), l2.end(), [](int val) {cout << val << " "; });
	cout << endl;

	return 0;
}

C++11提供的其它的顺序容器: 

forward_list:单向链表,单向顺序访问

array:固定大小数组,内存不可扩容,支持随机快速访问,不能删除或添加元素

顺序容器常见问题: 

1、deque底层空间是否连续?

deque容器底层是动态开辟的二维数组,因此底层空间不是完全连续的,每一个小段的第二维数组是连续的。  

2、vector 和 deque的插入删除效率谁高一些?

vector底层是数组,内存连续,尾插和尾删时间复杂度O(1),其它位置插入删除是O(n)。但是vector的随机访问的时间复杂度是O(1),给定一个下标就可以通过这个下标来访问到元素的值。

deque 头部和尾部的插入删除都是O(1),但是其它位置的插入和删除都是O(n),因为deque底层内存空间不完全连续,可能会牵扯到访问另一段内存空间而耗时,所以在中间进行插入删除时效率没有Vector的高,因为vector底层连续,数据移动的效率更高。

3、vector 和 list如何选择?

一个是数组,一个是链表。两个如何选择,就是对于数组和链表的选择。数组随机访问快,插入删除慢;链表插入删除快,但不支持随机访问,查找需要从头结点开始遍历。

4、迭代器失效的问题?

迭代器在遍历容器的过程中,是不允许修改容器的元素的,如果有增加或者删除导致vector的长度改变,就会使已存在的迭代器失效,所以每次操作之后必须对迭代器进行更新,否则可能会导致程序崩溃。另一种理解:容器不允许在一个线程用迭代器遍历,另一个线程对容器元素进行增删操作。

5、两个不同容器的迭代器能否进行比较?

不同容器的迭代器不能比较,源代码认为如果迭代器没有迭代同一个容器,它们之间的比较没有意义。

因此如果比较,那么两个迭代器比较必须作用于同一个容器。因此迭代器在进行比较的时候,需要判断:

①是否是同一个容器的迭代器

②迭代的容器的元素长度是否一样,迭代过程中容器元素经过改动,一定要及时更新迭代器。

猜你喜欢

转载自blog.csdn.net/Disremembrance/article/details/89358835
今日推荐