我眼中的数据结构

“ 无论我成了什么狗样,我都相信自己前头无量。”

有些事情从写下开始的时候,就没说过最后的结果会是如何。

今天是10月份的第一个工作日:加油!

数据结构呢其实是指

相互之前存在一种或多种特定关系的数据元素的集合,

而这些数据元素之间的关系被称为结构

典型的数据结构有:

     (1)集合(2)线性结构(3)树形结构 (4)图状结构或网状结构

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

                    

提到数据结构,

对于C++来说,STL(标准模板库)绝对是最实用最方便的,STL之所以强大,是因为STL封装了许多复杂的数据结构和算法,提供了大量流行常用的数据结构操作。比如像:vector封装了数组,list封装了链表,map和set封装了二叉树。

对于STL,其核心主要包括:容器(Containers)、算法(Algorithms)、迭代器(iterators)。

迭代器是STL的精髓,因为它提供了一种方法,可以按照顺序的访问某个容器所含的各个元素,但无需暴露该容器的内部结构。

常量迭代器:for (string::const_iterator it = str2.begin(); it != str2.end(); it++)

正向迭代器:for(string::iterator it = str.begin();it!=str.end();it++)

反向迭代器:for (vector<string>::reverse_iterator iter = v6.rbegin(); iter != v6.rend(); iter++)

容器则是存放数据的地方,常见的容器有序列式容器和关联式容器。序列式容器是指其中的元素不一定有序,但可以被排序,比如string、vector(数组)、list、queue、stack、heap、priority_queue;而关联式容器内部结构基本上是一个平衡二叉树,所谓关联,指每个元素都有一个键值和一个实值<key,value>,元素按照一定的规则存放。比如map、set.

算法是作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。

那来说一说STL中最基本最常用的类或者容器:

stringvector(数组)forward_listlistqueuedequepriority_queuestackheapmapset

单向容器:forward_listqueue           双向容器:stringvector(数组)listdeque

输出双向容器第一个元素和最后一个元素的方法:

❶list<int>l={3,2,4,5,10,7}; cout<<*l.begin()<<*l.rbegin()<<endl; ❷cout<<l.front()<<l.back()<<endl;

 

1.string  

♠string s[]={“ab”,”cd”,”esd”};//这是字符串数组,注意区分int a[]={0};char ch[]=”helloworld”;              

♠ string s2 = s1; //拷贝初始化,深拷贝字符串

♠string s3("I am Yasuo"); string s3=”hello”;//直接初始化,s3存了字符串      

string s4(10, 'a'); //s4存的字符串是aaaaaaaaaa

♠string s5(s4); //拷贝初始化,深拷贝字符串               

♠ string s7 = string(6, 'c'); //拷贝初始化,cccccc               

string str; getline(cin, str); cout << str << endl;    

string str=”helloworld”; cout<<s.find(“word”)<<endl;    if (s.find("ord", 0) == string::npos)

string s("helloworld"); cout<<s.front()<<" "<<s.back()<<endl;//切记这种用法,输出字符串的第一个字符和最后一个字符

for(char c:s) cout<<c; for(auto i:v) cout<<I;   sort(v.begin(),v.end());//注意这个是用的algorithm下的排序函数,不是string自带的,一会区分list

string str("hi sysu");  for (string::const_iterator it = str.begin(); it != str.end(); it++) { cout << *it << endl; *it = 'l'; //这是错误的,不能写,因为使用const_iterator使得访问元素时是能读不能写,这跟常量指针意思差不多。 } 跟数组、容器一样对于字符串里的元素支持下标访问和迭代器访问。

2.vector

♠vector<int> v5 = { 1,2,3,4,5 }; //列表初始化,注意使用的是花括号              

♠ vector<vector<int> >;  //注意空格。这里相当于二维数组int a[n][n];

♠vector<string> v6 = { "hi","my","name","is","lee" };   

♠ vector<int> v7(5, -1); //初始化为-1,-1,-1,-1,-1。第一个参数是数目,第二个参数是要初始化的值

♠vector<string> v8(3, "hi");    vector<int> v9(10); //默认初始化为0      

♠ vector<string> v10(4); //默认初始化为空字符串

vector<int> vec={5,9,10,2,3};  vec.resize(3);//重新调整vec的大小为3,也就是保存前3个元素

cout<<vec.front()<<" "<<vec.back()<<endl;  //切记这种用法,输出容器的第一个元素和最后一个元素

vec.insert(v5.begin()+1,9); //在第二个位置插入9,也就是最终容器的第二个元素是9  vec.insert(vec.begin() + 1, 7,9); //连续插入79

vec.erase(vec.begin() + 3);  //删除第四个元素  for (int it : vec)  cout << it << ' '//遍历输出容器元素

顾名思义push_back把元素插入容器末尾,insert把元素插入任何你指定的位置。不过push_back速度一般比insert快。能用尽量先用push_back

对于访问和操作vector中的每个元素,注意:

只能对已存在的元素进行赋值或者修改操作,如果是要加入新元素,务必使用push_back

push_back的作用有两个:告诉编译器为新元素开辟空间、将新元素存入新空间里。

比如:vector<int> vec;   vec[0] = 1;  //错误!

3.list

单项链表:forward_list<int> l={1,2,3,4,5,5,6,7,7};  cout<<l.front()<<" "<<l.back()<<endl;//是错的,因为单项链表中没有.back()这一项,只能从一端访问

注意:在编写程序时,最好先调用empty()函数判断list是否为空,再调用front()back()函数。If(!l.empty())//一定要判断非空

双向链表:list<int> l={1,2,3,4,5,5,6,7,7};//有头有尾的双向链表  l.push_front(x);//从链表前端把元素x插入到链表头部  l.push_back(); l.pop_back();

l.unique();//删除链表中的重复元素  list<char> l3(10);//创建10个元素的链表  list<int> l4(5, 10); //将元素都初始化为10  l.clear()//清空

list<int>b{1,2,3,4,5};  b.assign(5,10);// a.assign(n, val):将a中的所有元素替换成n个val元素,即b中的元素变为10, 10, 10, 10, 10

list<int>a{6,7,8,9}; list<int>b{1,2,3,4,5}; b.assign(a.begin(),a.end());//b中的元素变为6,7,8,9

a.remove(7);// 删除了a中所有值为7的元素,此时a中元素为6,8,9,10  

bool is_odd(const int& value)

{

    return (value==4);

}  list<int> a{6,7,4,9,7,10}; a.remove_if(is_odd);//移除值为4的所有元素。

a.erase(a.begin());  //将a的第一个元素删除,删除的是一个元素  a.erase(a.begin(),a.end());  //将a的从begin()到end()之间的元素删除。

a.insert(a.begin(),2, 100);   //在a的开始位置插入2个100

注意:list容器不能调用algorithm下的sort函数进行排序,因为sort函数要求容器必须可以随机存储,而list做不到。所以,list自己做了一个自己用的排序函数,用法如下:l.sort();

swap(a,b);//交换两个链表  reverse(l.begin(),v.end());//逆序 std::min: 返回两个值中的最小值;std::min_element:返回序列中的最小值;(algorithm下的)

4.deque

https://images2017.cnblogs.com/blog/1153161/201708/1153161-20170829222500296-1339056219.pngdeque双向开口可进可出的容器 连续线性空间

 

https://images2017.cnblogs.com/blog/1153161/201708/1153161-20170829222521296-2057017524.png

其实deque由一段一段构成 ,他是分段连续,而不是内存连续 段与段之间用链表相连,当走向段的尾端时候自动跳到下一段 所以支持迭代器++ 操作,自动跳到下一段的方法由operator++实现deque每次扩充申请一个段。

deque<int> c; //创建一个空的deque

deque<int> c1(c); //拷贝构造

deque<int> c2 = c1; //赋值拷贝

deque<int> c3 (5,6); //指定元素5个6

deque<int> c6 = {2,3,4,5};

c1.push_back(6);//队列尾部插入

c1.push_front(0);//队列头部插入

c1.pop_back();//弹尾元素

c1.insert(c1.begin()+3, 10);//指定位置插入元素

c1.erase(c1.begin()+3); //删除指定位置元素

c.clear();

swap(c1,c2);

//重新设定deque大小 少退多补

c.resize(10, 5);

deque<int> c = {1,2,3,4,5}; //指向第一个元素的迭代器 cout << *c.begin() << endl;

cout << *c.rbegin() << endl; //反向deque的第一个元素的迭代器

5.stack(模板类)

stack是一种先进后出(FILO)的数据结构,它只有一个出口。stack没有迭代器,所以除了顶部元素,无法存取其它元素,即不能遍历stackstack的成员函数都是针对其顶部元素进行操作:push()pop()top()。底层可以用多种容器来实现。

构造 template <class T, class Container = deque<T> > class stack;

如上,这对尖括号中有两个参数,第一个是T,表示栈中存放的数据的类型,比如int,double,或者结构体之类。

第二个参数指明底层实现的容器类型,也就是指明这个栈的内部实现方式,比如vector,deque,list。如果不指明它,默认使用deque(双端队列)。当然一般情况下不需要指定这一项参数。

stack<int> s1;  stack<string> s2;   stack 的基本操作有:

入栈,如例:s.push(x); 出栈,如例:s.pop();注意,出栈操作只是删除栈顶元素,并不返回该元素。访问栈顶,如例:s.top()

判断栈空,如例:s.empty(),当栈空时,返回true。访问栈中的元素个数,如例:s.size()。

切记,栈是一种数据结构,确切的说它不是一种容器,我们可以用vectorlistdeque等容器来实现它。例:

stack<int> first;   //构造一个用于存放int类型的空栈(默认底层容器为deque),size=0。这是最简单也是最常用的方式   

deque<int> mydeque (3,100);//构造一个包含3个int元素的双端队列 stack<int> second (mydeque); //用自己的双端队列构造一个栈(size=3)

stack<int,vector<int> > third;          //指明用vector实现一个栈(存放int),空栈size=0

6.queue(模板类)

queue是一种先进先出(FIFO)的数据结构,它有两个出口。Queue默认也是以deque作为底部结构,封闭其底端的出口和前端的入口。queue,只有顶端(两端)的元素能被外部使用,所以queue也没有迭代器,不提供遍历功能。queue的成员函数有:front()back()push()pop()

可以看到stackqueue的成员函数以及特性都是针对其数据结构来的,所以不能与stringvectorlistdeque等容器所有的众多成员函数混淆。当然stackqueue也可以listvector为底层结构实现。

与stack 模板类很相似,queue 模板类也需要两个模板参数,一个是元素类型,一个容器类型,元素类型是必要的,容器类型是可选的,默认为deque 类型。定义queue 对象的示例代码如下:queue<int> q1;queue 的基本操作有:

入队,如例:q.push(x); 将x 接到队列的末端。

出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。访问队首元素,如例:q.front(),即最早被压入队列的元素。访问队尾元素,如例:q.back(),即最后被压入队列的元素。判断队列空,如例:q.empty(),当队列空时,返回true。访问队列中的元素个数,如例:q.size()

7.priority_queue(模板类)

优先队列是一种优先级高的元素先出的数据结构,priority_queue默认的底层结构是vector,它有两个出口。跟queue一样,只有两端的元素能被外部使用,所以也没有迭代器,不提供遍历功能。(默认为大者优先,也可以通过指定算子来指定自己的优先顺序)。

priority_queue 模板类有三个模板参数,第一个是元素类型,第二个容器类型,第三个是比较算子。其中后两个都可以省略,默认容器为vector,默认算子为less,即小的往前排,大的往后排(出队时序列尾的元素出队)。定义priority_queue 对象的示例代码如下:

priority_queue<int> q1;   priority_queue< pair<int, int> > q2; // 注意在两个尖括号之间一定要留空格。

priority_queue<int, vector<int>, greater<int> > q3; // 定义小的先出队

priority_queue 的基本操作与queue 相同。当然在使用priority_queue 时,最困难的可能就是如何定义比较算子了。

如果是基本数据类型,或已定义了比较运算符的类,可以直接用STL less 算子和greater算子,默认使用less 算子,即小的往前排,大的先出队。

如果要定义自己的比较算子,方法有多种,这里介绍其中的一种:重载比较运算符。优先队列试图将两个元素x 和y 代入比较运算符(对less 算子,调用x<y,对greater 算子,调用x>y),若结果为真,则x 排在y 前面,y 将先于x 出队,反之,则将y 排在x 前面,x 将先出队。

8. heap(算法)

heap并不是一种STL容器,也不是数据结构,而是一种类属算法,包含在头文件algorithm中。它分为大顶堆max heap 和小顶堆min heap,在缺省情况下(默认情况下),max-heap是优先队列(priority queue)的底层实现机制,而这个实现机制中的max-heap实际上是以一个vector表现的完全二叉树(complete binary tree)。

STL在<algorithm.h>中实现了对存储在vector/deque 中的元素进行堆操作的函数,包括make_heap, pop_heap, push_heap, sort_heap.

 

数据结构中堆的含义:(不要跟STL里heap操作弄迷糊)

堆(heap)是一种非常重要的数据结构(这里指二叉堆),它是一棵满足特定条件的完全二叉树,对于该完全二叉树中的每一个结点x,其关键字大于等于(或小于等于)其左右孩子结点,而其左右子树均为一个二叉堆。若堆中父亲结点关键字的值大于等于孩子结点,则称该堆为大顶堆;若堆中父亲结点关键子的值小于等于孩子结点,则称该堆为小顶堆。由于堆是一棵完全二叉树,所以我们可以很轻易地用一个数组存储堆中的每一个元素,并且由子结点访问到其父亲结点和由父亲结点访问到其子结点。

9. map(关联容器)

map是STL的一个关联容器,运用了哈希表地址映射的思想,也就是key-value的思想。有序无重复的二元组的集合。底层是基于一颗红黑二叉树R-B Tree(一种高效的平衡搜索二叉树) map的内部键的数据都是排好序的,查找和删除、插入的效率都是O(lgN)。

例:用字符串作为key去查询操作对应的value。

map<string, int> m1; //<>里的第一个参数表示key的类型,第二个参数表示value的类型;m1["Kobe"] = 100; m1["James"] = 99; string s("Jordan");m1[s] = 90;

cout << m1["Kobe"] << endl; m1.erase("Curry");//通过关键字来删除m1.insert(pair<string, int>("Harris", 89)); //也可以通过insert函数来实现增加元素

if (m1.count("Lee"))  cout << "Here!" << endl;

for (map<string, int>::iterator it = m1.begin(); it != m1.end(); it++)

{

    cout << it->first<<"  "<<it->second << endl;  //注意用法,不是用*it来访问了。first表示的是keysecond存的是value

}

10. set(关联容器)

set跟vector差不多,它跟vector的唯一区别就是,set里面的元素是有序的且唯一的,只要你往set里添加元素,它就会自动排序,而且,如果你添加的元素set里面本来就存在,那么这次添加操作就不执行。要想用set先加个头文件set。

set<int> s1{9,8,1,2,3,4,5,5,5,6,7,7 }; //自动排序,从小到大,剔除相同项  s1.insert(9); //有这个值了,do nothing

set<string> s2{ "hello","sysy","school","hello" }; //字典序排序   s2.insert("aaa"); //没有这个字符串,添加并且排序

 

sweet~

猜你喜欢

转载自blog.csdn.net/m0_38087936/article/details/82958057