我肝了long long time 的笔记(别白嫖啊)

#include<assert.h>    //设定插入点
#include <ctype.h>     //字符处理
#include <errno.h>     //定义错误码
#include <float.h>     //浮点数处理
#include <fstream.h>   //文件输入/输出
#include <iomanip.h>   //参数化输入/输出
#include<iostream.h>   //数据流输入/输出
#include<limits.h>    //定义各种数据类型最值常量
#include<locale.h>    //定义本地化函数
#include <math.h>     //定义数学函数
#include <stdio.h>    //定义输入/输出函数
#include<stdlib.h>    //定义杂项函数及内存分配函数
#include <string.h>    //字符串处理
#include<strstrea.h>   //基于数组的输入/输出
#include<time.h>     //定义关于时间的函数
#include <wchar.h>    //宽字符处理及输入/输出
#include <wctype.h>    //宽字符分类
#include <algorithm>    //STL通用算法
#include <bitset>     //STL位集容器
#include <complex>     //复数类
#include <deque>      //STL双端队列容器
#include <exception>    //异常处理类
#include <functional>   //STL定义运算函数(代替运算符)
#include <list>      //STL线性列表容器
#include <map>       //STL 映射容器
#include <ios>      //基本输入/输出支持
#include<iosfwd>     //输入/输出系统使用的前置声明
#include <istream>     //基本输入流
#include <ostream>     //基本输出流
#include <queue>      //STL队列容器
#include <set>       //STL 集合容器
#include <sstream>    //基于字符串的流
#include <stack>      //STL堆栈容器    
#include <stdexcept>    //标准异常类
#include <streambuf>   //底层输入/输出支持
#include <string>     //字符串类
#include <utility>     //STL通用模板类
#include <vector>     //STL动态数组容器
#include <complex.h>  //复数处理
#include <fenv.h>    //浮点环境
#include <inttypes.h>  //整数格式转换
#include <stdbool.h>   //布尔环境
#include <stdint.h>   //整型环境
#include <tgmath.h>   //通用类型数学宏
using namespace std; 
int isalpha(int ch) //若ch是字母('A'-'Z','a'-'z')返回非0值,否则返回0
int isalnum(int ch) //若ch是字母('A'-'Z','a'-'z')或数字('0'-'9') 返回非0值,否则返回0
int isascii(int ch) //若ch是字符(ASCII码中的0-127)返回非0值,否则返回0
int iscntrl(int ch) //若ch是作废字符(0x7F)或普通控制字符(0x00-0x1F) 返回非0值,否则返回0
int isdigit(int ch) //若ch是数字('0'-'9')返回非0值,否则返回0
int isgraph(int ch) //若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否则返回0
int islower(int ch) //若ch是小写字母('a'-'z')返回非0值,否则返回0
int isprint(int ch) //若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否则返回0
int ispunct(int ch) //若ch是标点字符(0x00-0x1F)返回非0值,否则返回0
int isspace(int ch) //若ch是空格(''),水平制表符('\t'),回车符('\r'), 走纸换行('\f'),垂直制表符('\v'),换行符('\n')返回非0值,否则返回0
int isupper(int ch) //若ch是大写字母('A'-'Z')返回非0值,否则返回0
int isxdigit(int ch)//若ch是16进制数('0'-'9','A'-'F','a'-'f')返回非0值, 否则返回0
int tolower(int ch) //若ch是大写字母('A'-'Z')返回相应的小写字母('a'-'z')
int toupper(int ch) //若ch是小写字母('a'-'z')返回相应的大写字母('A'-'Z')
int     abs(inti)                    //返回整型参数i的绝对值
double  cabs(struct complexznum)     //返回复数znum的绝对值
double  fabs(doublex)                //返回双精度参数x的绝对值
long   labs(longn)                   //返回长整型参数n的绝对值
double   exp(doublex)                //返回指数函数ex的值
double frexp(double value,int*eptr)  //返回value=x*2n中x的值,n存贮在eptr中
double ldexp(double value,intexp);   //返回value*2exp的值
double   log(doublex)                //返回logex的值
double log10(doublex)                //返回log10x的值
double   pow(double x,doubley)       //返回xy的值
double pow10(intp)                   //返回10p的值
double  sqrt(doublex)                //返回+√x的值
double  acos(doublex)                //返回x的反余弦cos-1(x)值,x为弧度
double  asin(doublex)                //返回x的反正弦sin-1(x)值,x为弧度
double  atan(doublex)                //返回x的反正切tan-1(x)值,x为弧度
double atan2(double y,doublex)       //返回y/x的反正切tan-1(x)值,y的x为弧度
double   cos(doublex)                //返回x的余弦cos(x)值,x为弧度
double   sin(doublex)                //返回x的正弦sin(x)值,x为弧度
double   tan(doublex)                //返回x的正切tan(x)值,x为弧度
double  cosh(doublex)                //返回x的双曲余弦cosh(x)值,x为弧度
double  sinh(doublex)                //返回x的双曲正弦sinh(x)值,x为弧度
double  tanh(doublex)                //返回x的双曲正切tanh(x)值,x为弧度
double hypot(double x,doubley)       //返回直角三角形斜边的长度(z), x和y为直角边的长度,z2=x2+y2
double  ceil(doublex)                //返回不小于x的最小整数
double floor(doublex)                //返回不大于x的最大整数
void   srand(unsignedseed)           //初始化随机数发生器
int    rand()                        //产生一个随机数并返回这个数
double  poly(double x,int n,double c[])//从参数产生一个多项式
double  modf(double value,double *iptr)//将双精度数value分解成尾数和阶
double  fmod(double x,doubley)       //返回x/y的余数
double frexp(double value,int*eptr)   //将双精度数value分成尾数和阶
double  atof(char*nptr)              //将字符串nptr转换成浮点数并返回这个浮点数
double  atoi(char*nptr)              //将字符串nptr转换成整数并返回这个整数
double  atol(char*nptr)              //将字符串nptr转换成长整数并返回这个整数
char   *ecvt(double value,intndigit,int *decpt,int *sign) //将浮点数value转换成字符串并返回该字符串
char   *fcvt(double value,intndigit,int *decpt,int *sign) //将浮点数value转换成字符串并返回该字符串
char   *gcvt(double value,intndigit,char *buf) //将数value转换成字符串并存于buf中,并返回buf的指针
char  *ultoa(unsigned long value,char*string,int radix) //将无符号整型数value转换成字符串并返回该字符串,radix为转换时所用基数
char   *ltoa(long value,char*string,int radix) //将长整型数value转换成字符串并返回该字符串,radix为转换时所用基数
char   *itoa(int value,char*string,int radix) //将整数value转换成字符串存入string,radix为转换时所用基数
double atof(char *nptr) //将字符串nptr转换成双精度数,并返回这个数,错误返回0
int   atoi(char *nptr) //将字符串nptr转换成整型数,  并返回这个数,错误返回0
long   atol(char *nptr)//将字符串nptr转换成长整型数,并返回这个数,错误返回0
double strtod(char *str,char **endptr)//将字符串str转换成双精度数,并返回这个数,
long   strtol(char *str,char**endptr,int base)//将字符串str转换成长整型数, 并返回这个数,
int         matherr(struct exception *e) //用户修改数学错误返回信息函数(没有必要使用)
double      _matherr(_mexcep why,char *fun,double*arg1p, double *arg2p,double retval)//用户修改数学错误返回信息函数(没有必要使用)
unsigned int _clear87()  //清除浮点状态字并返回原来的浮点状态
void        _fpreset()   //重新初使化浮点数学程序包
unsigned int _status87()  //返回浮点状态字
c++常用库函数
头文件:cstring
{
void *memcpy(void *dest,const void *src,sizet const);//从src拷贝count个字节到dest
void *memset(void *dest,int. c,sizet const);//设置dest的前count个字节为字符c
int strcmp(const char *string1,constchar *string2);//返回值<0,string1小于string2;返回值=0,string1等于string2,返回值>0,string1大于string2 
char *strcpy(char *strDestination,constcha *strSource);//把源字符串strSource(包括结尾的空字符)拷贝到strDestination所指的位置
size_t strlen(const char *string);//返回string中的字符个数,不包括尾部NULL。没有指出错误的返回值 
}
头文件:cctpye
{
int isalnum(int c);//测试c是否字母或数字
int isalpha(int c);//测试c是否字母
int islower(int c);//测试c是否小写字母
int isupper(int c);//测试c是否大写字母 
int tolower(int c);//将字符转换为小写字符
int toupper(int c);//将字符转换为大写字母 

头文件:cmath
{
int abs(int n);//求绝对值
double acos(double x);//计算并返回范围在0到π弧度的x的反余弦值
double asin(double x); //计算并返回范围在-π/2到π/2弧度之间的x的反正弦值
{
double atan(double x);
double atan2(double y,double x);//atan返回x的反正切值,atan2返回y/x的反正切值
}
double ceil(double x);//对x向上取整,并以double型浮点数形式存储结果
double cos(double x);//计算并返回x的余弦值(cos)
double exp(double x);//计算并返回e的x幂
double fabs(double x);//计算并返回浮点参数x的绝对值
double floor(double x);//向下取整,并以double型浮点数形式存储结果
double log(double x);//计算并返回x的自然对数 
double log10(double x);//计算并返回x的以10为底的对数
double pow(double x,double y);//计算并返回x的y次幂
double sin(double x);//sin返回x的正弦值
double sqrt(double x);//计算并返回x的平方根
double tan(double x);//sin返回x的正切值
}
头文件:iostream 
{
type max(type a,type b);//比较a和b并返回其中较大者 
}
结构体:
struct a
{
    int g;
    char s;
    float x;
 } ;
&取地址运算 
*取地址运算
指针变量
int *p;
float *q;
int a=5 ,*p=&a;
int a=5,*p;
p=&a;
p=NULL;//不指向
p=&a;//指向a
p1=&a;
p2=p1;//可以相互赋值
p1=new int;
p1=new(int);//分配存放一个整型数据的空间
p2=new int[10];同上
p+n或p-n指后面或前面n个
p++或p--指向n后面或前面1个元素
用两个指针指向同一数组的不同元素,可以位置次序,小前大后
指向指针的指针,称为多重指针
delete释放指定的内存单元
指针变量名=new数据类型(初始值);
计算机执行new运算符时,按照数据类型和数组大小。 
单向链表的第一个节点只有后继节点,没有前驱节点。 
单向链表的最后一个结点的指针域为空(NULL)。为了访问列表,需要一个头指针指向第一个节点。
定义:
struct Node//定义链表节点
{
    int data;//数据域 
    Node *next;//指针域 
};
Node *head;//头指针 
数组是顺序存储结构,当插入或删除时,需要移动元素。 
链表是链式存储结构,当插入或删除时,不需要移动元素。 
文件:
stream类的流文件对文件进行操作。 
可以先在桌面上保存。 
头文件:
#include<fstream> 
#include <cstdio>
#include <algorithm>
例如: 
/*
#include<iostream>
#include<fstream>
using namespace std;

ifstream fin("abc.in");//fin是自己取的 

ofstream fout("abc.out");//fout是自己取的
//如"d:/c/abc.in" ,"d:/c/abc.out"
int main()
{
    int a, b;
    fin >> a >> b;//fin:输入缓冲区 
    fout << a+b;//fout:输出缓冲区 
    return 0;
    
}
*/
fin.close()   fout.close()//都是关闭文件 
fin.eof()//(end of file)用来判断文件是否结束    如果返回0表示文件没结束  如果返回1说明文件结束了 
fout.open//用来打开文件 
FILE *fin, *fout;//定义了两个FILE类型的指针变量
fopen("abc.in","r");//打开文件,以只读的方式打开
fopen("abc.out","w");//打开文件,以只写的方式打开 
fscanf(fin,"%d",&n);//读入数据
fprintf(fout,"%d",a[i]);//输出数据
feof(fin)//如果文件结束返回值为假,没结束返回值为真
要利用计算机解决实际问题,必须将需要处理的信息,抽象到计算机中,变成计算机 
中,变成计算机可以识别和处理的符号,因此,数据是可以输入计算机中,且能被计算机处理的
各种符号的集合
特点: 
1.数据是信息的载体
2.数据是对客观事物符号化的表示
3.数据能够被计算机识别、存储和加工
类别:
1.数值型数据:指可以进行加减乘除、平方、开方等数值运算的数据,如整数、实数等
2.非数值型数据:通常不能进行数值运算,如文字、图像、图形、声音等 
数据元素:
数据中有些部分的光系比较紧密,在进行加工操作时,是作为一个整体来进行操作的,
将它们成为数据元素。因此:数据元素是数据的基本单位,在计算机程序中通常作为一个
整体来进行考虑和处理。如学生信息表中,某一位同学的相关信息即为一个数据元素。
数据元素也简称为元素,或记录、结点、顶点等。
数据项:数据元素又是由若干数据项组成的,如学生信息表中,对于每个学生
的祥光信息,都包括学号、姓名、性别、年级等,这些都是构成学生信息元素的
若干数据项,因此:数据项是构成数据元素的不可分割的最小单位。 
数据对象:
数据对象是性质相同的数据元素的集合,是数据的一个子集,如:
大写字母字符数据对象是集合C={'A','B'...'Z'}
正整数数据对象是集合N={0,1,2...}
高年级学生数据对象是由若干条学生信息记录构成的集合。
数据、数据元素、数据项之间的光系:
数据>数据对象>数据元素>数据项
数据结构:
数据元素不是孤立存在的,它们之间存在某种光系,数据元素相互之间的关系成为结构。
因此:
数据结构指想互之间存在一种或多种特定光系的数据元素的集合。
公式:程序=数据结构+算法
数据结构包括三方面内容:
1.逻辑结构:指数据元素之间的逻辑关系 
2.存储结构:又成为数据的物理结构,指数据元素及护具元素之间的关系如何在计算机内存
中表示(又称映像) 
3.运算和实现:指对数据元素可以进行的操作,及这些操作在相应的存储结构上如何实现。
逻辑结构和存储结构的关系:
1.逻辑结构是数据结构的抽象,存储结构是数据结构的实现。
2.存储结构是数据元素本身的映像和逻辑光系的映像。
3.逻辑结构和存储结构两者综合起来,便建立了数据元素之间的结构关系。
逻辑结构:
逻辑结构指数据元素之间的逻辑关系。数据元素的逻辑关系与数据元素的存储位置无关
是独立于计算机的,是从集体问题抽象出来的数学模型
逻辑结构种类的划分:
方法一:
1.线性结构:
有且仅有一个开始结点和终端结点,且所有结点都最多只有一个直接前趋和一个直接后继。
即数据元素之间存在一对一的线性关系。例如:线性表、栈、队列、串。 
2.非线性结构:一个结点可能有多个直接前趋和直接后继。
即数据元素之间存在一对多的层次关系或多对多的任意光系。
例如:树、图。
在划分,可以分为两种:
(1)树形结构:数据元素存在一对多的关系。例如:二叉树、堆。
(2)图形结构:数据元素存在多对多的关系。例如:无向图、有向图。 
方法二: 
1.集合结构:
数据元素之间除了同数一个集合的关系之外,没有任何其他关系。例如:并查集 
2.线性结构
3.树形结构
4.图形结构 
存储结构:
逻辑结构指数据元素的逻辑结构在计算机中的存储形式。包括数据元素的机内表示和关系
的机内表示。 
分类:
1.顺序存储结构:
用内存中一组连续的存储单元,按照数据元素的顺序,依次存储数据元素。数据元素之间的
逻辑光系由元素的存储位置来表示。
使用数组实现,利用下标指示下一个元素存储的相对位置。
2.链式存储结构:
用内存中一组任意的(可以不连续的)存储单元来存储数据元素,数据元素之间的逻辑
关系用指针来表示。
使用链表实现,利用指针指示下一个元素存储的位置。 
3.索引存储机构:
在存储结点信息的同时,建立附加的索引表。类似于手机中的通讯录。
4.散列存储结构:
根据结点的关键字直接计算出该结点的存储地址。 
对计算机有贡献的人:
1.图灵Alan Mathison Turing
1966年建立图灵奖,1950人工智能之父
2.约翰·冯·若依曼
3.克劳德·艾尔伍德·香农
4.莫奇莱和学生埃克特
5.杰克·基尔比
6.丹尼斯·里奇
7.理查德·马修·斯托曼
8.华罗庚
9.王选
模拟链表要两个数组实现
数据数组data
游标数组next
虚拟链表第一个元素是data[next[0]] 
栈:只能在一端进行插入和删除的线性表,叫栈
?栈顶
?栈底
?空栈
LIFO:last in first out//先入后出,后入先出
1.栈的初始化:初始化一个空栈,动态分配Maxsize大小的空间
2.入栈:先判断是否满栈
3.出栈:先判断是否空栈
4.取栈顶元素:返回栈顶元素的值,和出栈不同,取栈顶元素时,栈内元素不变 
如:
/* 
#include <iostream>
using namespace std;
truct Stack
{
    int *stack;
    int top;    
};
//初始化栈 
void InitStack(Stack &s)
{
    
}
//入栈 
void Push(Stack &s,int a)
{
    
}
//出栈 
int Pop(Stack &s)
{
    
}
//读取栈顶元素
int Peek(Stack &s)
{
    

}
//清空栈 
void ClearStack(Stack &s) 
{
    
}
bool EmptyStac(Stack &s)
{
    
}
int main(){
    Stack s;
    InitStack(s);
    int a[8]={5,8,2,4,15,27,42,36};
    for(int i=0;i<8;i++){
        Push(s,a[i]);
    }
    cout<<Peek(s)<<endl;
    while(!EmptyStack(s)){
        cout<<Pop(s)<<" ";
    }
    clearStack(s);
    return 0;

*/
头文件:<stack> stack<int> s;
empty()判断栈是否为空
size()返回栈中元素的个数
top()返回栈顶元素
push(vai)入栈,将vai压入栈中
pop()出栈 
一代:20世纪40年代
二代:20世纪50年代
三代:20世纪60年代
四代:20世纪70年代
五代:20世纪80年代 LSI(大规模集成电路) VLSI(超大规模集成电路) 
摩尔定律
1.单片机 
2.微型机 
3.工作站 
4.小型机 
5.大、中型机 
6.巨型机 
队列:队列是一种操作受限的特殊线性表,只能一段进,一段出。
称为队尾,删除的一段称为队头
FIFO:first in first out
先进先出/后进后出
head:队头指针
tail:队尾指针
hhead==tail:表示队列已满
tail==n-1:表示队列已满
先把指针head加1,再把待删除元素赋给临时变量t。 
head=(head+1)%n;
t=Q[head]; 
队列有假溢出,tail指针 
当数组的贮存区看成一个首尾相连的环形区域,这叫循环队列。
初始化:头指针和尾指针都设为数组的第一个元素。
先把指针tail加1,再把元素x插入队尾。
tail=(tail+1)%n;
Q[tail]=x; 
(tail+1)%n==head //判断队列满 
#include <bits/stdc++.h>
using namespace std;
const int maxsize=100;
struct queue
{
    int *queue;
    int head,tail;
};
void initqueue(queue &q)
{
    q.queue=new int[maxsize];
    q.head=0;
    q.tail=0;
}
bool fullqueue(queue &q)
{
    return q.tail=maxsize-1;
}
bool emptyqueue(queue &q)
{
    reutnr q.head==q.tail;
}
void push(queue &q,int a)
{
    if(!fullqueue())
    {
        q.tail=(q.tail+1)%maxsize;
        q.queue[q.tail]=a;
    }    
}
int pop(queue &q)
{
    if(!emptyqueue(q))
        return q.queue[++q.head];
}
int peekhead(queue &q)
{
    return q.queue[q.tail];
}
int peektail(queue &q)
{
    return q.queue[q.tail];     
}
void clearqueue(queue &q)
{
    if(q.queue)
    {
        delete []q.queue;
        q.queue
    }
}
int main()
{
    queue q;
    initiqueue(q);
    int a[8]={5,8,2,4,15,27,42,36};
    for(int i=0;i<8;i++)
        push(q,a[i]);
    cout<<peekhead(q)<<" ";
    cout<<peektail(q)<<endl;
    cout<<"head="<<q.head<<endl;
    cout<<"tail="<<q.tail<<endl;
    while(!emptyqueue(q))
        cout<<pop(q)<<" ";
    cout<<endl;
    cout<<"head="<<q.head<<endl;
    cout<<"tail="<<q.tail<<endl;
    clearqueue(q);
    return 0;
}
#include<queue>//容器
empty()//测试队列是否为空
size()//返回队列中元素的个数
front()//回队头元素(注意:不是入队)
back()//返回队尾元素(注意:不是出队) 
push(val)//入队
pop()//出队 
树是由n(n>=0)个节点(元素)组成的有限集合,
当n=0是,称为空树。
对于任何一颗非空树(n>0),他满足以下两个条件:
1.有且仅有一个节点没有前驱。
2.除根节点外,其余节点可分成m(m>=0)个互不相交的有限集合:
(T0,T1,T2,T3,...,Tm-1)。 
树的逻辑结构表示数据之间的关系是一对多或者多对一的关系。它的结构特点具有明显的层次关系,
是一种十分重要的非线性的数据结构 
每个节点均有0个或多个后继节点
节点的度:
一个节点中拥有的子树个数 
树的度:
数的所有节点的度最大值 。即树中的最大分值数为树的度 
叶节点(Leaf):
度为0的结束。
其余节点称为非叶子结点或非终结结点 
分支节点:
度不为0的节点 
内部节点:
除根节点以外的分支节点称为内部节点 
外部节点:
一棵树所有叶节点称为外部节点 
父节点(Parent):
有子树的结点,是其子树的根节点的父节点 
子节点(child):
若A结点是B结点的父节点,则称B结点是A结点的子节点。子节点也称为孩子结点
兄弟结点:
具有同一父节点的各结点,彼此是兄弟结点
堂兄弟结点:
父节点在同一层的各结点,彼此是堂兄弟结点
路径和路径长度:
从结点n1到nk的路径为一个结点序列:n1,n2,...,nk,其中ni是ni+1的父节点。
祖先结点(Ancestor):
沿树根到某一节点路径上的所有结点,都是这个节点的祖先结点。
子孙结点:
某一结点子树中的所有节点 
结点的层次:
规定根结点在1层,其他任一结点的层数,是其父节点的层数加1.
即:根节点在第一层,根的孩子为第2层,以此类推,某结点为第k层,则其孩子为k+1层。
树的深度或高度:
树中所有结点中的最大层次
有序树和无序树:
如果树中每棵子树从左到右的排序拥有一定顺序,不得互换,则称为有序树,否则无序树。
森林:
m(m>=0)棵互不相交的树的集合
二叉树T是一个有穷的结点集
可以为空
若不为空,左子树TL和右子树TR互不相交的二叉树组成
二叉树子树有左右顺序之分
斜二叉树
满二叉树(Full Binary True),又称为完美二叉树(含有(2^k)-1个结点)
完全二叉树(Complete Binary True) 
一个二叉树第i层的最大结点数为:2^(i-1),i>=1。
深度为k的二叉树,其最大结点总数为:(2^k)-1,k>=1。
具有n个结点的完全二叉树,其深度为floor(log(2)(n))+1,floor为向下取整
如果对一颗有n个结点的完全二叉树的每个节点,按由上到下、由左到右进行编号,对于任意一个
结点i的编号,有如下规律:
如果i=1,则该节点为二叉树的根节点,无父节点;
如果i>1,则该结点的父节点为i/2(整除)
如果2*i>n,则该节点无左孩子,否则其左孩子为2*i
如果2*i+1>n,则该结点无右孩子,否则其右孩子为2*i+1
int Data[N];
int L_Child[N];
int R_Child[N];
int Root;
用结构体来储存二叉树
先序遍历:根左右
中序遍历:左根右 
后序遍历:左右根

集合:
集合是由一些不重复的数据组成的,在c++中我们常用的集合是set。 
c++中set的实现在一个<set>头文件中代码开头引入这个头文件,并且加上一句using namespace std;
如: 
#include <set>
using namespace std;
c++中直接构造一个set的语句为:set<T> s    //s是名称,T是数据类型,初始的时候s是空集合。如:set<int>aa,set<string> bbb 
使用insert()函数可以向集合中插入一个新的元素。如果集合中已经存在了某个元素,再次插入不会产生任何效果,集合中是不会出现重复元素的。
例如:
/*
#incldue <set>
#include <string>
using namespace std;
int main()
{
    set<string> country;           //{}
    country.insert("China");       //{"China"}
    country.insert("America");     //{"China","America"}
    country.insert("France");      //{"China","America","France"}
    country.insert("China");       //{"China","America","France"}
    return 0;
}
*/ 
使用erase()函数删除集合中的一个元素,如果集合中不存在这个元素,那么不进行任何操作。 
例如:
/*
#include <set>
#include <string>
using namespace std;
int main()
{
    set<string> country;           //{}
    country.insert("China");       //{"China"}
    country.insert("America");     //{"China","America"}
    country.insert("France");      //{"China","America","France"}
    country.erase("America");      //{"China","France"}
    country.erase("England");      //{"China","France"}
    return 0;
}
*/ 
如果你想知道某个元素是否在集合中出现,你可以直接用count()函数。如果集合中存在我们要查找的元素,返回1,否则会返回0。
/*
#include <set>
#include <string>
#include <stdio.h>
using namespace std;
int main() {
    set<string> country;  // {}
    country.insert("China"); // {"China"}
    country.insert("America"); // {"China", "America"}
    country.insert("France"); // {"China", "America", "France"}
    if (country.count("China")) 
    {
        cout << "China belong to country" << endl;
    }
    return 0;
}
*/
C++ 通过迭代器可以访问集合中的每个元素,迭代器就好像一根手指指向set中的某个元素。通过操作这个手指,
我们可以改变它指向的元素。通过*(解引用运算符,不是乘号的意思)操作可以获取迭代器指向的元素。通
过++操作让迭代器指向下一个元素,同理--操作让迭代器指向上一个元素。
迭代器的写法比较固定,set<T>::iterator it就定义了一个指向set<T>这种集合的迭代器it,T是任意的数据
类型。其中::iterator是固定的写法。
begin函数返回容器中起始元素的迭代器,end函数返回容器的尾后迭代器。
/*
#include <set>
#include <string>
#include <iostream>
using namespace std;
int main() {
    set<string> country;  // {}
    country.insert("China"); // {"China"}
    country.insert("America"); // {"China", "America"}
    country.insert("France"); // {"China", "America", "France"}
    for (set<string>::iterator it = country.begin(); it != country.end(); it++) 
    {
        cout << *it << endl;
    }
    return 0;
}
*/
注意,在 C++ 中遍历set是从小到大遍历的,也就是说set会帮我们排序的。
C++中调用clear()函数就可以清空set,同时会清空set占用的内存。
在set中插入、删除、查找某个元素的时间复杂度都是O(log n),并且内部元素是有序的。
C++ set 函数总结  
+-------+---------------------------+----------+
|函数   |  功能                     |时间复杂度|
+-------+---------------------------+----------+
|insert |  插入一个元素             |  O(log n)| 
+-------+---------------------------+----------+
|erase  |  删除一个元素             |  O(log n)| 
+-------+---------------------------+----------+
|count  |  统计集合中某个元素的个数 |  O(log n)|
+-------+---------------------------+----------+
|size   |  获取元素个数             |  O(1)    |
+-------+---------------------------+----------+
|clear  |  清空                     |  O(n)    |
+-------+---------------------------+----------+
set经常会配合结构体来使用,用set来储存结构体和vector有些区别set是需要经过排序的。
系统自带的数据类型有默认的比较大小的规则,而我们自定义的结构体,系统是不可能知道这个
结构体比较大小的方式的。 所以我们需要用一种方式来来告诉系统怎么比较这个结构体的大小。
其中一种方法叫做运算符重载,我们需要重新定义小于符号。
下面的代码定义了一个重载了小于符号的结构体:
/*
struct Node { 
    int x, y;
    bool operator<(const Node &rhs) const {
        if(x==rhs.x){
            return y<rhs.y;
        }
        else{
            return x<rhs.x;
        }
    }
*/
operator<表示我们要重载运算符<,可以看成是一个函数名。rhs是"right hand side"的简称,有右操作数的
意思,这里我们定义为一个const引用。因为该运算符重载定义在结构体内部,左操作数就当前调用operator<
的对象。 特别要注意,不要漏掉最后的const。const函数表示不能对其数据成员进行修改操作,并且const对
象不能调用非const成员函数,只允许调用const成员函数。 上面重载规定了排序方式为,优先按照x从小到
大排序,如果x相同,那么再按照y从小到大排序。经过了<运算符重载的结构体,我们就可以比较两个Node对象
的大小了,因此可以直接储存在set中了。
多重集合是数学中的一个概念,是集合概念的推广。在一个集合中,相同的元素只能出现一次,因此只能显示出
有或无的属性。而在多重集合之中,同一个元素可以出现多次。比如{1,1,1,2,2,3}不是一个集合,而是一个多
重集合。一个元素在多重集合里出现的次数称为这个元素在多重集合里面的重数(或重次、重复度)。 在 C++ 中
多重集合为multiset,同样是在头文件<set>中定义,相关函数都和set是一样的。
下面来看一个具体的例子:
/*
multiset<int> ms; //初始时集合为空 {}
ms.insert(5);     //插入元素 5 {5} 
ms.insert(4);     //插入元素 4 {4,5}  
ms.insert(5);     //插入元素 5 {4,5,5} 
ms.insert(7);     //插入元素 7 {4,5,5,7}  
ms.erase(3);      //删除元素 3 {4,5,5,7}  
ms.erase(4);      //删除元素 4 {5,5,7}   
ms.erase(5);      //删除元素 5 {7}  
*/
我们定义了一个存放int类型元素的多重集合ms,刚开始的时候为空。然后插入了4个元素,当前多重集合为{4,5,5,7}。
后面有三个删除操作,第一个删除的元素是 33,它在ms中并不存在,因此不会有任何变化(也不会出错)。
第二个要删除的数是 44,删完后ms中只剩下{5,5,7}。第三个要删除的数是5,它在ms中出现两次,erase函数会把这两
个5都从集合中删去,因此最后集合变为{7}。 
find(x)函数返回指向第一个值为 xx 的元素的迭代器,若 xx 不在容器中,则返回尾后迭代器。
multiset<int> ms;    //初始时集合为空 {}
ms.insert(5);        //插入元素 5 {5}
ms.insert(4);        //插入元素 4 {4,5}
ms.insert(5);        //插入元素 4 {4,5,5}
ms.insert(7);        //插入元素 4 {4,5,5,7}
multiset<int>::iteratou it;
it = ms.find(5);     //it 指向第一个 5 的位置 
cout<< *it <<endl;   //输出5 
it = ms.find(6);     //此时it为尾后迭代器 ms.end()
之前用的erase函数,它的参数是和容器内元素类型一样的,其实他还有别的重载版本
:参数可以是一个迭代器。利用find函数来得到相应位置的迭代器,这样我们就可以只删除一个元素了。
multiset<int> ms;       //初始时集合为空 
ms.insert(5);           //插入元素 5 {5} 
ms.insert(4);           //插入元素 4 {4,5}  
ms.insert(5);           //插入元素 5 {4,5,5} 
ms.insert(7);           //插入元素 7 {4,5,5,7}  
cout<<ms.count(5)<<endl;//容器中 5 的个数为 2 个 
ms.erase(ms.find(5));   //删除第一个元素5 {4,5,7}
cout<<ms.count(5)<<endl;//容器中 5 的个数为 1 个
这里我们使用count函数来统计容器内某个元素的个数,对set来说只可能是 0 或者 1,而multiset中元素
个数是可以超过 1 的。 
我们要记住:在multiset中,如果要删除一个元素而不是全部删除,要记得用ms.erase(ms.find(x))而不是直接ms.erase(x)。
警告:千万不能删除尾后迭代器,会出错的。
之前讲了find函数,还有两个和它类似的函数lower_bound和upper_bound,它们的时间复杂度都是
O(log n)。我们用这三个函数来获得所需的迭代器,并且在set和multiset等有序容器中都可以使用。
簂ower_bound(x)函数返回指向第一个 大于等于 xx 的元素的迭代器,若不存在则返回尾后迭代器。 upper_bound(x)
函数返回指向第一个 大于 x 的元素的的迭代器,若不存在则返回尾后迭代器。
例如:
multiset<int> ms;              //初始时集合为空 {}
ms.insert(5);                  //插入元素 5 {5}
ms.insert(4);                     //插入元素 4 {4,5}  
ms.insert(5);                    //插入元素 5 {4,5,5} 
ms.insert(7);                  //插入元素 7 {4,5,5,7}
cout<<*ms.lower_bound(4)<<endl;//容器中第一个大于等于 4 的元素是 4 
cout<<*ms.upper_bound(4)<<endl;//容器中第一个大于 4 的元素是 5
cout<<*ms.lower_bound(6)<<endl;//容器中第一个大于等于 6 的元素是 7
cout<<*ms.upper_bound(6)<<endl;//容器中第一个大于 6 的元素是 7
题目: 
珠宝店里有 n 颗珍珠,每颗珍珠的品质是 a[i]。现在依次来了 m 位顾客,每个顾客想要买品质不低于 x 的
珍珠。珠宝店老板会在满足顾客要求的情况下,卖出品质最差的珍珠。现在请你写一个程序帮忙计算卖给每个
顾客的珍珠的品质,如果没有符合要求的输出 -1。
分析:
这里我们用一个多重集合multiset来维护所有珍珠的品质,如果用普通集合set,相同品质的珍珠只能在集合中
存在一个,逻辑上肯定就不对了。每次找不低于 x 的最小品质,也就是调用lower_bound来查找。如果得到的
迭代器是尾后迭代器,就说明没有找到,输出 -1?1。否则,输出这个品质并从multiset里删除。 
答案: 
/*
#include <iostream>
#include <set>
using namespace std;
int main() {
    int n,m;
    cin >> n >> m;
    multiset<int> se;
    for(int i = 0; i < n; i++) {
        int x;
        cin >> x;
        se.insert(x);
    }
    while (m--) {
        int x;
        cin >> x;
        multiset<int>::iterator it = se.lower_bound(x);
        if (it == se.end()) {
            cout << -1 <<endl;
        } else {
            cout << *it << endl;
            se.erase(it);
        }
    }
    return 0;
}
*/ 
映射是指两个集合之间的元素的相互对应关系。通俗地说,就是一个元素对应另外一个元素。
比如有一个姓名的集合{"Tom", "Jone", "Mary"},班级集合{1,2}。姓名与班级之间可以有如下的映射关系:
class("Tom")=1,class("Jone")=2,class("Mary")=1
如图:
+-------------------+
|Tom      →       1|
+-------------------+
|Jone     →       2|
+-------------------+
|Mary     →       3| 
+-------------------+
我们称其中的姓名集合为 关键字集合 (key),班级集合为 值集合 (value)。
在 C++ 中我们常用的映射是map。
引用库:
在 C++ 中map的实现在一个<map>头文件中,在代码开头引入这个头文件,并且加上一句using namespace std; 。
#include <map>
using namespace std;
构造一个映射:
在 C++ 中,构造一个map的语句为:map<T1, T2> m;。这样我们定义了一个名为m的从T1类型到T2类型的映射。
初始的时候m是空映射。比如map<string,int> m 构建了一个字符串到整数的映射,这样我们可以把一个字符串和一个整数关联起来。
在 C++ 中通过insert()函数向集合中插入一个新的映射,参数是一个pair。
pair是一个标准库类型,定义在头文件utility中。可以看成是有两个成员变量first和second的结构体,并且
重载了<运算符(先比较first大小,如果一样再比较second)。当我们创建一个pair时,必须提供两个类型。
我们可以像这样定义一个保存string和int的pair:pair<string, int> p;
make_pair(v1, v2)函数返回由v1和v2初始化的pair,类型可以从v1和v2的类型推断出来。
我们向映射中加入新映射对的时候就是通过插入pair来实现的。如果插入的key之前已经存在了,将不会
用插入的新的value替代原来的value,也就是这次插入是无效的。 
/*
#include <map>
#include <string>
#include <utility>
using namespace std;
int main() {
    map < string, int > dick;          //dick是一个string到int的映照,存放每个名字对应的班级号,初始时为空
    dick.insert(make_pair("Tom",1));   //{"Tom"->1}
    dick.insert(make_pair("Jone",2));  //{"Tom"->1,"Jone"->2}
    dick.insert(make_pair("Mary",1));  //{"Tom"->1,"Jone"->2,"Mary"->1}
    dick.insert(make_pair("Tom",2));   //{"Tom"->1,"Jone"->2,"Mary"->1}
    return 0;

*/
并且我们可以之后再给映射赋予新的值,比如dict["Tom"] = 3,这样为我们提供了另一种方便的
插入手段。实际上,我们常常通过下标访问的方式来插入映射,而不是通过用insert插入一个pair来实现。
当然有些时候,我们不希望系统自动为我们生成映射,这时候我们需要检测"Tom"是否已经有映射了,如果
已经有映射再继续访问。这时就需要用count()函数进行判断。
/*
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
    map<string,int> dick;        //dick是一个string到int映照,存放每个名字对应的班级号,初始时为空
    dict["Tom"]=1;               //{"Tom"->1}
    dict["Jone"]=2;              //{"Tom"->1,"Jone"->2}
    dict["Mary"]=1;              //{"Tom"->1,"Jone"->2,"Mary"->1}
    cout << "Mary is in class " <<dick["Mary"] << endl;
    cout << "Tom is in class " <<dick["Tom"] << endl;
    return 0; 
}
*/
判断关键字是否存在
在 C++ 中,如果你想知道某个关键字是否被映射过,你可以直接用count()函数。如果关键字存在,返回 1
,否则会返回 0。
/*
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main(){
    map<string,int> dick;        //{}
    dict["Tom"]=1;               //{"Tom"->1}
    dict["Jone"]=2;              //{"Tom"->1,"Jone"->2}
    dict["Mary"]=1;              //{"Tom"->1,"Jone"->2,"Mary"->1}
    if(dick.count("Mary")) {
        cout << "Mary is in class " << endl;
    } else {
        cout << "Mary has no class" << endl;
    }
    return 0;
}
*/
遍历映射:
map的迭代器的定义和set差不多,map<T1, T2>::iterator it就定义了一个迭代器,其中T1、T2分别
是key和value的类型。 C++ 通过迭代器可以访问集合中的每个元素。这里迭代器指向的元素是一个
pair,有first和second两个成员变量,分别代表一个映射的key和value。
我们用->运算符来获取值,it->first和(*it).first的效果是一样的,就是获取迭代器it指向的pair里first成员的值。
/*
#include <iostream>
#include <map>
#includ <string>
using namespace std;
int main() {
    map<string, int> dict; // {}
    dict["Tom"] = 1; // {"Tom"->1}
    dict["Jone"] = 2; // {"Tom"->1, "Jone"->2}
    dict["Mary"] = 1; // {"Tom"->1, "Jone"->2, "Mary"->1}
    for (map<string, int>::iterator it = dict.begin(); it != dict.end(); it++) {
        cout << it->first << " -> " << it->second << endl; // first 是关键字, second是对应的值
    }
    return 0;

*/
注意,在 C++ 中遍历map是按照关键字从小到大遍历的,这一点和set有些共性。
清空:C++ 中只需要调用clear()函数就可清空map和其占用的内存。 
C++ 中map常用函数总结
+----------+-----------------------+------------+
|函数      |   功能                | 时间复杂度 |
+----------+-----------------------+------------+
|insert    |   插入一对映射        | O(log n)   |
+----------+-----------------------+------------+
|count     |   判断关键字是否存在  | O(logn)    |
+----------+-----------------------+------------+
|size      |   获取映射对个数      | O(1)       |
+----------+-----------------------+------------+
|clear     |   清空                | O(n)       |
+----------+-----------------------+------------+
二维的map不仅仅能map套map,还能map套set。实际上map套set更容易理解。
map套用set:全校有很多班级,每个班级每个人都会有中文名。现在我们需要用一种方式来记录全校
的同学的名字。如果直接用一个set记录,对于重名的同学,那么就没办法分辨了。
我们可以把全校的班级进行编号,对每一个班级建立一个set,也就是每个班级都
映射成一个set,这样就能分辨不同班级的同名同学了。对于同班的同学来说,一般很少有重名的。
map<int, set<string> > s就定义上面描述的数据结构,和二维vector一样,
两个> >中间的空格不能少了。 这样我们就可以进行插入和查询了。 比如对 2 班的yuhaoran同学,
我们s[2].insert("yuhaoran")。然后查询yuhaoran是不是一个 2 班的人,s[2].count("yuhaoran")。
然后还可以把他从 2 班删除,s[2].erase("yuhaoran")。 
map套用map:
上面的结构没有办法解决同班同名的情况。实际上,如果同班同名,单单通过名字本身是无法分辨 
的,需要通过其他特征来分辨。所以为了简单起见,我们只需要记录每个班级同名的人的个数。
这时候,我们把里面的set改成map即可。map<int, map<string, int> > s。2 班有一个yuhaoran,
s[2]["yuhaoran"]++。2 班又转来了一个yuhaoran,s[2]["yuhaoran"]++。
现在 2 班一共有多少个yuhaoran? 
cout << s[2]["yuhaoran"] << endl; 

点个免费的赞吧

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值