【数据结构与算法】第一篇:数据结构

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
TODO:写完再整理

文章目录


前言

认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长!

本文先对数据结构做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章


提示:以下是本篇文章正文内容

STL库的介绍

STL库是C++标准模板库,是C++语言中封装好的一些数据结构与快速实现的算法。
.

(1)C++标准模板库std与C++标准库stl的关系

std库提供的是C++语言基础的数据类型,有很多数据类型的内置函数可以用!而STL库提供了C++语言的多种数据结构(容器)和算法实现

C++标准模板库stl其实属于C++标准库std的一部分,C++标准模板库stl主要是定义了标准模板std的定义与声明,而这些模板主要都是类模板,我们可以调用这些模板来定义一个具体的类;与之前的自己手动创建一个函数模版或者是类模板不一样,我们使用了STL库就不用自己来创建模板了,这些模板都定义在标准模板库中,我们只需要学会怎么使用这些类模板来定义一个具体的类,然后能够使用类提供的各种方法来处理数据。

标准模板和类模板是C++提供的,模板可以认为就是一套框架,按着规范做就行!连数据结构自身都是模板的一部分

容器是类模板的一种,容器是随着面向对象语言的诞生而提出的,容器类在面向对象语言中特别重要,甚至它被认为是早期面向对象语言的基础。
.

(2)STL六大组件

容器,即存放数据的地方比如array等。在STL中,容器分为两类:序列式容器和关联式容器。

序列式容器,其中的元素不一定有序,但都可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue、slist;

关联式容器,内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap。

容器(containers)

在这里插入图片描述
(1)在C++中容器就是一种用来存放数据的对象。
(2)C++中容器类是基于类模板定义的
(3)容器还有另一个特点是容器可以自行扩展。数组在这一方面也力不从心。容器的优势就在这里,它不需要你预先告诉它你要存储多少对象,只要你创建一个容器对象,并合理的调用它所提供的方法,所有的处理细节将由容器来自身完成。它可以为你申请内存或释放内存,并且用最优的算法来执行您的命令。
(4)容器的分类
STL对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器
1)顺序性容器:矢量vector、队列deque、列表list
顺序性容器 是 一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。序列容器维护你指定的插入元素的顺序,序列式容器中的每个元素均有固定的位置。
.
.

2)关联性容器:set、multiset、map、multimap
关联式容器是非线性的树结构,各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。
.
.

3)容器适配器:stack、queue
容器适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。

一个适配器不是可以由任一个顺序容器都可以实现的。默认下stack 和queue 基于deque 容器实现,priority_queue 则基于vector 容器实现。当然在创建一个适配器时也可以指定具体的实现容器,创建适配器时在第二个参数上指定具体的顺序容器可以覆盖适配器的默认实现。
.
.

4)vector 、list 、deque 在内存结构上的特点
在这里插入图片描述
(1)vector 是一段连续的内存块,而deque 是多个连续的内存块, list 是所有数据元素分开保存,可以是任何两个元素没有连续。vector 的查询性能最好,并且在末端增加数据也很好,除非它重新申请内存段;适合高效地随机存储。

(2)list 是一个链表,任何一个元素都可以是不连续的,但它都有两个指向上一元素和下一元素的指针。所以它对插入、删除元素性能是最好的,而查询性能非常差;适合 大量地插入和删除操作而不关心随机存取的需求。

(3)deque 是介于两者之间,它兼顾了数组和链表的优点,它是分块的链表和多个数组的联合。所以它有被list 好的查询性能,有被vector 好的插入、删除性能。 如果你需要随即存取又关心两端数据的插入和删除,那么deque 是最佳之选。
.

算法(algorithms)

一些集成的排序算法

.

迭代器【类似于指针理解】(iterators)

迭代器是一种对象,它能够用来遍历STL容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址,所以可以认为迭代器其实就是用来指向容器中数据的指针,我们可以通过改变这个指针来遍历容器中的所有元素。

.

函数对象(functors)

.

适配器(adapters)

.

分配器(allocators)

.

一、变量(value)

(1)整型(int)

(1)判断两个数字的大小的API
		min(a,b),max(a,b)

(2)变量取绝对值的API函数
		abs(i - j)

(3)变量的强制转换
		(double)(a-b)
		
(4)变量数学运算等功能API,当然也可以自己实现
关于变量的算数运算C++语言是提供了很多内置的方法API的,或者使用算数运算符也可以实现(面试背一背,平时用就查一查)
【能用变量的算数运算API和运算符就用,不然要引入复杂的数学问题】

(2)浮点型(float\double)

.

(3)字符型(char)

.

(4)字符串型(string)

字符串可以理解成为一个字符数组(若字符串的元素为字符)

【取str1字符串的第1到第(index-1)个元素出来组成一个字符串】
  方法一:str.substr(0, index)
  方法二:String.substring(a,b)

【取strs中的第i个元素】 
  方法一:char c = str.charAt(i);
  方法二:string[i]

【在字符串中嵌套字符串的情况,取字符串元素中的一个字符的方法】
  char c = strs[0].charAt(i);  【strs是总的字符串,strs[0]是字符串元素】

【判断字符串和数组的元素长度】
 strs.length

【数组与字符串对应元素交换】
 swap(a, b);

字符串判断非空
String==null

(5)布尔型(bool)

非true即false,非1既0吗,常用于逻辑判断。

.
.


二、数组(array)

(1)数组的特征

(1)数组是内存的连续空间
(2)数组具有查找快、但插入/删除结点慢的特点
(3)数组的元素必须是同一类型
(4)创建一个数组的时候,要考虑数组的大小,因为数组插入/删除结点比较复杂,警惕越界错误,导致 Stack Over Flow
.

(2)数组的时间复杂度

(1)支持 O(1) 的随机访问操作
(2)平均为 O(n) 的插入和删除操作
在这里插入图片描述
.

(3)数组的实现

在这里插入图片描述
语言里面提供了语法创建

.

(4)数组的实际应用

(1)数组与字符串对应元素交换
    swap(a, b);
    
(2)检查数组array的数据结构的长度
    array.length

.
.


三、字符串string

(1)介绍

string容器就是个字符串,其实string并不是STL的一种容器,但是由于它的使用方法等等和STL容器很像,所以就把它当作 STL容器一样介绍。
#include<string.h>库更熟悉一些。我丝毫不否认这些传统字符操作的经典性和实用性,但是由于它们函数定义的局限,有些时候对于一些特殊的读入、输出、遍历等要求,它的操作并不如string容器好用。

.
.

(2)string容器的使用

string容器的使用方法及与传统字符读入的对比
在这里插入图片描述
(1)字符串创建

string str;//创建一个空的字符串 。
string str1("STL");//使用字符串s初始化。
string str2(str2);//使用一 个string对象初始化另一个string对象。
string str3(5,'a');//使用n个字符c初始化。

.
(2)字符串赋值

//函数声明
string& operator=(const chart s):char*类型字符串赋值给当前的字符串
string& assign(const char *s,int n):把字符串s的前n个字符赋给当前的字符串
string& assign(int n,char c):用n个字符C赋给当前字符串
string str; 
string str1("hello"); 
str="STL"; 
str.assign("HELLO",4);
str.assign(3,'A');

.
(3)字符串存取

str[0]='X'; 
str.at(0)='X';

.
(4)字符串的插入与删除

int insert(int pos,const string& str ):往字符串的第pos位置中插入str字符串,pos为迭代器 
int erase(int pos,int n):删除第pos位置的n个字符,pos为迭代器
str.insert(0,"hello"); 
str.erase(0,2) ;

.
(5)获取子串

int substr(int pos,int n):获取字符串中第pos位置的n个字符,pos为迭代器
str.substr(0,2);

.
(6)字符串比较

int compare(const string &str):比较两个字符串是否相等或者大小
str.compare(str1);

.
(7)字符串转整数

Int r = str[i]-0;

.
(8)vector数据类型转字符串数据类型

to_string(XXX)

.
(9)字符串拼接

//函数声明
string& operator+=(const char *str):将str字符串加到串尾部
string& append(const char *s):把字符串s连接到当前字符串结尾
string& append(const char *s,int n):把字符串s的前n个字符连接到当前字符申结尾
string& append(const string &s,int pos, int n):字符串s中从pos开始的n个字符连
string str; 
string str1("hello"); 
str="STL"; str+="hello"; 
str.append("hello"); 
str.append("hello",4); 
str.append(str1,0,4);

.
(10)字符串查找与替换

//函数声明
int find(const string& str, int pos):查找str第一次出现位置,从pos开始查找
string& replace(int pos,int n,const string& str):替换从pos开始n个字符为字符串str
str="STL"; 
str.find("S",0); 
str.replace(0,2,"hello");

.
.


四、向量vector

(1)介绍

vector是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。

vector容器的行为类似于数组,其大小可以不预先指定,但可以根据要求自动增加,vecto是一种动态数组,实现动态扩展。vector类是使用new和delete来管理内存,但这种工作是自动完成的。它可以随机访问、连续存储,长度也非常灵活。基于上述和其它原因,vector是多数应用程序的首选序列容器。

(2)图例

在这里插入图片描述

(3)创建原理

在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定。当存储的数据超过分配的空间时vector 会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作:
首先,vector 会申请一块更大的内存块;
然后,将原来的数据拷贝到新的内存块中;
其次,销毁掉原内存块中的对象(调用对象的析构函数);
最后,将原来的内存空间释放掉。
当vector保存的数据量很大时,如果此时进行插入数据导致需要更大的空间来存放这些数据量,那么将会大大的影响程序运行的效率,所以我们应该合理的使用vector。

(4)vector 的特点

(1) 指定一块如同数组一样的连续存储,但空间可以动态扩展。即它可以像数组一样操作,并且可以进行动态操作。通常体现在push_back() pop_back() 。

(2) 随机访问方便,它像数组一样被访问,即支持[ ] 操作符和vector.at()

(3) 节省空间,因为它是连续存储,在存储数据的区域都是没有被浪费的,但是要明确一点vector 大多情况下并不是满存的,在未存储的区域实际是浪费的。

(4) 在内部进行插入、删除操作效率非常低,这样的操作基本上是被禁止的。Vector 被设计成只能在后端进行追加和删除操作,其原因是vector 内部的实现是按照顺序表的原理。

(5) 只能在vector 的最后进行push 和pop ,不能在vector 的头进行push 和pop 。

(6) 当动态添加的数据超过vector 默认分配的大小时要进行内存的重新分配、拷贝与释放,这个操作非常消耗性能。 所以要vector 达到最优的性能,最好在创建vector 时就指定其空间大小

(5)vector向量的使用

(1)初始化vector对象的方式

T是一个类模板,类模板可以理解成预先定义好的数据结构和库就行了!
vector<T> v1;     // 默认的初始化方式,内容为空
vector<T> v2(v1);   // v2是v1的一个副本
vector<T> v3(n, i)   // v3中包含了n个数值为i的元素
vector<T> v4(n);   // v4中包含了n个元素,每个元素的值都是0
#include<vector> 
vector<int> vec; 
vector<char> vec;
vector<pair<int,int> > vec;
二维数动态数组vector<int,char> aa={
{1,”w”},
{2,”e”},
{3,”a”},
{4,”b”}
};
vector<vector<int>> ret(numRows);//创建杨辉三角的每一行的元素

.
.
(2)vector常用函数
在这里插入图片描述
(1)一维向量

(1)增加数组长度的操作
vector的数据类型定义的时候并没有限定元素数量,可以通过下标的形式增加元素
若是定义数组的时候没有指定数组长度,则可以直接增加数组的长度
若是定义数组的时候已经指定数组长度,则可以用api或者直接新开一个数组

(2)查询向量vector长度
    a.size()

(3)读取向量vector的第i个元素
	方法一:a.at(i)
	方法二:下标的方式a[0]

(4)删除向量vector元素
	这里用了双指针的方式,避开了数组删除元素的复杂操作

(5)向量vector定义
	1、一维向量:vector<int> a;/        vector<string> res;
	2、二维向量:vector<int,int> a

(6)向量Vector添加一个元素
	Arrary.push_back(num)

(2)二维向量

(1)二维的vector的读取方式
方式一:arr[i].second
方式二:用两个下标

二维数组就是一张表vector可以建立二维,甚至是三维的数据结构,而且vector的数据类型可以任意自己指定
对于C ++一般来说,我们会用向量vector来代替数组的功能,因为向量vector的元素可以是任意类型

.
.

(3)遍历方式
vector向量支持两种方式遍历,可以使用数组下标的方式,也可以使用迭代器的方式
在这里插入图片描述
.
.


五、链表(list)【渔网】

介绍

(1)链表的特征

(1)链表是内存的离散空间
(2)链表具有查找慢,插入/删除结点快的特点
(3)创建一个链表的时候不需要考虑链表的大小,因为链表节点的增加于删除非常快,也非常方便

.
(2)链表的原理

链表的每一个元素都储存了下一个元素的地址,从而使一系列随机的内存地址串联在一起,**好像一个寻宝游戏**,**当你前往第一个地址的时候,还会获得第二个地址的位置**

.

(3)链表增加\删除节点操作
1)增加节点操作
在这里插入图片描述

2)删除节点操作
在这里插入图片描述

3)操作复杂度为O(1)
在这里插入图片描述
.

(1)单链表

(1)单链表的结构
在这里插入图片描述
(2)单链表特征

(1)不支持随机访问,需要遍历去访问结点
(2)插入和删除只需要移动指针,时间复杂度为 O(1)
(3)每个结点需要额外的空间存储指针,需要的内存比数组大

(3)通常用class实现

定义链表的方法 ListNode dummy=ListNode(num); 
定义链表指针的方法:ListNode *prev=&dummy
ListNode *headA【headA是链表的头节点】
ListNode *temp = headA;【把头节点指针取出来】

.

(2)双链表

在单链表的基础上,除头结点外,每个结点多了一个存放前驱结点内存地址的指针
.

(1)介绍
list容器时双向链接链表,可在容器中的任何位置实现双向访问、快速插入和快速删除,但无法随机访问容器中list元素
list容器是一个线性链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。
.
.
(2)图例
在这里插入图片描述

.
.
(3)链表的特点

1)链表的优点
(a)动态的分配内存,当需要添加数据的时候不会像vector那样,先将现有的内存空间释放,在次分配更大的空间,这样的话效率就比较低了。

(b)【插入元素快】虽然随机检索的速度不够快,但是它可以迅速地在任何节点进行插入和删除操作。
.

(2)链表的缺点
(a)【遍历元素慢】由于其结构的原因,list 随机检索的性能非常的不好,因为它不像vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找,这样目标元素越靠后,它的检索时间就越长。检索时间与目标元素的位置成正比。不能随机访问,不支持[]方式和vector.at()

.

(4)双向链表list向量的使用

声明库

使用list容器之前必须加上<vector>头文件:#include<list>;
list属于std命名域的内容,因此需要通过命名限定:using std::list;也可以直接使用全局的命名空间方式:using namespace std;

.

使用指针

对于一个双向链表来说主要包括3个:
指向前一个链表节点的前向指针、
有效数据、
指向后一个链表节点的后向指针,双链表list游标一次只可以移动一步

(1)初始化list对象的方式

list<int> L0;    //空链表
list<int> L1(3);   //建一个含三个默认值是0的元素的链表
list<int> L2(5,2); //建一个含五个元素的链表,值都是2
list<int> L3(L2); //L3是L2的副本
list<int> L4(L1.begin(),L1.end());    //c5含c1一个区域的元素[begin, end]。

(2)list常用函数

检索首位两个元素或地址

c.front()      返回链表c的第一个元素。
c.back()      返回链表c的最后一个元素。

begin():返回list容器的第一个元素的地址
end():返回list容器的最后一个元素之后的地址

rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)
rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)

list的整体判断及排序


empty():判断链表是否为空
size():返回链表容器的元素个数
clear():清除容器中所有元素

c.assign(n,num)      将n个num拷贝赋值给链表c。
c.assign(beg,end)[beg,end)区间的元素拷贝赋值给链表c。

resize(n)      从新定义链表的长度,超出原始长度部分用0代替,小于原始部分删除。
resize(n,num)            从新定义链表的长度,超出原始长度部分用num代替。

c1.swap(c2);      将c1和c2交换。
swap(c1,c2);      同上。

c1.merge(c2)      合并2个有序的链表并使之有序,从新放到c1里,释放c2。
c1.merge(c2,comp)      合并2个有序的链表并使之按照自定义规则排序之后从新放到c1中,释放c2。
c1.splice(c1.beg,c2)      将c2连接在c1的beg位置,释放c2
c1.splice(c1.beg,c2,c2.beg)      将c2的beg位置的元素连接到c1的beg位置,并且在c2中施放掉beg位置的元素

remove(num)             删除链表中匹配num的元素。
reverse()       反转链表
unique()       删除相邻的元素

单个元素操作

insert(pos,num):将数据num插入到pos位置处(pos是一个地址)
insert(pos,n,num):在pos位置处插入n个元素num

erase(pos):删除pos位置处的元素

push_back(num):在链表尾部插入数据num
pop_back():删除链表尾部的元素

push_front(num):在链表头部插入数据num
pop_front():删除链表头部的元素

(3)遍历方式

双向链表list支持使用迭代器正向的遍历,也支持迭代器逆向的遍历,但是不能使用 [] 索引的方式进行遍历。

.

(3)循环链表

尾节点指针指向头结点
.

(4)静态链表

借助数组,伴随指向后继结点的指针
.

(5)跳表(面试不会让你手写)

(1)跳表的结构
在这里插入图片描述
(2)跳表的特征

(1)跳表查询的速度非常快,但是增加和删除元素非常慢
(2)跳表并不常用,因为逻辑十分复杂,维护十分困难,但是要掌握这种思想

(3)跳表的核心思想

(1)升维的方法:以空间复杂度换取时间复杂度
(2)计算机迭代次数少了,但是消耗的内存较多
(3)计算机的数据结构没有十全十美的,只有适用不同类型的数据类型

.


六、集合(Set)

介绍

(1)集合的特征

没有重复的元素的数据结构

(2)集合的实际应用

(1)把数组中重复的元素删除
(2)集合运算:交集、并集、非集的逻辑运算,用于数据处理

set内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值
.
.

(1)集合set

set 是一组元素的集合,但其中所包含的元素的值是唯一的,且是按一定顺序排列的,set 是内部排序的,内部以链表的方式组织、不允许有重复值

.

(2)可重复集合multiset

multiset是多重集合,它不要求集合中的元素是唯一的,也就是说集合中的同一个元素可以出现多次,且是按一定顺序排列的。
.

(3)无序集合unordered_set

unordered_set基于哈希表,因此元素的存储并不是顺序的

1)散列表的定义

哈希表也叫散列表,是根据**键码值(Key value)**而直接进行访问的数据结构,存放记录的数组叫做哈希表
映射函数叫做散列函数,有各种类型和功能的映射函数实现哈希表的

.

2)散列表的结构

散列表由键值对组成,是最有用的基本数据结构之一,一般来说,键是字符串,值是一个整形或者浮点型的数字,通俗来说,散列表“将输入映射到数字”,既可以将不同的输入映射到不同数字,也可以将不同的输入映射到相同的数字,做到**“提供键,返回值”**的功能

.

3)散列表的功能

(1)给参数的值找参数的位置
(2)它通过把关键码至映射到表中的一个位置来访问记录,以加快查找的速度

.

4)散列表的使用步骤

(1)创建映射(初始化定义哈希表)
        方法一:定义一个泛型的哈希表  
         unordered_map<int, int> hashtable;

		方法二:定义具体数据的哈希表  
	     unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };


(2)实现查找
	1)哈希表通过值寻找键
	 方法一:auto Key=hashtable(value);
	方法二:auto it = hashtable.find(target - nums[i]);
	方法三:map[nums[i]]


	2)判断键在不在哈希表中
   方法一: if(Key!=hashtable.end());
   方法二:map.count(nums[i]
	
(3)赋值(存入哈希表)
	map[nums[i]] = i;

【哈希表的使用特征】
涉及出现的次数问题,可以用哈希表,数组的元素作为键,元素的下标或者出现的次数作为值
哈希表的查询、插入是非常方便的,有一些问题可能需要使用哈希表和数组一起组合才能完成!

.

5)散列表的特征

(1)散列表的查找、插入和删除的速度都非常快
(2)散列表的数据结构并不需要自己去实现,编程语言已经提供了数据结构的实现了

6)散列表的实际应用

(1)用于查找【时间复杂度是常数,比简单查找和快速查找都要快】
(2)用于网站查找IP地址
(3)用于投票站
(4)用于缓存,避免了查询,调用变量的开销

.

(4)集合的使用

(1)创建集合
unordered_set<int> s;

(2)查询集合
s.find(x) != s.end()
s.find(某个元素),若元素存在数组中,则返回true,反之返回false

(3)插入集合
方法一:s.insert(x);
方法二:s.add(num)【插入的时候可以同时查看是否重复】

(4)查询集合元素个数
s.size()

(5)集合查询有无重复元素
s.count(num)

.
.


七、pair数据/键值对map

(1)pair数据

首先,map构建的关系是映射,也就是说,如果我们想查询一个键值,那么只会返回唯一的一个对应值。

但是如果使用pair的话,不仅不支持O(log)级别的查找,也不支持知一求一,因为pair的第一维可以有很多一样的,也就是说,可能会造成一个键值对应n多个对应值的情况。这显然不符合映射的概念。

pair<int, string>(1, “student_one”)

.
.

(2)键值对Map

(1)介绍

简单的理解:数组是用数字类型的下标来索引元素的位置,而map 是用字符型关键字来索引元素的位置。

map提供一种“键- 值”关系的一对一的数据存储能力。其“键”在容器中不可重复,且按一定顺序排列,其值是一种映射关系,可以重复

map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历。
.
.
(2)图例
在这里插入图片描述

(3)Map的使用
1)初始化/创建map对象的方法

一对一的数据映射。比如一个班级中,每个学生的学号跟他的姓名就存在着一一映射的关系,
学号用int描述,姓名用字符串描述(学号可以重复,姓名不可以重复)

// 实例化一个空map容器【最常用!】
map<char, string> m2;

// 实例化一个map容器,还有3组数据
map<int, string> m1 = { { 1, "guangzhou" }, { 2, "shenzhen" }, { 3, "changsha" } };

2)map数据的插入方法

第一种:用数组方式插入数据(括号内的是键,等号后的是值)【常用】

在数据的插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,但是用数组方式就不同了,它可以覆盖以前该关键字对应的值

Map<int, string> mapStudent;
       mapStudent[1] =  “student_one”;
       mapStudent[2] =  “student_two”;
       mapStudent[3] =  “student_three”;
       map<int, string>::iterator  iter;
       for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
       Cout<<iter->first<<”   ”<<iter->second<<end;
}

第二种:用insert函数插入pair数据【比较稳妥,直观一点】

在数据的插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的

#include <map>
#include <string>
#include <iostream>
Using namespace std;
Int main()
{
       Map<int, string> mapStudent;
       mapStudent.insert(pair<int, string>(1, “student_one”));
       mapStudent.insert(pair<int, string>(2, “student_two”));
       mapStudent.insert(pair<int, string>(3, “student_three”));
       map<int, string>::iterator  iter;//定义迭代器
       for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
       Cout<<iter->first<<”   ”<<iter->second<<end;
}
}
pair<char, string> p1=('S', "shenzhen")
insert(p1)//插入元素  p1 是通过pair函数建立的映射关系对

第三种:用insert函数插入value_type数据【这种太麻烦了几乎不采用】

Map<int, string> mapStudent;
       mapStudent.insert(map<int, string>::value_type (1, “student_one”));
       mapStudent.insert(map<int, string>::value_type (2, “student_two”));
       mapStudent.insert(map<int, string>::value_type (3, “student_three”));
       map<int, string>::iterator  iter;
       for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
       Cout<<iter->first<<”   ”<<iter->second<<end;
}

3)查看map的大小

size():返回容器中元素的个数

Int nSize = mapStudent.size();

4)map数据的查找(通过键找值)的方法
map在数据插入时内部实现保证数据有序

第一种:【bool】用count函数来判定关键字是否出现
无法定位数据出现位置,由于map的特性,count函数的返回值只有两个,要么是0,要么是1,出现的情况,当然是返回1了

第二种:用find函数来定位数据出现位置
它返回的一个迭代器(指针),当数据出现时,它返回数据所在位置的迭代器(指针),如果map中没有要查找的数据,它返回的迭代器(指针)等于end函数返回的迭代器(指针)

Map<int, string> mapStudent;
       mapStudent.insert(pair<int, string>(1, “student_one”));
       mapStudent.insert(pair<int, string>(2, “student_two”));
       mapStudent.insert(pair<int, string>(3, “student_three”));
       map<int, string>::iterator iter;
       iter = mapStudent.find(1);
if(iter != mapStudent.end())
{
       Cout<<”Find, the value is ”<<iter->second<<endl;
}
Else
{
       Cout<<”Do not Find”<<endl;
}

第三种:获取首位键值对的指针

begin():返回容器第一个元素的迭代器
end():返回容器最后一个元素之后的迭代器

.
.
5)map数据的遍历的方法
map容器支持迭代器正向方式遍历和迭代器反向方式遍历,同时也支持 [] 方式访问数据,[]中的索引值是键值,这个一定要清楚

第一种:用数组方式【常用这种方法】

Map<int, string> mapStudent;
       mapStudent.insert(pair<int, string>(1, “student_one”));
       mapStudent.insert(pair<int, string>(2, “student_two”));
       mapStudent.insert(pair<int, string>(3, “student_three”));
       int nSize = mapStudent.size()
for(int nIndex = 1; nIndex <= nSize; nIndex++)
       for(int nIndex = 0; nIndex < nSize; nIndex++)
{
       Cout<<mapStudent[nIndex]<<end;//map是内部排序的,直接取出来就好
}

第三种:应用前向迭代器【类似于指针的用法】

map<int, string>::iterator  iter;
       for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
       Cout<<iter->first<<”   ”<<iter->second<<end;
}

6)数据的清空与判空

(1)判空用empty()函数
判定map中是否有数据可以用empty()函数,它返回true则说明是空map

(2)元素全部清空clear()函数

(3)删除map中某一对元素
方法一:用关键字删除键值对【常用】

Int n = mapStudent.erase(key);//如果删除了会返回1,否则返回0

方法二:用迭代器删除键值对的方法

map<int, string>::iterator iter;
iter = mapStudent.find(key);//通过键找出值对应的指针
mapStudent.erase(iter);

7)交换两个map容器的元素

swap()

(3)可重复键值对Multimap

multimap和map 的原理基本相似,它允许“键”在容器中可以不唯一

(4)无序的键值对unordered_map

unordered_map存储机制是哈希表,即unordered_map内部元素是无序的。
std::unordered_map 就是以key来查找value而设计,不会根据key排序。
unordered_map的使用
unordered_map的用法几乎和map是一致的

.
.



八、队列(Queue)【排队】

(1)普通队列queue

(1)介绍
在这里插入图片描述
在这里插入图片描述

数据先进先出,后进后出(FIFO),不允许的操作,插队和删除是不被允许的

.
(2)queue容器的函数使用方法

queue容器的使用

queue容器存放在模板库:#include<queue>里,使用前需要先开这个库。

queue容器声明定义的方法

容器类型<变量类型> 名称
#include<queue> 
queue<int> q; 
queue<char> q; 
queue<pair<int,int> > q;

不允许的操作:插队和删除是不被允许的

(1)队列创建
queue<TreeNode*> Q;      //定义一个队列(这个队列是一个树形结构的队列)

(2)队列元素插入
Q.push(root);                     //把二叉树存入队列
Q.push(i);

(3)队列元素取出
Q.pop();

在这里插入图片描述
注意:queue并不支持全部vector的内置函数

1、queue不支持随机访问,即不能像数组一样地任意取值。
2、queue不可以用clear()函数清空,清空queue必须一个一个弹出。
3、queue也并不支持遍历,无论是数组型遍历还是迭代器型遍历统统不支持,所以没有begin(),end();函数

.
.

(2)双端队列

(1)介绍
deque(双端队列double-ended queue)——在功能上和vector相似,但是可以在前后两端向其中添加数据。

它允许较为快速地随机访问,但它不像vector 把所有的对象保存在一块连续的内存块,而是采用多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪。向deque 两端添加或删除元素的开销很小。它不需要重新分配空间,所以向末端增加元素比vector 更有效。

在这里插入图片描述
在这里插入图片描述

(2)deque 的特点
结合了堆栈和队列,入口和出口都可以进队和出队,既可以先入先出,又可以先入后出

(1) 随机访问方便,即支持[ ] 操作符和vector.at() ,但性能没有vector 好;
(2) 可以在内部进行插入和删除操作,但性能不及list ;
(3) 可以在两端进行push 、pop ;

.
(3)deque容器的使用
deque模板存储在C++STL的#include中

deque容器的常用函数
在这里插入图片描述

.

(3)优先级队列

(1)介绍
根据优先级来出队

priority_queue容器优先队列,具有最高值的元素始终排在队列的第一位
优先队列就是在这个队列的基础上,把其中的元素加以排序。其内部实现是一个二叉堆。所以优先队列其实就是把堆模板化,将所有入队的元素排成具有单调性的一队,方便我们调用。

.
(2)priority_queue容器的函数使用方法

1、定义
Priority_queue容器存放在模板库:#include里
(1)大根堆声明方式【常用】

#include<queue> 
priority_queue<int> q; 
priority_queue<string> q; 
priority_queue<pair<int,int> > q;

(2)小根堆声明方式【不常用】

priority_queue<int,vector<int>,greater<int> >q;

注意,当我们声明的时候碰到两个"<“或者”>"放在一起的时候,一定要记得在中间加一个空格。这样编译器才不会把两个连在一起的符号判断成位运算的左移/右移。
.

2、常用函数
在这里插入图片描述

.
.


九、栈(Stack)【试卷】

(1)介绍

栈的特征是对应数据顺序,数据先进后出,后入先出【LIFO】
在这里插入图片描述
堆栈类似于一叠便条,插入待办事项的便条放在最前面;读取待办事项时,你只需要读取最上面的便条,并将其删除,待办事项只有两种操作:压入(插入)和弹出(删除)
.

(2)stack容器的使用

1)stack容器的声明

stackstack容器存放在模板库:#include<stack>#include<stack> 
stack<int> st; 
stack<char> st; 
stack<pair<int,int> > st;

2、stack容器的常用函数
在这里插入图片描述

(1)定义一个堆栈
stack<char> stk;

(2)堆栈的入栈出栈操作
	出栈 stk.pop();
	入栈stk.push(ch);

(3)判断堆栈是否为空
stk.empty()

(4)取堆栈的最顶层元素
stk.top()

.

(3)栈的实际应用

(1)浏览器的前进与后退
(2)括号匹配
(3)表达式计算
.
.

.
.

十、树(tree)【针对数据库存储问题】

(1)泛型树的结构

在这里插入图片描述
.

(2)二叉树

(1)二叉树结构
在这里插入图片描述
(2)二叉树的特征

二叉树的每个分支只有两个

.

(3)二叉搜索树

(1)二叉搜索树结构
在这里插入图片描述
(2)二叉搜索树特征

(1)每个分支中,右分支节点的值总比左分支节点的值要大
(2)二叉搜索树存的是值,这样的结构有利于值的查找
(3)二叉搜索树不允许随机访问

(3)二叉搜索树的遍历

(1)递归遍历
(2)查询
(3)增加
(4)删除

(4)平衡二叉树

平衡二叉树的作用

为了提高树的执行效率,树的左右两边的高度要平衡,才能保证树不会退化成一个链表
.

1)AVL 树(面试一般不考察)

(1)判断树左右子树的高度是否在(-1~1)之间,若不在 ,使用旋转的方式整理该树,使之平衡

(2)记录左右子树的高度方法
在这里插入图片描述

(3)旋转操作方法
(1)左旋
在这里插入图片描述
(2)右旋
在这里插入图片描述

(3)左右旋
在这里插入图片描述
(4)右左旋
在这里插入图片描述

(4)注意
(1)AVL 树的代码实现十分复杂,面试的时候不会考察的,懂得理论和应用场景就行了
(2)每个结点需要存储额外的信息,且调整次数频繁
.
.

2)红黑树

1)红黑树定义
红黑树时一种近似平衡的二叉搜索树,它能够确保任何一个节点的左右子树的高度差小于两倍,具体来说,红黑树时满足 如下条件的二叉搜索树
1)每个节点要么是红色,要么是黑色
2)根节点是黑色
3)每个叶节点是黑色
4)不能有相邻的两个红色节点
5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

2)红黑树图例
在这里插入图片描述
.
.

(5)字典树(Trie)

1)字典树的定义

字典树又称单词查找树获键树,是一种树型结构,典型的应用是用于统计和排序大量的字符串,所以经常被搜索引擎系统用于文本词频的统计
.

2)字典树的数据结构

在这里插入图片描述
.

3)字典树的核心思想

(1)字典树是一个以(内存)空间换(CPU运算)时间的方法
(2)利用字符串的公共前缀来降低查询四件的开销以达到提高效率的目的

.

4)字典树的基本性质

(1)结点本身不存完整的单词
(2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
(3)每个节点的所有子节点路径代表的字符都不相同

.

5)字典树的优缺点

优点:最小限度的减少无所谓的字符串比较,查询效率比哈希表高
缺点:需要较大的存储空间
.

(6)树的实际应用

树的表达形式是不一样的,二叉树的每个位置是确定的,其他泛型树就不一定了

定义树的数据类型TreeNode* root
调用树的根节点root
调用树的左节点root->left
调用树的右节点root->right

判断树是否是空的
Root==nullptr

.
.

十一、图(graph)【针对优化问题】

(1)图的作用

用于模拟一系列的连接关系,建立复杂模型

.

(2)图的结构

图由节点(node)和边(edge)组成,类似于每个分支能形成一个循环的树,一个节点可能于众多的节点直接相连,这些节点成为该节点的邻居节点
.

(3)图的特征

遍历时需要记录已经访问过的结点
.

(4)图的实际应用

(1)建立复杂模型
(2)用于KNN搜索算法和简单的机器学习方法
(3)图优化基础

.
.


总结

这是基础,不用自己实现的,编程语言已经提供了数据结构的实现了,我们仅仅需要理解和选择合适的数据结构调用就行

要实现功能,用熟几个九很好

其他

 整体框架
Class solution{
Public:

/

} ;

stl库里面的底层实现–数据结构与算法
各种数据结构和实现多琢磨琢磨

STL容器的理解

https://blog.csdn.net/cnds123/article/details/118765133

vector的数据结构的使用

vector的数据结构有resize就可以用at,因为开了内存空间,没有resize就只能用push back动态开内存空间
Vector定义了没有push和resize操作,直接at访问导致崩溃
没有创建就直接访问数组导致崩溃问题

将一个vector内容赋值给另一个vector
https://zhidao.baidu.com/question/515060119.html
https://blog.csdn.net/jiuguaqiao6494/article/details/119122315
.
.

优先级队列的实现方式

优先级队列与普通队列的不同,优先级队列不再遵循FIFO的规则,而是按照自定义规则(优先级高低)将对应元素取出队列,比如取出优先级高的元素,或者淘汰优先级低的元素

实现原理:
实现这种功能,一般有两种方案,一种是在入队列时,根据入队元素的优先级,按规则放入相应位置,比如一个最大优先级数据/最小优先级数据即使入队列最晚,但是要放在队列的首位;另一种方案,入队列时依旧放在队列的末尾,在出队列的时候,再按照优先级比较,然后将优先级高的取出队列

实现可使用的容器类型:
1、数组vector
2、有序数组sort_vector
3、列表list
4、二叉树
5、堆栈heap

参考用例

typedef struct Heap* PriorityQueue;

PriorityQueue createPriorityQueue(uint16_t capacity);

ErrorCode resizePriorityQueue(PriorityQueue queue, uint16_t capacity = 0);

void deletePriorityQueue(PriorityQueue queue);

bool isFull(PriorityQueue queue);

bool isEmpty(PriorityQueue queue);

ErrorCode insert(PriorityQueue queue, Node* new_node);//若插入节点时,超过capacity会capacity+1继续插入

ErrorCode insertOrRearrange(PriorityQueue queue, int from_heap_id = -1, Node* new_node = NULL);

bool remove(PriorityQueue queue, Node* min_cost_node = NULL);//在优先级队列中,弹出代价最小的节点

PriorityQueue createPriorityQueue(uint16_t capacity) {
    PriorityQueue queue = (PriorityQueue)malloc(sizeof(Heap));
    queue->capacity_ = capacity;
    queue->current_size = 0;
    queue->nodes = (Node*)malloc(sizeof(Node) * capacity);//分配n个节点内存,改成vector实现
    if (!queue || !queue->nodes) 
    {
        queue = NULL;
    }
    return queue;
};  

ErrorCode resizePriorityQueue(PriorityQueue queue, uint16_t capacity) {
    if (queue->capacity_ == 0) {
        queue->capacity_ = 100;
        queue->current_size = 0;
        queue->nodes = (Node*)malloc(sizeof(Node) * queue->capacity_);
        if (!queue->nodes) return ErrorMalloc;
    }
    else {
        queue->capacity_ = 1 + (queue->capacity_ << 1);
        queue->nodes = (Node*)realloc(queue->nodes, sizeof(Node) * queue->capacity_);
        if (!queue->nodes) return ErrorRealloc;
    }
    return SUCCESSED;
};

void deletePriorityQueue(PriorityQueue queue) {
    if (queue) {
        if (queue->nodes) {
            free(queue->nodes);
            queue->nodes = NULL;
        }
        free(queue);
        queue = NULL;
    }
};

bool isFull(PriorityQueue queue) {
    return queue->capacity_ == queue->current_size;
};

bool isEmpty(PriorityQueue queue) { return 0 == queue->current_size; };

ErrorCode insert(PriorityQueue queue, Node* new_node) {
    if (isFull(queue)) { 
        if (resizePriorityQueue(queue) != SUCCESSED) return ErrorMalloc;
    }

    uint16_t index, parent;
    for (index = queue->current_size; index > 0; ) {
        parent = floor((index-1)*0.5);
        if (new_node->F < queue->nodes[parent].F) {
            queue->nodes[index] = queue->nodes[parent];
            index = parent;
        }
        else
            break;
    }
    queue->current_size++;
    queue->nodes[index] = *new_node;
    return SUCCESSED;
};


bool remove(PriorityQueue queue, Node* min_cost_node) {
    if (!isEmpty(queue)) {
        *min_cost_node =  queue->nodes[0];//第一个就是代价最低的
        
        uint16_t index = 0, left_child, right_child, small_index;
        Node* compare_element;
        compare_element = &queue->nodes[queue->current_size - 1];//与上一个元素相比
        queue->current_size--;

        //调整优先级队列
        for (index = 0; index < floor(queue->current_size * 0.5); ) {
            left_child = (index << 1) + 1;
            right_child = left_child + 1;
            if (queue->nodes[left_child].F < queue->nodes[right_child].F)
                small_index = left_child;
            else
                small_index = right_child;
            
            if ( compare_element->F > queue->nodes[small_index].F) {
                queue->nodes[index] = queue->nodes[small_index];
                index = small_index;
            }
            else
                break;
        }
        queue->nodes[index] = *compare_element;
        return true;
    }
    return false;
};

.
.

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盒子君~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值