算法笔记个人学习总结(未完待补充)

1.memset用于对数组每一个元素赋同样的值,需要加头文件string.h,但只建议赋0或-1,因为memset使用按字节赋值,对每个字节赋同样的值,这样组成int型的四个字节会被赋成相同的值由于0的二进制补码为全0/-1二进制补码为全1,不容易出错。赋其他数字使用fill(algorithm库中);使用memset会出错。使用方法:
memset(数组名,值,sizeof(数组名));

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
using namespace std;
int main()
{
	int a[5] = { 0 };
	memset(a, 2, sizeof(a));
	for (int i = 0;i < 5;i++)
		cout << a[i] << " ";
	return 0;


}
/33686018 33686018 33686018 33686018 33686018

2.如果不是使用scanf函数的%s格式或gets函数输入字符串(例如使用getchar),请一定要在输入的每个字符串后加入‘\0’(ascii 码0,空格ascii码32),否则printf和puts输出字符串会因为无法识别字符串末尾而输出一大堆乱码。
3.scanf %s通过换行和空格来识别一个字符串的结束,gets识别换行符\n作为输入结束.
4.以数组作为函数参数,数组的第一维不需要填写长度(如果是二维数组,则第二维需要填写长度),实际调用时也只需要填写数组名,最重要的是,数组作为参数时,在函数中对数组元素的修改等同于对原数组元素的修改(不同于不同的局部变量(有形参,实参))。
5.数组名称也作为数组的首地址使用,即a==&a[0];
6.指针类型也可以作为函数参数的类型,这时,视为把变量的地址传入函数,如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地被改变。

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
using namespace std;
void change(int* p) {
	*p = 233;
}
int main()
{
	int a = 1;
	int* p = &a;
	change(&a);//存入p也可以,注意不是*p,函数写*p只是在定义函数参数的类型是指针,而不是要传入*p
	cout << a;
	return 0;
}
//233

7.结构体内能定义除了自己本身(这样会引起循环定义的问题)之外的任何数据类型,不过虽然不能定义自己本身,但可以定义自身类型的指针变量。

struct node{
node n; //(x)
node *next;
};

8.结构体中,如果自己定义了构造函数,则不能不经初始化就定义结构体变量,也就是说,默认生成的构造函数被覆盖了。为了既能不初始化就定义结构体变量,又能享受初始化带来的便捷,可以把默认的构造函数加上。即,只要参数个数和类型不完全相同,就定义任意多个构造函数,以适应不同初始化场合。

struct studentinfo{
int id;
char gender;
studentinfo(){}
//默认的,用以不初始化就定义结构体变量
studentinfo(char gen){
gender=gen;
}//只初始化gender
studentinfo(int id,char gen){
id=id;
gender=gen;
}//申明一个studentinfo类型需要初始化
//studentinfo stu=studentinfo{10096,'M}
};

9.O(cn^2),其中c是一个常数,我们把这个常数称为算法时间复杂度,当有些算法实现较为复杂时,其常数会比较大,这时即便算法时间复杂度相同(指不带系数的),性能也会有较大差距如排序算法中各个算法的差距。
10.二分查找是基于有序序列的查找算法;时间复杂度O(logn)

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int binarysearch(int a[], int left, int right, int x) {
	int mid;//left和right的中点;
	while (left <= right) {
		//必须保证符合左区间小于等于右区间
		mid = (left + right) / 2;
		if (a[mid] == x) return mid;//找到所查询的值对应的下标;
		else if (a[mid] > x) {
			right = mid - 1;//范围控制到[left,mid-1];
		}
		else {
			left = mid + 1;//范围控制到[mid+1,right];

		}
	}
	return -1;//查找失败返回-1;
	}
int main() {
	const int n = 10;
	int a[n] = { 1,3,4,6,7,8,10,11,12,15 };
	printf("%d %d\n", binarysearch(a, 0, n - 1, 6), binarysearch(a, 0, n - 1, 9));
    //the answer is 3 -1
	return 0;
}

序列递减,只需把a[mid]>x=>a[mid]<x

还需考虑溢出问题,即二分上界超过int数据类型范围的一半,则当预查询数据在序列叫靠后的位置是,语句mid=(left+right)/2,left+right可能超过int导致溢出,此时我们可使用mid=left+(right-left)/2代换避免溢出

程序设计时多采用非递归写法完成二分查找;(避免栈溢出)

如果递增序列中元素可能重复,对给定的元素x,如何求出第一个大于等于x的元素的位置L以及第一个大于x的元素的位置R,这样元素x在序列中的存在区间为左闭右开区间[L,R) :
eg 对于下标从0开始,有五个元素的序列{1,3,3,3,6} ,查3,则 L=1,R=4,查5则L=R=4,如果查6,L=4,R=5,查8,L=R=5,若序列中没有x,呢么LR可以理解为假设序列中存在x,则x应当在的位置

求出第一个大于等于x的元素的位置L
在这里插入图片描述

代码:

//A[]为递增序列,x为欲查询得数,函数返回第一个大于等于x的元素的位置
//二分上下界为左闭右闭的[left,right]传入的初值是[0,n]
//下标从0开始
int lowerbound(int A[],int left,int right,int x){
 int mid;//mid为left和right的中点
 while(left<right){//对[left,right]来说,left==right意味着找到唯一位置
 mid=(left+right)/2;//取中点
 if(A[mid]>=x){//中间数大于等于x
 right=mid;//往左子区间[left,mid]查找
 }
 else{//中间数小于x
 left=mid+1;//往右子区间找
 }
}
return left;//返回夹出来的位置
}

注意点:1.循环条件为left<right,而不是之前的left<=right,这是由问题本身所决定的。再上一个问题中,需要当元素不存在是返回-1,这样当left>right时[left,right]不再是闭区间,可以此作为元素不存在的判定条件,因此当left<=right满足时,循环应当一直执行,但若想返回第一个大于等于x的元素的位置,就不需要判断元素x是否存在,因为就算其不存在,返回的也是“假设它存在,他应该在的位置”,于是当left= =right时,[left,right]刚好能夹出唯一的位置,就是需要的结果,因此只需当left<right时让循环一直执行即可。(就算不满足left<right,跳出循环,为left>=right情况,也找到了大于x的第一个位置,即left。
2.由于当left= =right时,while循环停止,因此最后的返回结果可以是left也可以是right。
3.二分的初始区间应当能覆盖到所有的可能返回的结果。首先,二分下界是0是显然的,但二分上界是n,or n-1?因为所查询元素可能比序列中的所有元素都要打,此时应当返回n(假设它存在,他应该在的位置),因此二分上界是n,故二分初始区间为[left,right]=[0,n];

求第一个大于x的元素的位置R
在这里插入图片描述
代码:

//A[]为递增序列,x为欲查询得数,函数返回第一个大于x的元素的位置
//二分上下界为左闭右闭的[left,right]传入的初值是[0,n]
int upper_bound(int A[],int left,int right,int x){
int mid;//mid为left和right的中点
	while (left < right) {对[left,right]来说,left==right意味着找到唯一位置
		mid = (left + right) / 2;//取中点
		if (a[mid] > x) {//中间数大于x
			right = mid;
		}//往左子区间[left,mid]找
			else { left = mid + 1; }//中间的数小于等于x,往右子区间[mid+1,right]查找}
			}
		return left;//返回夹出来的位置
}

同lower_bound相比,只是将A[mid]>=x换位A[mid]>x,其余完全相同;
寻找有序序列中第一个满足某条件的元素的位置是一个经典问题;lowerbound解决第一个满足条件值大于等于x的元素的位置,而后者解决第一满足条件大于x的元素的位置;
固定模板

int solve(int a[], int left, int right, int x) {
	//解决寻找有序序列中第一个满足某条件的元素位置问题的固定模板
	//二分区间为左闭右闭的[left,right],初值必须是覆盖解的所有可能取值;
	int mid;//mid为left和right的中点
	while (left < right) {对[left,right]来说,left==right意味着找到唯一位置
		mid = (left + right) / 2;//取中点
		if (条件成立) {//条件成立,第一个满足条件的元素位置<=mid
			right = mid;
		}//往左子区间[left,mid]找
			else {//条件不成立,第一个满足条件的元素位置>mid 
			left = mid + 1; }//往右子区间[mid+1,right]查找}
			}
		return left;//返回夹出来的位置
	}

如果想要寻找最后一个满足条件c的元素的位置,则可以先求第一个满足条件!c的元素位置,然后将该位置减一即可

左开右闭的模板(和上面等价)

int solve(int a[], int left, int right, int x) {
	//left初值要比解的最小取值小1,(例如对下标从0开始的序列来说,left和right取值为-1和n)
	//解决寻找有序序列中第一个满足某条件的元素位置问题的固定模板
	//二分区间为左开右闭的(left,right],初值必须是覆盖解的所有可能取值;
	int mid;//mid为left和right的中点
	while (left+1 < right) {对[left,right]来说,left==right意味着找到唯一位置
		mid = (left + right) / 2;//取中点
		if (条件成立) {//条件成立,第一个满足条件的元素位置<=mid
			right = mid;
		}//往左子区间[left,mid]找
			else {//条件不成立,第一个满足条件的元素位置>mid 
			left = mid; }//往右子区间[mid,right]查找}
			}
		return left;//返回夹出来的位置
	}

二分法拓展,利用其求√2的近似值;
f(x)=x2,在[1,2]范围内,f随着x增大而增大。我们可以利用二分法来逼近√2的值(因为√2是无理数,因此只能获得,他的近似值,这里不妨以精确到10-5为例)
令left和right初值分别为1和2,然后根据left和right的中点mid处f(x)值与2的大小进行逼近;如果f(mid)>2,即mid>√2,则应当在[left,mid]范围内继续逼近,故令right=mid。
在这里插入图片描述
如果f(mid)<2,即mid<√2,则应当在[mid,right]范围内继续逼近,故令left=mid。
在这里插入图片描述
当满足left-right<10-5时结束。因为left和right距离要求小于10-5满足精度要求,mid即为所求近似值。

const double eps = 1e-5;//精度
double f(double x) {
	return x * x;//计算x^2
}
double calsqrt() {
	double left = 1, right = 2, mid;
	while (right - left > eps) {
		mid = (left + right) / 2;
		if (f(mid) > 2) {
			//mid>sqrt(2),往左子区间[left,mid]趋近
			right = mid;
		}
		else {//mid<sprt(2),往右子区间[mid,right]趋近
			left = mid;
		}
	}
	return mid;//mid即为sqrt(2)的近似值
}

而计算√2的近似值是一个问题的特例即:给定一个在[L,R]上的单调函f(x),求方f(x)=0的根
我们同样可以预定精度=10-5,f(x)在[L,R]递增,则令left,right初值分别为L,R,接着根据中点mid的函数值f(mid)与0的大小关系来锁定根所在的区间;
如果f(mid)>0,即mid>0,则应当在[left,mid]范围内继续逼近,故令right=mid。
在这里插入图片描述
如果f(mid)<0,即mid<0,则应当在[mid,right]范围内继续逼近,故令left=mid。
在这里插入图片描述
同理当left-right<10-5达到精度要求,结束算法,返回当前mid值也为函数=0的根;

const double eps = 1e-5;//精度
double f(double x) {
	return  ...;//f(x)
}
double calsqrt() {
	double left = L, right = R, mid;
	while (right - left > eps) {
		mid = (left + right) / 2;
		if (f(mid) >0) {
			//往左子区间[left,mid]趋近
			right = mid;
		}
		else {//往右子区间[mid,right]趋近
			left = mid;
		}
	}
	return mid;//mid即为近似值
}

计算根号2的近似值也可以看作求f(x)=x2-2=0在[1,2]内的根。且若f(x)递减,则只需把f(mid) >0改为f(mid) <0即可

11.快速幂递归写法
求ab;时间复杂度O(logb);
在这里插入图片描述

typedef long long ll;
//求a^b^%m,递归写法;
ll binarypow(ll a,ll b,ll m){
	if(b==0) return 1;//b=0,a^0=1;
	//b为奇数,转换为b-1
	if(b%2==1)return a*binarypow(a,b-1,m)%m;
	else{//b为偶数,转换为b/2
	ll mul=binarypow(a,b/2,m);
	return mul*mul%m;
	}
}

而其中if(b%2==1)=>if(b&1)来代替,因为b&1进行位与操作判断b的末位是否为1,因此当b为奇数时返回1,if条件成立。加快效率;
另外还要注意,当b%2==0时,不要直接返回binarypow(a,b/2,m)*binarypow(a,b/2,m),而是先酸楚单个之后,再乘起来。因为前者会每次都调用两个binarypow函数导致复杂度为O(2log(b))=O(b).
若初始时a有可能大于等于m,那么需要在进入函数前就让a对m取模。
若m=1,则可以直接在函数外部特判为0,不需要进入函数来计算(因为任何正整数对1去模必等于0)。
快速幂迭代写法:

typedef long long ll;
//求a^b^%m,迭代写法;

ll binarypow(ll a,ll b,ll m){
	ll ans=1;
	while(b>0){
	if(b&1){
	//如果b的二进制末尾为1(也可以写成if(b%2))
	ans=ans*a%m;
	}
	a=a*a%m;
	b>>=1;//将b的二进制右移一位,即b=b>>1或b=b/2
	}
	return ans;
}

递归和迭代的效率差别并不明显,使用时,选择任意都可;
12.two pointers,eg:给定一个递增的正整数序列和一个正整数m,求序列中的两个不同位置的数a和b,使得他们的和恰好为m,输出满足所有条件的方案。比如{1,2,3,4,5,7},m=8,则存在2+6=8,3+5=8成立,正常思路为二重循环枚举序列中的a,b判断他们的和是否为m,如果是输出,不是继续枚举。时间复杂度O(n2),当n为105时u,其代价已难以承受,判断会超时;
分析:(1)对于一个确定的a[i],若a[j]满足a[i]+a[j]>m,则a[i]+a[j+1]>m也成立,因为序列递增,则a[j]后面的的数不需要枚举。要是无视此情况,会出现大量无效操作;
(2)对于某个a[i]来说,若找到a[j],使得a[i]+a[j]>m恰好成立,那么对于a[i+1]来说a[i+1]+a[j]>m也成立,所以a[i]后的元素也不必枚举
ij的枚举相互牵制,而two pointers利用有序序列的枚举特性来降低复杂度;
其具体思路如下:
在这里插入图片描述

while (i < j) {
		if (a[i] + a[j] == m) {
			printf("%d %d", i, j);
			i++;
			j--;
		}
		else if (a[i] + a[j] < m) {
			i++;
		}
		else {
			j--;
		}
	}

时间复杂度O(n);
序列合并问题:假设有两个递增序列A与B,要将他们合并成一个递增序列C。
思路:
在这里插入图片描述

int merge(int a[], int b[], int c[], int n, int m) {
	int i = 0, j = 0, index = 0;//i指向a[0],j指向b[0]
	while (i < n && j < m) {
		if (a[i] <= b[j]) {
			c[index++] = a[i++];
			//a[i]加入序列c
		}
		else {
			c[index++] = b[j++];//b[j]加入序列c
		}
	}
	while (i < n) c[index++] = a[i++];//将序列a中的剩余元素加入c
	while (j < m) c[index++] = b[j++];//将序列b中的剩余元素加入c
	return index;//返回序列c的长度;
}

two pointers原始定义就是针对第一个问题而言,广义的two pointers 则是利用问题本身与序列的特性,使用两个下标i,j对序列进行扫描,可同向也可反向,以较低的复杂度解决问题。
13.二路归并:将序列两两分组,将序列归并为n/2个组(上取整),组内单独排序,然后再将这些组再两两归并,生成n/4个组(上取整),组内再单独排序,以此类推,直到剩下一个组为止。时间复杂度O(logn);
递归写法:
在这里插入图片描述

const int maxn = 100;
//将数组a的[l1,r1]与[l2,r2]区间合并为有序区间此处l2=r1+1
void merge(int a[], int l1, int r1, int l2, int r2) {
	int i = l1,j = l2;//i指向a[l1],j指向a[l2]
	int temp[maxn], index = 0;//temp临时存放合并后的数组,index为其下标;
	while (i <= r1 && j <= r2) {
		if (a[i] <= a[j]) {
			temp[index++] = a[i++];
		}
		else {
			temp[index++] = a[j++];
		}
	}
	while (i <= r1) temp[index++] = a[i++];
	while (j <= r2) temp[index++] = a[j++];
	for (i = 0;i < index;i++) {
		a[l1 + 1] = temp[i];//将合并后的序列赋值回数组a;
	}
}
//将array数组当前区间[left,right]进行归并排序;
void mergesort(int a[], int left, int right) {
	if (left < right) {
		//只要left<right
		int mid = (left + right) / 2;//取中点
		mergesort(a, left, mid);
		mergesort(a, mid+1, right);
		merge(a,left,mid,mid+1,right)//合并左右子区间
	}
}

非递归:
在这里插入图片描述
在这里插入图片描述
14.快速排序,排序算法中,平均时间时间复杂度O(nlogn)的一种算法。
在这里插入图片描述

int partition(int a[], int left, int right) {
	int temp = a[left];//将a[left]存至零时变量temp
	while (left < right) {
		//只要left,right不相遇;
		while (left<right && a[right]>temp) right--;
		a[left] = a[right];//将a[right]赋给a[left];
		while (left < right && a[left] <= temp) left++;
		a[right]=a[left];//将a[left]赋给a[right];
	}
	a[left] = temp;//把temp赋给left=right的位置;
	return left;
}

快排思路:(1)调整序列中的元素,使当前序列最左端的元素在调整后满足左侧所有元素均不超过该元素,右侧所有元素均大于该元素;(2)对该元素的左右侧分别递归进行1的调整,直到当前调整区间长度不超过1.
快排的递归实现:

void quicksort(int A[], int left, int right) {
	//left and right 为序列的首尾下标;
	if (left < right) {
		//当前区间长度不超过1
		//将[left,right]按a[left]一份为二
		int pos = partition(A, left, right);
		quicksort(A, left, pos - 1);//对左子区间递归进行快速排序;
		quicksort(A, pos+1, right);//对右子区间递归进行快速排序;
	}
}

快排算法在序列中元素排列比较随机时效率最高,但当序列中元素接近有序时,会达到最坏时间复杂度O(n2),因为主元没有将当前区间划分为两个长度接近的子区间;解决方法:随机选择主元,即对于a[left…right]来说,不总是用a[left]作为主元,而是从a[left],a[left+1]…a[right]随机选择一个作为主元,虽然最坏时间复杂度仍为O(n2)每次都选择了a[left],但对任意输入数据的期望时间复杂度都能达到O(logn),不存在一组特定的数据能使该算法出现最坏情况;
随机数生成 代码:

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;

int main() {
	srand((unsigned)time(NULL));//生成随机数的种子,srand初始化随机种子
	for (int i = 0;i < 10;i++) {
		printf("%d ", rand());
	}
	return 0;
}
//rand()只能生成[0,RAND_MAX]范围内的整数,RAND_MAX是stdlib.h的一个常数,不同系统环境下,常数值不同
//想输出给定范围里的[a,b]内的随机数,需使用rand()%(b-a+1)+a,rand()%(b-a+1)范围是[0,b-a],在+a之后就是[a,b]
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;

int main() {
	srand((unsigned)time(NULL));
	for (int i = 0;i < 10;i++) {
		printf("%d ", rand()%2);//[0,1]
	}
	printf("\n");
	for (int i = 0;i < 10;i++) {
		printf("%d ", rand() % 5+3);//[3,7]
	}
	return 0;
}

上述方法,只对左右端点不超过RAND_MAX的区间的随机数有效,如果需要生成更大的数,如[a,b],b>32767就不行了若想生成大范围随机数,eg:多次生成rand随机数,然后按位运算拼接起来,或者直接把两个rand随机数相乘;也可以随机选每一个数位的值(0~9)然后拼接成一个大整数;先用rand()生成一个[0,RAND_MAX]范围的随机数,然后用这个数除以RAND_MAX,这样可得到一个[0,1]范围内的浮点数,我们只需要利用这个浮点数乘以范围长度(b-a+1),再加上a即可,即(int)((double)rand()/32767*(b-a+1)+a),即这个浮点数即是[a,b]范围内的比例位置.

//生成[10000,60000]的随机数
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;

int main() {
	srand((unsigned)time(NULL));
	for (int i = 0;i < 10;i++) {//[10000,60000]
		printf("%d ", (int)(round(1.0 * rand() / RAND_MAX * 50000 + 10000)));
	}
	
	return 0;
}

以此为基础讨论随机快排,我们需要在a[left,right]中随机选取一个主元,因此不妨在[left,right]范围内生成一个随机数p。利用a[p]作为主元来进行划分,具体做法:a[p]与a[left]交换,然后按原partition的写法即可(只增加了两句话)。quicksort不需改变

int randpartition(int a[], int left, int right) {
	//生成[left,right]的随机数p
	int p = (round(1.0 * rand() / RAND_MAX * (right - left)) + left);
	swap(a[p], a[left]);//交换a[p],a[left];
	int temp = a[left];
	while (left < right) {
		//只要left,right不相遇;
		while (left<right && a[right]>temp) right--;
		a[left] = a[right];//将a[right]赋给a[left];
		while (left < right && a[left] <= temp) left++;
		a[right] = a[left];//将a[left]赋给a[right];
	}
	a[left] = temp;//把temp赋给left=right的位置;
	return left;
}

15.随机选择算法:如何从一个无序的数组中求出第K个大的数(数组中的数各不相同)
eg:{5,12,7,2,9,3}第三大的数是5,第五大的数是9.
可以先对数组排序,再取出第k个元素即可,但这样的做法需要O(logn)的时间复杂度,利用随机选择算法可达到O(n)的时间复杂度。其思想类似快排,当对A[left,right]执行一次randparition函数之后,主元左侧的元素个数就是确定的,且他们都小于主元。假设此时主元A[p],那么A[p]就是第p-left+1大的数。不妨令m表示p-left+1,那么如果km成立,说明第k大的数就是主元A[p],如果k<m成立,就说明第K大的数在主元左侧,即A[left…(p-1)]中的第k大,往左侧递归即可,如果k>m成立,则说明第k大的数在主元右侧,即A[(p+1)…right]中的第k-m大,往右侧递归。用leftright作为递归边界,返回A[left].
(找数组中第k大的,也是找数组前半段第k大的,如果k被包含在其中;找数组中第k大的,是找后半段数组中第k-m大的,因为其起始角度已不一样)

int randselect(int a[], int left, int right, int k) {
	if (left == right) return a[left];//边界
	int p = randpartition(a, left, right);
	//划分后主元位置为p;
	int m = p - left + 1;//a[p]是a[left,right]中的第M大
	if (k == m) return a[p];//找到第k大的数
	if (k < m)//第k大的数在主元左侧
		return randselect(a, left, p - 1, k);//往主元左侧找第k大
	else {
		return randselect(a, p + 1, right, k - m);//往主元右侧找第k-m大;
	}
}

16.给定一个由整数组成的集合,集合中的整数各不相同,现在要分成两个子集合,使得两个集合并为原集合,交为空集合,同时在两个子集合元素个数n1和n2的差绝对值|n1-n2|尽可能小的前提下,要求他们各自的元素之和是s1和s2之间的差距|S1-S2|尽可能大,求这个|S1-S2|等于多少。
当原集合元素个数为n,n为偶数,则两个子集合中元素个数为n/2,n为奇数则分别为n/2,n/2+1(除法为向下取整)为使|s1-s2|尽可能大,最直接思路是原集合从小到大排序,取排序前n/2个元素作为其中一个子集合,剩下的元素作为另一个子集合即可,时间复杂度O(nlogn)。更好的方法是使用上面介绍的随机选择算法,求原集合中的元素第n/2个大,同时根据这个数把集合分为两部分,一个集合中的元素都不小于这个数,而另一个子集合中的元素都大于这个数,并不关心其内部顺序,利用randselect找出第n/2个大的数即可,该函数会自动切分好两个集合,期望时间复杂度O(n);

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;
const int maxn = 100010;
int a[maxn], n;//a存放所有整数,n为其个数
int randpartition(int a[], int left, int right) {
	//选取随机主元,对去区间[left,right]进行划分
	//生成[left,right]内的随机数p
	int(round(1.0 * rand() / RAND_MAX * (right - left) + left));
	//swap(a[p], a[left]);
	int temp = a[left];//我们直接利用普通快排做也可以
	while (left < right) {
		if (left<right && a[right]>temp) right--;
		a[left] = a[right];
		if (left<right && a[left]<temp) left++;
		a[right] = a[left];
	}
	a[left] = temp;
	return left;
}
void randselect(int a[], int left, int right, int k) {
	if (left == right) return ;//边界
	int p = randpartition(a, left, right);
	//划分后主元位置为p;
	int m = p - left + 1;//a[p]是a[left,right]中的第M大
	if (k == m) return ;//找到第k大的数
	if (k < m)//第k大的数在主元左侧
		randselect(a, left, p - 1, k);//往主元左侧找第k大
	else {
		randselect(a, p + 1, right, k - m);//往主元右侧找第k-m大;
	}
}
int main() {
	srand((unsigned)time(NULL));//初始化随机种子
	//sum sum1 记录所有整数之和与切分后前n/2元素之和
	int sum = 0, sum1 = 0;
	scanf("%d", &n);
	for (int i = 0;i < n;i++) {
		scanf("%d", &a[i]);
		sum += a[i];
	}
	randselect(a, 0, n - 1, n / 2);//寻找第n/2大的数并进行切分
	for (int i = 0;i < n / 2;i++)
		sum1 += a[i];
	printf("%d\n", (sum - sum1) - sum1);
	return 0;
}


17.分数处理:
分数的表示:对分数来说,最简洁的方式是写成假分数的形式,即无论分子比分母大或者小,都保留其原数,利用结构体来存储

struct fraction{
	int up,down;//分子分母
}

制定规则:a.使down为非负数,若分数为负,那么令分子up为负即可;b.若该分数恰为0,规定其分子为0,分母为1;c.分子分母没有除1以外的公约数;

分数的化简:
分数的化简目的在于让fraction变量满足三项规定,因此化简也分为三步:如果分母为负,z则分子分母各取相反数,如果分子为0,则令分母为1,约分,求出分子绝对值与分母绝对值的最大公约数,分子分母同除他;

fraction reduction(fraction result) {
	if (result.down < 0) {
		reslut.up = -result.up;
		reslut.down = -result.down;
	}
	if (result.up == 0) {
		result.down = 1;
	}
	else {
		int d = gcd(abs(result.up), abs(result.down));
		result.up /= d;
		result.down /= d;
	}
	return result;
}

分数的四则运算:
分数的加法:
在这里插入图片描述

fraction add(fraction f1, fraction f2) {
	fraction result;
	result.up = f1.up * f2.down + f2.up * f1.down;
	result.down = f1.down * f2.down;
	return reduction(result);//返回化简后的结果;
}

分数的减法:
在这里插入图片描述

fraction minu(fraction f1, fraction f2) {
	fraction result;
	result.up = f1.up * f2.down - f2.up * f1.down;
	result.down = f1.down * f2.down;
	return reduction(result);//返回化简后的结果;
}

分数的乘法:
在这里插入图片描述

fraction multi(fraction f1, fraction f2) {
	fraction result;
	result.up = f1.up *f2.up;
	result.down = f1.down * f2.down;
	return reduction(result);//返回化简后的结果;
}

分数的除法:
在这里插入图片描述

fraction divide(fraction f1, fraction f2) {
	fraction result;
	result.up = f1.up * f2.down;
	result.down = f1.down * f2.up;
	return reduction(result);//返回化简后的结果;
}

除法需要注意,如果读入的除数为0,(之前设定成分子为0,所以判断up为0即可),那么应当直接特判输出题目要求的输出语句(如输出error,inf之类)。只有当除数不为0,才能用上面的函数计算。
分数的输出:
注意点:
输出分数先要进行化简;如果某分数的分母为1,说明该分数是整数,一般来说题目会要求直接输出分子,忽略分母输出;如果分数的分子的绝对值大于分母,说明该分数是假分数,应该按带分数形式输出,即整数部分是up/down,分子部分为abs(up)%down,分母部分为r.down;以上均不满足说明分数是真分数,按原样输出即可。

void showresult(fraction r) {
	r = reduction(r);
	if (r.down == 1) printf("%d", r.up);
	else if (abs(r.up) > r.down) {
		printf("%d %d/%d", r.up / r.down, abs(r.up) % r.down, r.down);
	}
	else {
		printf("%d/%d", r.up, r.down);
	}
}

由于分数的乘法除法可能会使分子分母超过int型表示范围,因此一般情况下,分子分母应用long long存储。
18.素数,除1和本身不能被其他数整除的一类数,即对给定的正整数n,如果对任意的正整数a(1<a<n)都有n%a!=0,则称n是素数,否则n为合数,1既不是素数也不是合数。利用O(sqrt(n))的复杂度判断素数:

bool isPrime(int n) {
	if (n <= 1) return false;
	int sqr = (int)sqrt(1.0 * n);//sqrt 需要double类型的参数,返回double类型的值利用两次强制类型转换;
	for (int i = 2;i <= sqr;i++)
	{
		if (n % i == 0) return false;//n是i的倍数,则n不是素数
	}
	return true;
}

如果n没有接近int变量范围上界,则可以写为:

bool isPrime(int n) {
	if (n <= 1) return false;
	
	for (int i=2;i*i<=n;i++)
	{
		if (n % i == 0) return false;
	}
	return true;
}

n在109以内都会是安全的,当n接近int型变量的范围上界时会导致i*i溢出,解决方法是将i定义成long long型,就不会溢出。
素数表:时间复杂度O(nsqrt(n)),对n不超过105的大小没有问题。

const int maxn = 101;//表长;
int prime[maxn], pnum = 0;//prime数组存放所有素数,pnum为素数个数;
bool p[maxn] = { 0 };//p[i]==true,表示i是素数
void findprime() {
	for (int i = 1;i < maxn;i++) {
		if (isPrime(i) == true) {
			prime[pnum++] = i;//是素数把i存入prime数组
			p[i] = true;
		}
	}
}

筛选法,时间复杂度O(nloglogn)
在这里插入图片描述

const int maxn = 101;//表长;
int prime[maxn], pnum = 0;//prime数组存放所有素数,pnum为素数个数;
bool p[maxn] = { 0 };//p[i]==true,表示i是素数
void findprime() {
	for (int i = 2;i < maxn;i++) {
		if (p[i] == false) {
			//i是素数
			prime[pnum++] = i;
			for (int j = i + i;j < maxn;j += i) {
				//筛去所有i的倍数
				p[j] = true;
			}
		}
	}
}

求解100以内所有素数的程序:

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;
bool isPrime(int n) {
	if (n <= 1) return false;
	
	for (int i=2;i*i<=n;i++)
	{
		if (n % i == 0) return false;
	}
	return true;
}
const int maxn = 101;//表长;
int prime[maxn], pnum = 0;//prime数组存放所有素数,pnum为素数个数;
bool p[maxn] = { 0 };//p[i]==true,表示i是素数
void findprime() {
	for (int i = 2;i < maxn;i++) {
		if (p[i] == false) {
			//i是素数
			prime[pnum++] = i;
			for (int j = i + i;j < maxn;j += i) {
				//筛去所有i的倍数
				p[j] = true;
			}
		}
	}
}
int main() {
	findprime();
	for (int i = 0;i < pnum;i++) {
		printf("%d ", prime[i]);
	}
	return 0;
}

19.质因子分解,指将一个正整数n写成一个或多个质数的乘积形式。注意:1不是素数,因此他没有质因子。如果有些题目中要求对1进行处理,那么视题目条件而定来进行特判处理。

struct factor{
int x,cnt;//x为质因子,cnt为其个数
}fac[10];//fac[]数组存放的就是给定的正整数n的所有质因子

eg:对于180;

在这里插入图片描述
考虑到23571113171923*29就已经超过了int范围,因此对一个int型范围的数,fac数组大小只需开到10就可以了(因为fac数组存的是正整数n的质因子和其个数,当达到10个质因子时,即使个数都为1,也超过了int承受范围)
在这里插入图片描述

if (n % prime[i] == 0) {//如果prime[i]是n的因子
		fac[num].x = prime[i];
		fac[num].cnt = 0;
		while (n % prime[i] == 0) {
			fac[num].cnt++;
			n /= prime[i];
		}
		num++;
	}

如果p不是n的因子,就直接跳过。
如果在上面步骤结束后n仍然大于1,说明n有且仅有一个大于sqrt(n)的质因子,(有可能是n本身,因为此时没有找的小于sqrt(n)的因子,或者已经除过,n变化过),这时需要把这个质因子加入fac数组,并令其个数为1。

if (n != 1) {//如果无法被根号n以内的质因子除尽
		fac[num].x = n;//那么一定有一个大于根号n的质因子。
		fac[num++].cnt = 1;
	}

在这里插入图片描述
20.大整数
存储利用数组存储,整数的高位存储在数组的高位,整数的低位存储在数组的低位(注意读入整数时按%s,是逆位存储的,需要另存至一个数组翻转一下)

struct bign {
	int len;//记录长度
	int d[1000];
	bign() {//构造函数
		memset(d, 0, sizeof(d));
		len = 0;
	}
};
//赋值
bign change(char str[]) {
	bign a;
	a.len = strlen(str);
	for (int i = 0;i < a.len;i++) {
		a.d[i] = str[a.len - i - 1] - '0';//从字符型转化为整形存入数组;

	}
	return a;
	}

两个大整数的比较:先比较len大小,不相等,则以长的为大,若相等则从高位到低位进行比较,直到出现不等为止,即可判断大小;

int compare(bign a, bign b) {
	if (a.len > b.len) return 1;//a大
	else if (a.len < b.len)return -1;//a小
	else {
		for (int i = a.len - 1;i >= 0;i--) {
			//从高位到低位比较;
			if (a.d[i] > b.d[i])return 1;
			else if (a.d[i] < b.d[i]) return -1;
		}
		return 0;
		//所有位相等,两数相等;
	}
}

高精度加法:

bign add(bign a, bign b) {
	bign c;
	int carry = 0;//进位
	for (int i = 0;i < a.len || i < b.len;i++) {
		//以较长的为界;
		int temp = a.d[i] + b.d[i] + carry;//超出也没有关系,之前设置的数组大小1000,高位都是0;
		c.d[c.len++] = temp % 10;//个位数作为该位的值;
		carry = temp / 10;//十位数作为进位;
	}
	if (carry != 0) {
		//如果进位不为0,则直接付给结果的最高位;
		c.d[c.len++] = carry;
	}
	return c;
}

完整a+b:

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;
struct bign {
	int len;//记录长度
	int d[1000];
	bign() {//构造函数
		memset(d, 0, sizeof(d));
		len = 0;
	}
	
};
bign change(char str[]) {//将整数转换为bign
	bign a;
	a.len = strlen(str);
	//可以先判断下第一位是否为负号,若是则读取时少读取一个字符
	for (int i = 0;i < a.len;i++) {
		a.d[i] = str[a.len - i - 1] - '0';//从字符型转化为整形存入数组;

	}
	return a;
	}
int compare(bign a, bign b) {
	if (a.len > b.len) return 1;//a大
	else if (a.len < b.len)return -1;//a小
	else {
		for (int i = a.len - 1;i >= 0;i--) {
			//从高位到低位比较;
			if (a.d[i] > b.d[i])return 1;
			else if (a.d[i] < b.d[i]) return -1;
		}
		return 0;
		//所有位相等,两数相等;
	}
}
bign add(bign a, bign b) {
	bign c;
	int carry = 0;//进位
	for (int i = 0;i < a.len || i < b.len;i++) {
		//以较长的为界;
		int temp = a.d[i] + b.d[i] + carry;//超出也没有关系,之前设置的数组大小1000,高位都是0;
		c.d[c.len++] = temp % 10;//个位数作为该位的值;
		carry = temp / 10;//十位数作为进位;
	}
	if (carry != 0) {
		//如果进位不为0,则直接付给结果的最高位;
		c.d[c.len++] = carry;
	}
	return c;
}
void print(bign a) {
	for (int i = a.len - 1;i >= 0;i--) {
		printf("%d", a.d[i]);
	}
}
int main() {
	char str1[1000], str2[1000];
	scanf("%s%s", str1, str2);
	bign a = change(str1);
	bign b = change(str2);
	print(add(a, b));
	return 0;
}

这样的写法是两个对象都是非负整数,若有一方为负可以在转换到数组这一步时去掉其负号,然后采用高精度减法,若两个都是负的,就都去掉负号用高精度加法,最后再把负号加回去即可。
高精度减法:

bign sub(bign a, bign b) {
	bign c;
	for (int i = 0;i < a.len || i < b.len;i++) {
		//以较长的为界限
		//用的时候注意可以先比较大小,利用大的减小的,以防结果有误(有需要补一个负号在输出中,再利用sub函数);
		if (a.d[i] < b.d[i]) {
			a.d[i + 1]--;
			a.d[i] += 10;
		}
		c.d[c.len++] = a.d[i] - b.d[i];
	}
	while (c.len - 1 >= 1 && c.d[c.len - 1] == 0) {
		c.len--;//去除高位的0,同时至少保留一位最低位;
	}
	return c;
}

高精度与低精度乘法:
在这里插入图片描述

bign multi(bign a, int b) {
	bign c;
	int carry = 0;
	for (int i = 0;i < a.len;i++) {
		int temp = a.d[i] * b + carry;
		c.d[c.len++] = temp % 10;//个位作为该位的结果
		carry = temp / 10;//高位部分作为新的进位
	}
	while (carry != 0) {//不同于加法,乘法进位不止一位,因此用while
		c.d[c.len++] = carry % 10;
		carry /= 10;
	}
	return c;
}

如果a和b中存在负数,需要先记录下其负号,然后取绝对值带入函数;
高精度与低精度除法:
在这里插入图片描述

bign divide(bign a, int b, int& r) {
	bign c;
	c.len = a.len;
	//被除数的每一位和商的每一位都是一一对应的,因此先令长度相等;
	for (int i = a.len - 1;i >= 0;i--) {
		r = r * 10 + a.d[i];//和上一位遗留的余数组合
		if (r < b) c.d[i] = 0;//不够除该位为0
		else {
			c.d[i] = r / b;//商
			r = r % b;//新余数
		}
	}
	while (c.len - 1 >= 1 && c.d[c.len - 1] == 0) {
		c.len--;//去除高位的0,同时至少保留一位最低位;
	}
	return c;
}

在这里插入图片描述

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
using namespace std;
struct bign {
	int len;//记录长度
	int d[1000];
	bign() {//构造函数
		memset(d, 0, sizeof(d));
		len = 0;
	}
	
};
bign change(char str[]) {//将整数转换为bign
	bign a;
	a.len = strlen(str);
	for (int i = 0;i < a.len;i++) {
		a.d[i] = str[a.len - i - 1] - '0';//从字符型转化为整形存入数组;

	}
	return a;
	}
int compare(bign a, bign b) {
	if (a.len > b.len) return 1;//a大
	else if (a.len < b.len)return -1;//a小
	else {
		for (int i = a.len - 1;i >= 0;i--) {
			//从高位到低位比较;
			if (a.d[i] > b.d[i])return 1;
			else if (a.d[i] < b.d[i]) return -1;
		}
		return 0;
		//所有位相等,两数相等;
	}
}
bign add(bign a, bign b) {
	bign c;
	int carry = 0;//进位
	for (int i = 0;i < a.len || i < b.len;i++) {
		//以较长的为界;
		int temp = a.d[i] + b.d[i] + carry;//超出也没有关系,之前设置的数组大小1000,高位都是0;
		c.d[c.len++] = temp % 10;//个位数作为该位的值;
		carry = temp / 10;//十位数作为进位;
	}
	if (carry != 0) {
		//如果进位不为0,则直接付给结果的最高位;
		c.d[c.len++] = carry;
	}
	return c;
}
bign sub(bign a, bign b) {
	bign c;
	for (int i = 0;i < a.len || i < b.len;i++) {
		//以较长的为界限
		//用的时候注意可以先比较大小,利用大的减小的,以防结果有误(有需要补一个负号在输出中,再利用sub函数);
		if (a.d[i] < b.d[i]) {
			a.d[i + 1]--;
			a.d[i] += 10;
		}
		c.d[c.len++] = a.d[i] - b.d[i];
	}
	while (c.len - 1 >= 1 && c.d[c.len - 1] == 0) {
		c.len--;//去除高位的0,同时至少保留一位最低位;
	}
	return c;
}
void print(bign a) {
	for (int i = a.len - 1;i >= 0;i--) {
		printf("%d", a.d[i]);
	}
}
bign multi(bign a, int b) {
	bign c;
	int carry = 0;
	for (int i = 0;i < a.len;i++) {
		int temp = a.d[i] * b + carry;
		c.d[c.len++] = temp % 10;//个位作为该位的结果
		carry = temp / 10;//高位部分作为新的进位
	}
	while (carry != 0) {//不同于加法,乘法进位不止一位,因此用while
		c.d[c.len++] = carry % 10;
		carry /= 10;
	}
	return c;
}
bign divide(bign a, int b, int& r) {
	bign c;
	c.len = a.len;
	//被除数的每一位和商的每一位都是一一对应的,因此先令长度相等;
	for (int i = a.len - 1;i >= 0;i--) {
		r = r * 10 + a.d[i];//和上一位遗留的余数组合
		if (r < b) c.d[i] = 0;//不够除该位为0
		else {
			c.d[i] = r / b;//商
			r = r % b;//新余数
		}
	}
	while (c.len - 1 >= 1 && c.d[c.len - 1] == 0) {
		c.len--;//去除高位的0,同时至少保留一位最低位;
	}
	return c;
}
int main() {
	char str1[1000], str2[1000];
	scanf("%s%s", str1, str2);
	bign a = change(str1);
	bign b = change(str2);
	print(add(a, b));
	return 0;
}

21.组合数:
求n!中有多少质因子p,直观思路:计算从1~n中的每个数各有多少质因子p,然后将结果累加,时间复杂度为O(nlogn)

在这里插入图片描述
此法在当n为1018是无法承受的。
在这里插入图片描述

int cal(int n, int p) {
	int ans = 0;
	while (n) {
		ans += n / p;//累加n/p^k
		n /= p; //相当于分母多乘一个p
	}
	return ans;
}

在这里插入图片描述
explain:
n整除5(向下去整)就是答案,所谓末尾有0,就是10的倍数,10的因数有1,2,5,10,10的5的倍数,n整除5的过程把10的倍数也算上了,2的倍数比5的倍数多得多,肯定够用
在这里插入图片描述
在这里插入图片描述

int cal(int n, int p) {
	if (n < p) return 0;//n<p时,1-n中不可能有质因子p
	return  n / p + cal(n / p, p);//返回n/p加上(n/p)!中质因子的个数
}

组合数的计算:
在这里插入图片描述
在这里插入图片描述
这种方法虽然组合数的值都不大,但却因为计算容易溢出的原因而无法得到正确值的做法显然不太合适,比如C(21,10)=352716,由上面的做法却因为21!超过long long型的数据范围而没办法算出来。
在这里插入图片描述

long long C(long long n, long long m) {
	if (m == 0 || m == n) return 1;
	if (res[n][m] != 0) return res[n][m];
	return  res[n][m] = C(n - 1, m) + C(n - 1, m - 1);
	//赋值给res[n][m]并返回
}
//或是下面这种把整张表计算出来的递推代码
//修改过,与pdf不同
//无法运行是错的,原版也是错的??
void calC() {
	for (int i = 0;i <= n;i++) {
		res[i][0] = res[i][i] = 1;//初始化边界
	}
	for (int i = 1;i <= n;i++) {
		for (int j = 1;j <= i / 2;j++) {
			res[i][j] = res[i - 1][j] + res[i - 1][j - 1];
			//递推计算C(i,j)
			res[i][i - j] = res[i][j];
			//c(i,i-j)=c(i,j)
		}
	}
}

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

long long c(long long n, long long m) {
	long long ans = 1;
	for (long long i = 1;i <= m;i++) {
		ans = ans * (n - m + i) / i;//注意要先乘再除;
	}
	return ans;

}

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

//递归
int c(int n, int m, int p) {
	if (m == 0 || m == n)return 1;//c(n,0)=c(n,n)=1
	if (res[n][m] != 0) return res[n][m];//已经有值
	return res[n][m] = (c(n - 1, m,p) + c(n - 1, m - 1,p)) % m;//赋值并返回
}
//递推
void calC() {
	for (int i = 0;i <= n;i++) {
		res[i][0] = res[i][i] = 1;//初始化边界
	}
	for (int i = 2;i <= n;i++) {
		for (int j = 1;j <= i / 2;j++) {
			res[i][j] = (res[i - 1][j] + res[i - 1][j - 1])%p;
			//递推计算C(i,j)
			res[i][i - j] = res[i][j];
			//c(i,i-j)=c(i,j)
		}
	}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
22.vector模板;
vector:变长数组,长度根据需要而自动改变的数组;(还可以用来以邻接表的方式存储图)
使用时需要添加vector头文件,即#include<vector>
定义:vector<typenname> name相当于一维数组name[size],只不过其长度可以根据需要发生变化。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
vector容器内元素的访问:
在这里插入图片描述

int main()
{
	vector<int> vi;
	for (int i = 1;i <= 5;i++) {
		vi.push_back(i);//push_back()在vi的末尾添加元素i,即依次添加12345
	}
	//vi.begin()为取vi首元素的地址,而it指向这个地址
	vector<int>::iterator it = vi.begin();
	for (int i = 0;i < 5;i++) {
		printf("%d ", *(it + i));
	}
	return 0;
}
//从这里可以看出v[i]和*(vi.begin()+i)是等价的

在这里插入图片描述

int main()
{
	vector<int> vi;
	for (int i = 1;i <= 5;i++) {
		vi.push_back(i);//push_back()在vi的末尾添加元素i,即依次添加12345
	}
	//vector的迭代器不支持it<vi.end()写法,因此循环条件只能用it!=vi.end()
	
	for (vector<int>::iterator it = vi.begin();it != vi.end();it++) {
		printf("%d ", *it);
	}
	return 0;
}

在常用stl容器中,只有在vector和string中,才允许使用vi.begin()+3这种迭代器加上整数的写法;
vector常用函数解析:
push_back();时间复杂度O(1)


int main()
{
	vector<int> vi;
	for (int i = 1;i <= 5;i++) {
		vi.push_back(i);//push_back()在vi的末尾添加元素i,即依次添加12345
	}
	//vector的迭代器不支持it<vi.end()写法,因此循环条件只能用it!=vi.end()
	
	for (vector<int>::iterator it = vi.begin();it != vi.end();it++) {
		printf("%d ", *it);
	}
	return 0;
}

pop_back() 用以删除vector的尾元素,时间复杂度O(1)

int main()
{
	vector<int> vi;
	for (int i = 1;i <= 5;i++) {
		vi.push_back(i);//push_back()在vi的末尾添加元素i,即依次添加12345
	}
	//vector的迭代器不支持it<vi.end()写法,因此循环条件只能用it!=vi.end()
	vi.pop_back();
	for (int i = 0;i < vi.size();i++) {//size()函数会给出vi元素的个数
		printf("%d ", vi[i]);
	}
	return 0;
}

在这里插入图片描述

在这里插入图片描述

int main()
{
	vector<int> vi;
	for (int i = 1;i <= 5;i++) {
		vi.push_back(i);//push_back()在vi的末尾添加元素i,即依次添加12345
	}
	//vector的迭代器不支持it<vi.end()写法,因此循环条件只能用it!=vi.end()
	vi.clear();
	printf("%d", vi.size());
	return 0;
}

insert()
在这里插入图片描述

int main()
{
	vector<int> vi;
	for (int i = 1;i <= 5;i++) {
		vi.push_back(i);//push_back()在vi的末尾添加元素i,即依次添加12345
	}
	//vector的迭代器不支持it<vi.end()写法,因此循环条件只能用it!=vi.end()
	vi.insert(vi.begin() + 2, -1);//将-1插入vi[2]的位置
	for (int i = 0;i < vi.size();i++) {
		printf("%d ", vi[i]);
	}//1 2 -1 3 4 5
	return 0;
}

erase:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
若想删除vector内的所有元素,正确的写法应该是vi.erase(vi.begin(),vi.end()),因为vi.end()是尾元素地址的下一个地址,最好的方法是使用vi.clear()
在这里插入图片描述
vector注意事项:
1.使用 vector v; 声明一个容器v时,如果没有给他预定存储空间(如:vector v;),则可以直接使用v.push_back(x)插入变量x,那么插入的第一个元素可以用v[0]访问到。
2.使用 vector v(n); 声明一个容器v时,如果给他预定存储空间(如:vector v(n)😉,则vector v(n) 等价于vector v(n,0); 如果要使得位置0存储元素x,则只能使用v[0]=x,如果使用v.insert(x)插入变量x,那么v的第一个元素还是0,即v[0]=0,因为v.push_back(x)是将x插入到v[n],又因为声明v时,v最多能存储n个元素,即x根本没有成功插入容器v中。

vector<int> v;
v.push_back(1);//只能这样赋值,不能用v[0]=1;
 
vector<int> v(n);//等价于vector<int> v(n,0);
v[0]=1;//只能这样赋值,不能用v.push_back(1),因为此时的v.push_back(1)是把1插入到v[n]位置,但是v[n]越界了,实际上是无法插入的;
 
vector<int> v(n,0);
v[0]=1;//只能这样赋值,不能用v.push_back(1);

23.set集合
在这里插入图片描述
需要添加头文件#include<set>;
set的写法

set<typename> name;

在这里插入图片描述
set只能通过迭代器访问

set<typename>::iterator it;

在这里插入图片描述

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
using namespace std;
int main()
{
	set<int> st;
	st.insert(3);//insert将x插入set中
	st.insert(3);
	st.insert(2);
	st.insert(5);
	//注意不支持it<st.end()的写法,同vector
	for (set<int>::iterator it = st.begin();it != st.end();it++) {
		printf("%d", *it);//235
	}
}

set内的元素自动递增排序,且自动去除了重复元素
set常用函数
在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
using namespace std;
int main()
{
	set<int> st;
	for (int i = 1;i < 3;i++) {
		st.insert(i);
	}
	set<int>::iterator it = st.find(2);
	//在set中查找2,返回其迭代器
	printf("%d\n", *it);
	//也可以直接写成printf("%d",*(st.find(2));
	return 0;
}

在这里插入图片描述

#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
using namespace std;
int main()
{
	set<int> st;
	for (int i = 1;i < 4;i++) {
		st.insert(i*100);
	}
	st.erase(st.find(100));
	//利用find()函数找到100,然后用erase删除它
	st.erase(st.find(200));
	for (set<int>::iterator it = st.begin();it != st.end();it++) {
		printf("%d", *it);
	}
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
using namespace std;
int main()
{
	set<int> st;
	for (int i = 0;i < 4;i++) {
		st.insert(i*100);
	}
	set<int>::iterator it = st.find(200);
	st.erase(it, st.end());//删除200至末尾之间的元素即200 300
	for (it = st.begin();it != st.end();it++) {
		printf("%d ", *it);//0 100
	}
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
using namespace std;
int main()
{
	set<int> st;
	for (int i = 0;i < 4;i++) {
		st.insert(i*100);
	}
	printf("%d", st.size());
	
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
using namespace std;
int main()
{
	set<int> st;
	for (int i = 0;i < 4;i++) {
		st.insert(i*100);
	}
	st.clear();
	printf("%d", st.size());
	
	return 0;
}

在这里插入图片描述
24.String
目的:为了更方便的对字符串进行操作
注意,使用string要添加头文件#include<string> ,string.h和string不是一样的头文件
string定义:在这里插入图片描述
string内容的访问:
通过下标:
可以直接像字符数组那样去访问string

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcd";
	for (int i = 0;i<str.length();i++) {
		printf("%c", str[i]);
	}

	return 0;
}

如果要读入和输出整个字符串,则只能用cin和cout,不能直接用printf

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcd";
	//如果要读入和输出整个字符串,则只能用cin和cout
	//printf("%s", str);
	string a;
	cin >> a;
	cout << a;

	return 0;
}

利用c_str()将string转换为字符数组,就可以利用printf输出;

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcd";
	//利用c_str()将string转换为字符数组,就可以利用printf输出;
	printf("%s", str.c_str());
	

	return 0;
}



通过迭代器访问:
一般的需求通过下标即可满足,但是有些函数比如insert erase要求以迭代器作为参数
string不像其他STL容器那样需要参数,因此可以直接下定义:

string::iterator it;
#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcd";
	string::iterator it;
	for (it=str.begin();it!=str.end();it++)
	{
		printf("%c", *it);
	}
	return 0;
}

string和vector一样,支持直接对迭代器进行加减某个数字,如str.begin()+3的写法是可行的

string常用函数

string加法可以直接将两个string拼接起来

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcd";
	string str2 = "efgh";
	string str3 = str + str2;
	str += str2;
	cout << str << endl;//abcdefgh
	cout << str3 << endl;//abcdefgh
	return 0;
}

在这里插入图片描述
在这里插入图片描述
结果分别为 ok1 ok2 ok3

在这里插入图片描述
insert
insert(pos,string)在pos号位置插入字符串string

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcd";
	string str2 = "efgh";
	str.insert(2, str2);
	cout << str << endl;//abefghcd
	return 0;
}



insert(it,it2,it3)
在这里插入图片描述

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcxyz";
	string str2 = "opq";
	//在str的三号位即c和x之间插入str2
	str.insert(str.begin()+3,str2.begin(),str2.end());
	cout << str << endl;//abcopqxyz
	return 0;
}



erase: 删除单个元素,删除一个区间内的所有元素复杂度均为O(N)

删除单个元素
str.erase(it),it为需要删除元素的迭代器

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcxyz";
	str.erase(str.begin()+2);
	cout << str;//abxyz
	return 0;
}

删除一个区间内的所有元素:
在这里插入图片描述
在这里插入图片描述
clear 清空string中的数据

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "abcxyz";
	str.clear();
	cout << str.length();
	return 0;
}



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

#pragma warning(disable:4996);
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<time.h>
#include<stdlib.h>
#include<vector>
#include<set>
#include<string>
using namespace std;
int main()
{
	string str = "thank you for your smile";
	string str2 = "you";
	string str3 = "me";
	if (str.find(str2) != string::npos) {
		cout << str.find(str2) << endl;//6
	}
	if (str.find(str2,7) != string::npos) {
		cout << str.find(str2,7) << endl;//14
	}
	if (str.find(str3) != string::npos) {
		cout << str.find(str3) << endl;
	}
	else {
		cout << "i know there is no position for me." << endl;
	}
	return 0;
}



在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值