算法笔记的笔记

基本知识

如果数组大小较大,需要定义在主函数外面,否则会异常退出。因为函数内部申请的局部变量来自于系统栈,允许申请的空间较小。函数外部申请的全局变量来自静态存储区。

memset按字节赋值(头文件string.h),所以最好初始化0或-1. 若需要赋值其他,用fill函数(memset快

int a[5];
memset(a,0,sizeof(a)); //全赋值为0

字符数组可以通过直接赋值字符串来初始化(仅限于初始化)

char str[15]="Good Story!";

%s识别空格作为字符串的结尾,所以到空格之后,scanf不能继续接收,scanf在使用%s时,后面对应数组名前面不用加&获取地址。

gets识别换行符\0作为输入结束。因此如果scanf完一个整数之后要使用gets,需要先用getchar接收整数后换行符。

gets(str1);
gets(str2);

不可输入:
Dear Mozart //用空格是错的,必须换行

可输入:
Dear
Mozart

若不是使用scanf的%s格式或者gets函数输入字符串(这两个可以结尾自动补\0),例如使用getchar,一定要在输入的每个字符串后面加"\0",否则printf和puts输出字符串会乱码

函数: strlen(), strcmp()

strcpy(str1,str2) 把str2复制给str1

strcat(str1,str2)把一个字符串接到另一个字符串后面

sscanf()和sprintf()

sscanf()是把字符数组中的内容以一定格式传到另一个变量。

如 sscanf(str,“%d”,&n); //把字符数组str中的内容以%d的格式写到n中。

int n;
char str[10]="123";
sscanf(str,"%d",&n);
printf("%d",n);

输出: 123

sprintf(str, “%d”,n)作用是把n以%d的格式写到str字符数组中

int n = 233;
int str[10];
sprintf(str,"%d",n);
printf("%s\n",str);

输出:233

用sscanf将str中的内容按格式分别写入n,db,str2:

int n;
float db;
char str[100]="2048:3.14,hello";
char str2[10];
sscanf(str,"%d:%f,%s",&n,&db,str2);
printf("n=%d,db=%.2f,str2=%s",n,db,str2);

输出:
n=2048,db=3.14,str2=hello

sprintf也可如上复杂,将多个变量内容按格式输到一个字符数组。

参数中数组的第一维不需要填写长度(二维数组要填第二维长度),数组作为参数时,在函数中对数组元素的修改就等同于对原数组元素的修改

数组可以作为参数,但不允许作为返回类型,如果要返回数组,只能用作为参数去修改(上述方式)

C++ 引用 对引用变量的操作就是对原变量的操作

void change(int&x){
  x=1;
}

int main(){
  int x=10;
  change(x); //也可用其他变量
  printf("%d",x);
  return 0;
}

传入地址交换两个变量:

void swap(int* &p1,int* &p2){
  int* temp;
  temp=p1;
  p1=p2;
  p2=temp;
}
int main(){
  int a=1,b=2;
  int *p1=&a, *p2=&b;
  swap(p1,p2);
  printf("a=%d,b=%d",*p1,*p2);
  return 0;
}

访问结构体中的元素

struct studentInfo{
  int id;
  char name[20];
  studentInfo* next;
}stu, *p;

//访问stu中变量的写法:
stu.id
stu.name
stu.next

//访问指针变量p中元素:
(*p).id
(*p).name
(*p).next

//访问结构体指针变量内元素还可写为:
p->id
p->name
p->next

构造函数初始化结构体:

(运行有问题)

struct Point{
    int x,y;
    Point(){};
    Point(int _x, int _y):x(_x),y(_y){};
}pt[10];

int main()
{
    int num=0;
    for(int i=1;i=3;i++){
        for (int j=1;j<=3;j++){
            pt[num++]=Point(i,j); //直接使用构造函数
        }
    }
    for(int i=0;i<=num;i++){
        printf("%d,%d\n",pt[i].x,pt[i].y);
    }
    return 0;
}

浮点数的比较 考虑到精确度

const double eps=1e-8;
#define Equ(a,b)((fabs((a)-(b)))<(eps)); 

多点测试

while(scanf("%d",&n)=!EOF){};
或者
while(T--){};

日期处理:

我的想法:

先用除、取余将两个日期的年月日分别提出来。假设第一个日期在前面。否则交换。

然后若为同一年,直接按月日计算出天数;

若不为同一年,将第一个日期加到与第二个日期相差一年(这期间相加的各年份的天数要算上),然后算出第一个日期离后一年有多少天,第二个日期距离该年开始有多少天,然后再相加。

字符串处理,说反话

int main()
{
    int num=0;
    char ans[90][90];
    while(scanf("%s",ans[num])&&(*ans[num]!='a')) //scanf直接按空格可分开
    {
        num++;
    }
    for(int i=num-1;i>=0;i--){
        printf("%s",ans[i]);
        if(i>0)
            printf("\n");
        
    }
    return 0;
}

//也可以用gets来一次性先得到整个数组,再按空格分开

sort(首个数指针,末尾的数的后一个指针,cmp)

cmp:

bool cmp(int a, int b){
  return a>b;
} //由大到小排序

递归

全排列:

#include <cstdio>
const int maxn = 11;
int n, p[maxn], hashTable[maxn]={false};//长度,排列数,标记

void generateP(int index){
  if(index==n+1){
    for(int i=1;i<=n;i++){
      printf("%d",p[i]);
    }
    printf("\n");
    return;
  }
  
  for(int x=1;x<=n;x++){
    if(hashTable[x]=false){
      p[index]=x;
      hashTable[x]=true;
      generateP(index+1);
      hashTable[x]=false;   //已处理完p[index]为x的子问题,还原状态(如果少了这一行就只能输											出第一个排列)
    }
  }
}

int main(){
  n=3;		//1~3的全排列
  generateP(1); //从p[1]的开始填
  return 0;
}

n皇后 count不加::会报错:Reference to ‘count’ is ambiguous

const int maxn = 11;
int n, p[maxn], hashTable[maxn]={false};//长度,排列数,标记
int count = 0;
void generateP(int index){
  if(index==(n+1)){
      bool flag = true;
      for (int i=1; i<=n; i++) {
          for(int j=i+1;j<=n;j++){
              if(abs(i-j)==abs(p[i]-p[j])){
                  flag=false;
              }
          }
      }
      if(flag){
          ::count++;
          for(int i=1;i<n;i++)
              printf("%d",p[i]);
          printf("\n");
      }
      
      return;
  }
  
  for(int x=1;x<=n;x++){
    if(hashTable[x]==false){
      p[index]=x;
      hashTable[x]=true;
      generateP(index+1);
      hashTable[x]=false;   //已处理完p[index]为x的子问题,还原状态
    }
  }
}

int main(){
  n=5;        //1~3的全排列
  generateP(1); //从p[1]的开始填
    printf("%d",::count);
  return 0;
}

n皇后改进,回溯法(在发现不管怎样都会失败后及时跳出回归)

void generateP(int index){
  if(index == n+1){//递归边界
    count++;		//能到这里的一定合法
    return;
  }
  for(int x=1;x<=n;x++){
    if(hashTable[x]==false){
      bool flag = true;
      for(int pre=1;pre<index;pre++){
        if(abs(index-pre)==abs(x-p[pre])){
        	flag=false;			//与之前的皇后在一条对角线,冲突,跳出这个递归
       		break;
        }
      }
     if(flag){			//如果可以把皇后放在x行
      p[index]=x;
      hashTable[x]=true;
      generateP(index+1);
      hashTable[x]=false;
    }
    }
  }
}

区间贪心

1.如果有小区间被另一大区间包含,则去掉大区间(因为留下小区间可以有更多空间容纳其它区间)

2.按左端点从大到小排序,左端点一样的按右断点从小到大排序


问题2:给出N个闭区间,求最少需要确定多少个点,才能使每个闭区间中都至少存在一个点


二分法

第一种问题可能查找失败,会返回-1,第二种问题不会查找失败,因为就算没有也会假设其存在的位置

一.查找序列中“是否存在”满足某条件的元素

// [left,right],传入的初值为[0,n-1]
int binarySearch(int A[],int left,int right, int x){
  while(left<=right){		//如果left>right就没办法形成闭区间了
    mid = (left+right)/2;
    if(A[mid]==x) return mid;
    else if(A[mid]>x){
      right=mid-1;
    }else{
      left=mid+1;
    }
  }
  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));
  return 0;
}

二. 解决“寻找有序序列第一个满足某条件元素的位置”问题的固定模版(若不存在,也可以返回一个“假设它存在,它应该在的”位置)

//二分区间为左闭右闭的[left,right],初值必须能覆盖解的所有可能取值
int solve(int left, int right){
  int mid;
  while(left<right){	//对[left,right]来说,left==right意味着找到了唯一位置
    if(条件成立){
      right = mid;
    }else{
      left = mid+1;
    }
  }
  return left; //返回夹出来的位置
}

1.需要找第一个小于等于x的元素,条件应为A[mid]<=x;

2.需要找第一个大于x的元素,条件应为A[mid]>x;

因为第一个出现的满足条件的,一定是从左往右第一个出现的满足条件的(木棒切割问题刚好相反);所以若条件成立,此时的A[mid]一定在最后得到的正确答案的右侧(或其本身就是答案),所以满足条件后 right=mid;

如果想要找最后一个满足条件C的元素,可以找到第一个不满足条件C的元素,然后将该位置-1即可

木棒切割问题,因长度与段数大小约成反比,所以“段数至少为k”是要找到最后一个满足条件的长度。(长度从小到大,则段长从大到小)

n=3, k=7 , 三根长度分别为:10,15,24时

L: 12 6 9 8 7

K: 3 7 4 5 6 (如表,段数与长度约成反比)

int n,k;
int f(int A[],int l){
  int result=0;
  for(int i=0;i<n;i++){
    result += A[i]/l;
  }
  return result;
}

bool cmp(int a,int b){
  return a>b;
}

int binarSolve(int a[],int left,int right){   //left和right是长度, 长度是从小到大
  											   //所以结果也是从满足到不满足,与之前相反
    int mid,count;
    while(left<right){
        mid=(left+right)/2;
        count=f(a,mid);
        if(count<k)         //题目问至少有k段,要找到最后一个符合条件的,所以找到第一个不符合										条件的然后-1
            right=mid;
        else
            left=mid+1;
    }
    return left-1;
}

int main(){
  int result,A[n];
  scanf("%d%d",&n,&k);
  for(int i=0;i<n;i++){
    scanf("%d",&A[i]);
  }
  sort(A,A+n,cmp);			//选出最大的木棒长度,分段长度不能超过它
  int l = A[0];
    result = binarSolve(A, 1, l);
    printf("the result is %d\n",result);
  return 0;
}

左开右闭区间(left, right] : 修改两处:

1.left<right 改为left+1<right

2.left=mid+1 改为left=mid

//二分区间为左开右闭的(left,right],初值必须能覆盖解的所有可能取值,且left比最小取值小1
int solve(int left, int right){
  int mid;
  while(left+1 <right){	//对[left,right]来说,left==right意味着找到了唯一位置
    if(条件成立){
      right = mid;
    }else{
      left = mid;
    }
  }
  return left; //返回夹出来的位置
}

三. 求√2的近似值(精确到10^-5)

相当于求f=x*x-2 的零点

const double eps=1e-5;
double f(double x){
  return x*x;
}
double calSqrt(){
  double left=1,right=2,mid;  //(√2)在1与2之间
  while((right-left) > eps){
    mid = (right+left)/2;
    if(f(mid)>2){		//即当mid>√2 时
      right=mid;
  }else{			//这里的else相当于 f(mid)<2, 因为mid的平方不能能为2,√2不是一个有理数
    left=mid;
    }
  }
  return mid;
}
//求函数零点问题

const double eps=1e-5;
double f(double x){
  //函数内容
}
double solve(double L, double R){
  double right = R, left=L, mid;
  while((right-left)>eps){
    mid=(left+right)/2;
    if (f(mid)>0)
    right=mid;
  else
    left=mid;
  }
  return mid;
}

快速幂

1.二分幂,快速幂的递归写法

typedef long long LL;
//求a^b%m 的递归写法, 例如a^4%m=a^2*a^2%m, a^5%m=a*a^4%m;
LL binaryPow(LL a,LL b,LL m){
  if(b==0)
    return 1;
  else if(b&1)		//b为奇数
    return a*binaryPow(a,b-1,m)%m;
  else{
    LL mul = binaryPow(a,b/2,m);
    return mul*mul%m;
  }
}

2.快速幂的迭代写法

typedef long long LL;
//求a^b%m 的迭代写法,将b看作2进制数,当某一位为1时才需要乘入结果
LL binaryPow(LL a,LL b,LL m){
  int ans=1;
  while(b>0){
    if(b&1){			//b为奇数是,代表最后一位为1(二进制最后一位)
      ans=ans*a%m;
    }
    a=a*a;		//a平方
    b>>=1;		//将b的二进制右移1位,即b>>=1或者b=b/2
  }
  return ans;
}

⚠️注:

1.初始时a有可能大于m,则进入函数前就让a对m取模

2.如果m为1,可以直接在函数外特判为0(任何数对1取模都为0)

two pointers

1.求递增数组中和相加为m的两个数(i=j时不行,只能i<j,因为不能把一个数选两次)

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

2.序列合并问题(从小到大排列)

int merge(int A[],int B[],int C[],int n, int m){
  while(i<n&&j<m){
    int i=0,j=0,index=0;
    if(A[i]<A[j]){
      C[index++]=A[i++];
    }
    else{
      C[index++]=B[j++];
    }
  }
  while(i<n) C[index++]=A[i++];			//剩下来的多的接到后面
  while(j<m) C[index++]=B[j++];
  return index;			//返回序列C的长度
}

3.归并排序

1>递归实现

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;
  int temp[maxn],index=0;
  while(i<R1&&j<R2){
    if(A[i]<A[j]){
      temp[index++]=A[i++];
    }
    else{
      temp[index++]=j++;
    }
  }
  while(i<=R1) temp[index++]=A[i++];			//剩下来的多的接到后面
  while(j<=R2) temp[index++]=A[j++];
  for(i=0;i<index;i++){
    A[L1+i]=temp[i];			//将合并后的序列赋值回数组A
  }
}


//将array数组当前区间[left,right]进行归并排序
void mergeSort(int A[],int left,int right){
  int mid =(left+right)/2;
  mergeSort(A,left,mid);				//递归,将左子区间[left,mid]归并排序
  mergeSort(A,mid+1,right);				//递归,将右子区间[mid+1,right]归并排序
  merge(A,left,mid,mid+1,right); 		//左右子区间合并排序
}

2>非递归实现

void mergeSort(int A[]){
  //step为组内元素个数,step/2为左子区间元素个数,⚠️注意等号可以不取
  for(int step=2;step/2<=n;step*=2){
    for(int i=1;i<=n;i+=step){		//对每一组
      int mid = i+step/2-1;			//左子区间元素个数为step/2
      if(mid+1<=n){					//右子区间存在元素则合并
        //左子区间为[i,mid],右子区间为[mid+1,min(i+step-1,n)]
        merge(A,i,mid,mid+1,min(i+step-1,n));
      }
    }
  }
}

4.快速排序

//对区间[left,right]进行划分(按中间值划分)
int Partition(int A[],int left,int right){
  int temp=A[left];			//讲A[left]作为中间值存在temp中
  while(left<right){
    while(left<right && A[right]>temp)	right--;
    A[left]=A[right];	//将此时right所指的值放到A[left]
    while(left<right && A[left]<=temp)	left++;
    A[right]=A[left];
  }
  A[left]=temp;
  return left;		//返回相遇下标
}

//快速排序,left与right初值为序列首位下标(例如1与n)
void quickSort(int A[],int left,int 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);
  }
}

生成随机数:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
  srand((unsigned)time(NULL));
  for(int i=0;i<10;i++){
    printf("%d",rand());
  }
  return 0;
}
//rand()%(b-a+1)+a的范围是[a,b]
//例如:rand()%2 范围是[0,1]   rand()%5+3 范围是[3,7]

rand()函数只能是[0,RAND_MAX]范围内整数,RAND_MAX是stdlib.h中的一个常数,在不同系统中该常数的值不同。如:32767,若范围要为[a,b]且左右两端超过RAND_MAX, 则可以

(int)((double)rand()/32767*(b-a+1)+a), 相当于这个浮点数就是[a,b]范围内的比例位置。

随机快排

round()函数cmath标头的库函数,用于对最接近该数字的给定值进行四舍五入,一半的情况下舍入为零,它接受一个数字并返回四舍五入的值。

int randPartition(int A[],int left,int right){
  //生成[left,right]内的随机数
  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){
    while(left<right && A[right]>temp)	right--;
    A[left]=A[right];
    while(left<right && A[left]<=temp)	left++;
    A[right]=A[left];
  }
  A[left]=temp;
  return left;
}

随机选择算法

//随机选择算法,从A[left,right]中返回第K大的数
int randSelect(int A[],int left,int right,int K){
  if(left==right)	return A[left];
  int P = randPartition(A,left,right);
  int M = P-left+1;			//A[p]是A中[left,right]中的第M大
  if(M==K)	return A[P];
  if(K<M){
    return randSelect(A,left,p-1,K);		//往主元左侧找第K大
  }
  else
    return randSelect(A,p+1,right,K-M);			//往主元右侧找第K-M大
}

数学问题

最大公约数

辗转相除法(欧几里得算法)(其他写法需要注意a和b的大小关系)

int gcd(int a ,int b){
  if(b==0)
    return a;
  else return gcd(b,a%b);			//a小于b则会交换位置
}

或者:
  
int gcd(int a,int b){
  return !b?a:gcd(b,a%b);
}

素数筛法(复杂度O(nloglogn))

从小到大1~n,筛去素数的倍数。如果没有被前面的步骤筛去,那么从小到达轮着的下一个没被筛去的一定是素数。如2为素数,则筛掉4,6,8… ,下一个是3为素数,筛掉6,9,12…

const int maxn=101;		//表长
int prime[maxn],pNum=0;
bool p[maxn]={0};		//i为素数时,p[i]=true
void Find_Prime(){
  for(int i=2;i<maxn;i++){
    if(p[i]==true){
      prime[pNum++]=i;
      for(int j=i+i;j<maxn;j+=i){		//筛去素数的倍数
        p[j]=false;
      }
    }
  }
}

大整数

用数组存数字,定义结构体,数组低位存放大整数的低位

构造函数初始化结构体

struct bign{
  int d[1000];
  int len;
  bign(){
    memset(d,0,sizeof(d));
    len=0;
  }
}

大整数加法:

bign add(bign a,bign b){
  bign c;
  int carry;
  for(int i=0;i<a.len || i<b.len;i++){
    int temp=a.d[i]+b.d[i]+carry;
    c.d[c.len++]=temp%10;	//个位为其结果
    carry=temp/10;
  }
  if(carry!=0){
    c.d[c.len++]=carry;
  }
  return c;
}

当使用STL的queue时,元素入队的push操作只是制造了该元素的一个副本入队,因此在入队后对原元素的修改不会影响队列中的副本,对队列中副本的修改也不会影响到原元素,因此,当需要对队列中元素进行修改而不是仅仅访问时,队列中存放的元素最好不要是元素本身,而是他们的编号(如果是数组的话就是下标)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值