c/c++语言算法技巧汇总/大复习[未完结]

目录

分配回收内存

C函数库中的malloc和free分别用于执行动态内存分配和释放。

这两个函数的原型如下所示,他们都在头文件stdlib.h中声明。

c 复制代码
void *malloc ( size_t size );
void free ( void *pointer );
void *realloc (void ptr, size_t new_size );

malloc的作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL,确保非空之后再使用非常重要。malloc所分配的内存是一块连续的空间。同时,malloc实际分配的内存空间可能会比你请求的多一点,但是这个行为只是由编译器定义的。malloc不知道用户所请求的内存需要存储的数据类型,所以malloc返回一个void *的指针,它可以转换为其它任何类型的指针。

realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小。当起始空间的地址为空,即ptr = NULL,则同malloc。

如果原先的内存尾部空间不足,或原先的内存块无法改变大小,realloc将重新分配另一块nuw_size大小的内存,并把原先那块内存的内容复制到新的内存块上。因此,使用realloc后就应该改用realloc返回的新指针。

string/char*篇

strchr() 函数用于查找给定字符串中某一个特定字符。

头文件:string.h

语法/原型:

char* strchr(const char* str, int c);

参数说明:

str:被查找的字符串。

c:要查找的字符。

strchr() 函数会依次检索字符串 str 中的每一个字符,直到遇见字符 c,或者到达字符串末尾(遇见\0)。

返回值:返回在字符串 str 中第一次出现字符 c 的位置,如果未找到该字符 c 则返回 NULL。

易错tips

  1. 定义char a[ ][ ]时用scanf("%s",a[i]);输入每一行时最后都会加入一个换行符进去 所以要多留一个空 否则会出现很奇怪的错
  2. 注意向char数组和int数组放入数字 再取出来是不一样的char数组取出来转变成int还需要减去'0'

串的置换算法

编写一个实现串的置换操作Replace(&S,T,V)的算法。对一个字符串S,使用一个长度小于S的串T替换S的子串V,并将替换后的串输出。

cpp 复制代码
Status StrReplace(HString &S, HString T, HString V)//将v替换主串s中出现的所有与T相等的不重叠子串
{
	HString head, tail, temp, S0;
	int i, n;
	for (n = 0, i = 1; i <= (S.length - T.length + 1); i++)
	{
		SubString(temp, S, i, T.length);//用temp返回串s的第i个字符起长度为len的子串
		if (!StrCompare(temp, T))//返回0也就是相等时
		{
			SubString(head, S, 1, i - 1);
			int m = S.length - i - T.length + 1;
			SubString(tail, S, i + T.length, m);
			Concat(S0, head, V);//s0返回head+v
			Concat(S, S0, tail);
			i += V.length - 1;
			n++;
		}
	}
	printf("%s\n", S.ch);
	return n;
}

kmp算法

  1. 目的:有一个文本串S,和一个模式串P,实现主串和子串快速匹配,最终查找到P在S中的位置。解决暴力匹配过程中的回溯浪费,即主串和子串发生不匹配时子串仅后移一位继续匹配完全不相同的一段浪费。

  2. 基本元素

    (1)next数组:为了编程方便,间接表示PMT(部分匹配值),它的值就是将PMT数组向后偏移一位得到的。next第0位的值,我们将其设成了-1,这只是为了编程的方便,并没有其他的意义。

    求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。也就是说:从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算在任一位置,能匹配的最长长度就是当前位置的next值。

    (2)匹配步骤

    默认两个指针都从头开始匹配,两个指针指的值一样时都向后匹配下一个字符

    如果刚开始就都不相同将整个子串向后挪一位;发现不匹配时将j指针挪动到新的位置(往后挪几位取决于当前最后一个匹配字符的next值是几,移动的位数=已匹配数-next[当前最后一个匹配字符的下标])

    (3)实例:要在主字符串"ababababca"中查找模式字符串"abababca"

    pmt的值易知,就是包括当前下标 至串首的子串的前缀集合与后缀集合的交集中最长元素的长度。

    next数组易知,是pmt数组往后移一个单位,前面补-1。定义为(不包括第0个字符,第0个字符默认next值为-1)不包括当前下标至串首的子串的前缀集合与后缀集合的交集中最长元素的长度。

  3. 原理

    (1)字符串的前缀和后缀

    如果字符串A和B,存在A=BS,其中S是任意非空 字符串,那就称B为A的前缀。例如,"Harry"的前缀包括{"H", "Ha", "Har", "Harr"}

    我们把所有前缀组成的集合,称为字符串的前缀集合。后缀及后缀集合同理。

    (2)PMT(部分匹配值)中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。

    例如,对于"aba",它的前缀集合为{"a", "ab"},后缀 集合为{"ba", "a"}。两个集合的交集为{"a"},那么长度最长的元素就是字符串"a"了,长 度为1,所以对于"aba"而言,它在PMT表中对应的值就是1。

    对于字符串"ababa",它的前缀集合为{"a", "ab", "aba", "abab"},它的后缀集合为{"baba", "aba", "ba", "a"}, 两个集合的交集为{"a", "aba"},其中最长的元素为"aba",长度为3。

    如果在 j 处字符不匹配,主字符串在 i 位失配,也就意味着主字符串从 i−j 到 i 这一段是与模式字符串的 0 到 j 这一段是完全相同的。

    模式字符串从 0 到 j−1 ,在这个例子中就是"abababca",其前缀集合与后缀集合的交集的最长元素为"abab", 长度为4。主字符串中i指针之前的 4 位一定与模式字符串的第0位至第 4 位是相同的,即长度为 4 的后缀与前缀相同。

    我们就可以将这些已经匹配好的字符段的比较省略掉。做法是,保持i指针不动,然后将j指针指向模式字符串的PMT[j −1]位即可。

    所以为了编程的方便, 我们不直接使用PMT数组,(因为要j-1),而是将PMT数组向后偏移一位。我们把新得到的这个数组称为next数组。

    匹配过程视频见 https://v.youku.com/v_show/id_XMzUyMjQyMTE3Mg==.html

    (2:15)

最简单的标记book

练习1 L1-011 A-B (20分)

本题要求你计算A−B。不过麻烦的是,A和B都是字符串 ------ 即从字符串A中把字符串B所包含的字符全删掉,剩下的字符组成的就是字符串A−B。

输入格式:

输入在2行中先后给出字符串A和B。两字符串的长度都不超过10^​4,并且保证每个字符串都是由可见的ASCII码和空白字符组成,最后以换行符结束。

输出格式:

在一行中打印出A−B的结果字符串。

输入样例:

I love GPLT! It's a fun game!

aeiou

输出样例:

I lv GPLT! It's fn gm!

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char a[10001];
	char b[10001];
	int c[128];
	int i;
	gets(a);
	gets(b);
	for(i=0;i<128;i++)
	{
	  c[i]=0;	
	}	
	for(i=0;i<strlen(b);i++)
	{
		c[(int)b[i]]=1;
	}
	for(i=0;i<strlen(a);i++)
	{
		if(c[(int)a[i]]==0)
		  printf("%c",a[i]);
	}
	printf("\n");
	return 0;
 } 

最简单的hash表

子串变位词

变位词:组成字母完全相同但顺序不一样,如abc acb。给定两个串a和b,问b是否是a的子串的变位词。例如输入a = hello, b = lel, lle, ello都是true,但是b = elo是false。

cpp 复制代码
1.方法1:暴力
依次遍历b中字母检查是否在a里
2.方法2:先给两个串排序,然后比较
3.制作hash表
//制作a串的hash表
for (int i = 0; i < lena; ++i) 
	num[a[i] -- 'a']++;利用ascii码的差作为下标,形成对应的映射 如果出现0的情况则不是变位词
for (int j = 0; i < lenb; ++i) 
	if (num[b[i] -- 'a'] == 0)
		{	cout<<"false"<<endl;
			return 0;}
cout<<"true"<<endl;

排序

最简单的桶排序

利用数组序号的自顺序进行排序输入输出

c 复制代码
int book[1001],i,j,t,n;//size=num-1
for(i=0;i<=1000;i++)
	book[i]=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
	scanf("%d",&t);
	book[t]++;
}
for(i=1000;i>=0;i--)
	for(j=1;j<book[i];j++)//book[i]里有几就打印几次
		printf("%d",i);

字典序

先按照第一个字母、以 a、b、c......z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,sigh 和 sight),那么把短者排在前。

快排

基于二分法的思想。

练习1 :0-1交换

把一个0-1串(只包含0和1的串)进行排序,你可以交换任意两个位置,问最少交换的次数?

分析: 快排partition:最左边的那些0和最右边的那些1都可以不管;从左开始扫到第一个出现1的位置,从右扫第一个出现0的位置 然后进行交换即可。

cpp 复制代码
int answer = 0;	//次数
for (int i = 0, j = len -- 1; i < j; ++i, --j) {
	for (;(i < j) && (a[i] == '0');++i);
	for (;(j > i) && (a[j] == '1'); --j);
	if (i <j) ++answer;
} 
练习2:交换星号

一个字符串只包含和数字,请把它的星号都放开头。

cpp 复制代码
//这个方法会打乱数字顺序
for (int i = 0, j = 0; j < n; ++j) 
		if (s[j] == '*') swap(s[i++], s[j]);
//不打乱数字顺序的方法 直接倒着覆盖 省空间
int j = n - 1;
	for (int i = n - 1; i >= 0; --i) 
			if (isdigit(s[i])) s[j--] = s[i];//isdigit是否是数字
	for (; j >= 0; --j) s[j] = '*';

最简单的去重

c 复制代码
//假设经过排序之后
for(i=2;i<=n;i++)
	if(a[i]!=a[i-1])
		printf("%d",a[i]);

线性表篇

线性表是具有相同特性的数据元素的一个有限序列,其中数据元素的个数n定义为表的长度,当n = 0时称为空表;在非空的线性表,有且仅有一个开始结点,其没有直接前驱,仅有一个直接后继;有且仅有一个终端结点,其没有直接后继,仅有一个直接前驱;其余的内部结点都仅有一个直接前驱和一个直接后继。

线性链表

练习1:生成26个字母的线性表,并实现对特定字母的插入和删除的程序
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define YES 1
#define NO 0
#define OK 1
#define ERROR 0
#define SUCCESS 1
#define UNSUCCESS 0
#define OVERFLOW -2
#define UNDERFLOW -3
#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
typedef int Status;
typedef char ElemType;
typedef struct {
    ElemType *elem;
    int length;
    int listsize;
}SqList;

Status InitList(SqList *L){
    (*L).elem =(ElemType *)malloc(LIST_INIT_SIZE *sizeof(ElemType));
    if(!(*L).elem) exit(OVERFLOW);//分配失败
    (*L).length=0;
    (*L).listsize = LIST_INIT_SIZE;
    return OK;
}

Status ListInsert(SqList *L,int i,ElemType e)//插入第i位即下标i-1 值为e
{   ElemType *newbase;
    ElemType *p,*q;
    if(i<1||i>(*L).length+1)return ERROR;
    if((*L).length>=(*L).listsize){
        newbase = (ElemType *) realloc ((*L).elem,((*L).listsize+LISTINCREMENT)*sizeof(ElemType));
        if(!newbase) exit (OVERFLOW);
        (*L).elem =newbase;
        (*L).listsize +=LISTINCREMENT;

    }
    q=&((*L).elem[i-1]);
    for(p=&((*L).elem[(*L).length-1]);p>=q;--p)
        *(p+1) = *p;
    *q=e;
    ++(*L).length;
    return OK;

}

Status ListDelete(SqList *L,int i,ElemType *e)
{
    int j;
    ElemType *p,*q;
    if(i<1||i>(*L).length)return ERROR;
    p=&(*L).elem[i-1];
    *e=*p;
    q=(*L).elem+(*L).length-1;//指向最后的一个节点
    for(;p<=q;++p)
        *(p-1)=*p;
    (*L).length--;
    return OK;
}
Status GetElem(SqList L,int i ,ElemType *e )
{
    if(i<1||i>L.length)return ERROR;
    else *e=L.elem[i-1];
    return OK;
}

int main(){
SqList L;
int i;
ElemType e,z;
InitList (&L);
for(int i=1; i<=26;i++)
    ListInsert(&L,i,(char)i+64);

ListDelete(&L,4,&z);
GetElem(L,4,&z);
printf("%c\n",z);

ListInsert(&L,4,'D');
GetElem(L,4,&z);
printf("%c\n",z);
}
练习2:约瑟夫环

约瑟夫环问题是一个很经典的问题:一个圈共有N个人(N为不确定的数字),第一个人的编号为1,假设这边我将第一个人的编号设置为1号,那么第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为N号,现在提供一个数字M,第一个人开始从1报数,第二个人报的数就是2,依次类推,报到M这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到M的人出局,直到N个人全部出局,请问,这个出局的顺序是什么?

方法1:数组方式

数组:一开始将所有的元素初始化为0,代表一开始所有人都处于未出局的状态,一旦某个人出局,将其对应的数组元素的值设为非0的一个数,代表他不再报数。

i:既代表数组的下标,也代表每个人的编号

k:用来计数,从0开始,一旦k的值达到M,代表当前这个人需要出局,并且k的值需要重新置为0,这样才能找到所有需要出局的人

方式2:实现循环链表

cpp 复制代码
//方法1
#include<bits/stdc++.h>
using namespace std;
//用数组实现约瑟夫环问题
int a[110]={0};   //元素值为0表示未出局 
//i既代表数组的下标,也代表每个人的编号
//k是用来计数的,一旦k的值达到m,代表此人需要出局,并且k需要重新计数,这样才能够找出所有需要出局的人
//数组的0代表未出局的人,数组非0代表出局的人,未出局的人需要报数,出局的人不需要报数 
int main()
{
	int N,M;
	int cnt=0,i=0,k=0;  //cnt表示目前出局的人数 
	cin>>N>>M;  //表示总共有n人,数到数字m时出局 
	while(cnt!=N) //因为要求N个人的出局顺序,因此当cnt(用来统计已经出局的人)未达到n时,需要循环不断报数 
	{
		i++;   //i是每个人的编号 
		if(i>N) i=1;  //这里需要特别注意:i的值是不断累加的,一旦发现i的值>N,那么i需要重新从第1个人开始
		              //数组要从第一个元素重新开始一个一个往后判断 
		if(a[i]==0)   //只有元素值为0的人 才需要报数,元素值为非0的代表已经出局了,不用报数 
		{
			k++;
			if(k==M)     //代表已经某个人已经报了M这个数,需要出局 
			{
				a[i]=1;  //编号为i的这个人出局 
				cnt++;   //出局的人数+1 
				cout<<i<<" ";  //输出出局的人的编号 
				k=0;   //清空k,让下一个人重新从1开始报数   
			}
		}
	}
	return 0;
} 

//方法2
typedef struct node  //typedef用来重命名struct node这种数据类型,将其命名为Node 
{
	int data;
	struct node* next;
}Node;

	//初始化循环链表
	Node *head = NULL,*p=NULL,*r=NULL;   //head为头指针,指向链表的第一个结点,一开始赋值为NULL,代表不指向任何结点 
	head = (Node*)malloc(sizeof(Node));  //让head指向一个实际的空间
	if(NULL==head)  //内存空间可能会申请失败,大多数情况不会申请失败 
	{
			cout<<"Memory Failed!";
			return;
	} 
	head->data=1;       //从1开始编号 
	head->next=NULL;    //一开始整个链表只有一个Node(结点),这个Node有两个域,分别是data和next
	                    //data从1开始,next指向NULL,总共需要N个结点,现在创建了一个,还需要N-1个 
    p=head;             //head要保持不能改变,才能够找到链表的起始位置,一开始p也指向第一个结点
	                    //p等一下会被使用,用它可以便于创建剩下的N-1个结点 
	
	//尾插法创建链表,已经有一个1号结点了,还需要创建剩下的n-1个结点 
	for(int i=2;i<=N;i++)
	{
		r=(Node*)malloc(sizeof(Node)); 
		r->data=i;
		r->next=NULL;
		//插入结点 
		p->next=r;
		p=r;
	}
	//创建循环链表
	p->next=head;   //最后一个结点的next指向头结点
	p=head;         //为后续方便,将p指向头结点
	
	//开始游戏
	while(p->next!= p)  //如果p的next=p,说明目前只有一个元素 
	{
		for(int i=1;i<M;i++)  //报到数字为M的时候出局 
		{
			  r=p;   //保留出局的前一个结点 
			  p=p->next; //p指向的是要出局的这个结点,需要保留前一个结点
		}
		// 输出
		cout<<p->data<<" ";
		r->next=p->next;    //删除p的目的,此时p指向哪里?  :
		free(p);  
		p=r->next;  //更新p重新进行报数 
	} 
	cout<<p->data; 
练习3:Joseph问题

练习2的变形。

约瑟夫(Joseph)问题的一种描述是:编号为 1,2,...,n 的 n 个人按顺时针方向围坐一圈,每人持有一个密码(正整数),一开始任选一个整数作为报数上限 m,从第一人开始按顺时针方向从自 1 开始顺序报数,报到 m 时停止报数。报 m 的人出列,将他的密码作为新的 m 值,从他的顺时针方向上的下一个人开始重新从 1 报数,如此下去,直至所有人全部出列为止,设计一个程序求出出列顺序。

采用单向循环链表模拟此过程,按照出列的顺序印出各人的编号

cpp 复制代码
/**
	Joseph问题
	Author:BaoMinyang
	Date:2018/09/20 
*/
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
#define OK 1
#define ERROR 0
#define OVERFLOW -2

typedef int ElemType;
typedef int DeleteType;
typedef int Status;

//定义链表结点 
typedef struct LNode{
	ElemType num;
	ElemType data;
	LNode *next;
}*LinkList;

//定义被删除的结点编号变量 
DeleteType del_num;

//链表初始化 
Status InitRList(LinkList &L){
	L = (LinkList) malloc (sizeof(LNode));
	if(!L) exit(OVERFLOW);
	L->next = NULL;
	return OK;
}

//创建链表 
Status CreateRList(LinkList &L,int a[],int n){
	LinkList r = L;
	for (int i = 0; i < n; ++i){
		LinkList q = (LinkList) malloc (sizeof(LNode));
		q->num = i+1;
		q->data = a[i];
		r->next = q;
		r = q;
		printf("num:%d,p:%d\n",q->num,q->data);
	}
	r->next = L->next; 
	return OK;
}

//删除链表结点 
LinkList DeleteRList(LinkList &m,int key){
	LinkList p,q;
	p = m;
	for (int j = 0; j < key-1; j++) p = p->next;
	q = p->next;
	p->next = q->next;
	printf("num:%d出列\n",q->num);
	del_num = q->data;
	free(q);
	return p;
}

//游戏开始 
Status Joseph(LinkList &L,int n,int key){
	LinkList q = L;
	bool isDone = 1;
	while (n-1){
		if (isDone){
			q = DeleteRList(q,key);
			isDone = 0;
		}
		else {
			q = DeleteRList(q,del_num);
		}
		n--;
	}
	return q->num;
}

int main(){	

	int a[35],n=0,init_m;
	LinkList L;
	
	printf("请输入m的初始值:");
	scanf("%d",&init_m);
	
	printf("请输入参加游戏的人数n:");
	scanf("%d",&n);
	
	printf("请输入每个人的密码:");
	for (int i = 0; i < n; ++i){
		scanf("%d",&a[i]);
	}
	
	InitRList(L);
	
	CreateRList(L,a,n);
	
	printf("游戏开始:\n"); 
	printf("最终剩下的是num:%d",Joseph(L,n,init_m));
	
	return 0;
}

数制转换
cpp 复制代码
/*
By WYY
*/
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
#define OVERFLOW -1
#define MAXSIZE 100
typedef int Status;
typedef int SElemType;

typedef struct {
    SElemType *base;
    SElemType *top;
    int stacksize;}SqStack;
    
Status InitStack(SqStack &s){
	s.base = (SElemType *)malloc(STACK_INIT_SIZE *sizeof(SElemType));
	if(!s.base)exit(OVERFLOW);
	s.top=s.base;
	s.stacksize=STACK_INIT_SIZE;
	return OK;
}

Status Push(SqStack &s,SElemType e)//插入新元素e
{
    if(s.top-s.base>=s.stacksize){//栈满 需要追加存储空间
        s.base=(SElemType *)realloc(s.base,(s.stacksize +STACKINCREMENT)* sizeof(SElemType));
        if(!s.base)exit(OVERFLOW);
        s.top =s.base +s.stacksize;
        s.stacksize +=STACKINCREMENT;
    }
    *s.top++ =e ;
    return OK;
}

Status Pop(SqStack &s ,SElemType &e){//用e返回删除的这个值
	if(s.top == s.base)return ERROR;
	e = * --s.top;
	return OK;
}

Status StackEmpty(SqStack &s){
if(s.top==s.base)return TRUE;
else return FALSE;
}

void conversion();

int main(){
    conversion();
}

void conversion (){
    int n,m;//数n,进制m
    cin>>n>>m;
    int e;
    SqStack s;
    InitStack(s);

    while(n){
        Push(s,n%m);
        n=n/m;}
        
    while(!StackEmpty(s))
    {
        Pop(s,e);
        printf("%d",e);
    }
}
括号匹配
表达式求值
回文串------用栈解密

队列

操作受限的线性表。

判断回文
cpp 复制代码
while ((ch = getchar()) != '\n'){
		Push(S,ch);
		EnQueue(q,ch);
	}
	while (!StackEmpty(S)){
		Pop(S,e1);
		DeQueue(q,e2);
		if (e1 != e2) 
		{
			printf("No!!");
			return OK;
		}
	}
猴子分桃

动物园里的n只猴子编号为 1,2,...,n,依次排成一队等待饲养员按规则分桃。动物园的分桃规则是每只猴子可分得m个桃子,但必须排队领取。饲养员循环地每次取出1 个,2 个,...,k个桃放入筐中,由排在队首的猴子领取。取到筐中的桃子数为k 后,又重新从1开始。当筐中桃子数加上队首猴子已取得的桃子数不超过m 时,队首的猴子可以全部取出筐中桃子。取得桃子总数不足m个的猴子,继续到队尾排队等候。当筐中桃子数加上队首猴子已取得的桃子数超过m时,队首的猴子只能取满m个,然后离开队列,筐中剩余的桃子由下一只猴子取用。上述分桃过程一直进行到每只猴子都分到m 个桃子。对于给定的n,k和 m,模拟上述猴子分桃过程。

★数据输入

第 1 行中有 3 个正整数 n,k 和 m,分别表示有 n 只猴子,每次最多取k个桃到筐中,每只猴子最终都分到m个桃子。

★数据输出

将分桃过程中每只猴子离开队列的次序依次输出

输入示例

5 3 40

输出示例

1 3 5 2 4

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
#define OK 1
#define ERROR 0
#define OVERFLOW -2

typedef int ElemType;
typedef int DeleteType;
typedef int Status;

//定义链表结点
typedef struct LNode{
	ElemType num;
	ElemType data;
	LNode *next;
}*LinkList;


//链表初始化
Status InitRList(LinkList &L){
	L = (LinkList) malloc (sizeof(LNode));
	if(!L) exit(OVERFLOW);
	L->next = NULL;
	return OK;
}

//创建链表
Status CreateRList(LinkList &L,int n){
	LinkList r = L;
	for (int i = 0; i < n; ++i){
		LinkList q = (LinkList) malloc (sizeof(LNode));
		q->num = i+1;//排编号
		q->data = 0;//桃子数为0
		r->next = q;//放进链表
		r = q;//尾指针更新
	}
	r->next = L->next;
	return OK;
}

int ListLength(LinkList L)
{
	if (L->next == NULL) return 0;
	int i = 0;
	LinkList p = L->next;
	if (p->next == L->next) return 1;
	else{
		p = p->next;
		while(p != L->next)
		{
			i++;
			p=p->next;
		}
	}
	return i+1;
}

//删除链表结点
LinkList DeleteRList(LinkList &L,int m){
	LinkList p,q;
	p = L;
	while (p->next->data != m)
		p = p->next;//p指向桃子数为m的前一个
	q = p->next;//q指向桃子数为m的那个
	p->next = q->next;//将桃子数为m的节点删除
	p = q->next;//p指向更新的节点
	printf("num:%d出列\n",q->num);
	free(q);
	return p;
}

//开始分桃
Status Peaches(int n,int k,int m){//n猴子,最多一次放k,一猴子拿够m
	LinkList L,p;
	InitRList(L);
	CreateRList(L,n);
	p = L->next;
	int temp = 0,cnt = 0,t = 0;//cnt猴子取得的篮里的桃
	//t篮里桃

	while (ListLength(p) > 1){
		if (t) cnt = t;
		else {//重新分发桃
			cnt = temp % k + 1;
			temp++;
		}
		t = 0;//拿走桃子

		if (p->data + cnt < m){
			p->data += cnt;
			p = p->next;
		}
		else {
			t = p->data + cnt - m;
			p->data = m;
			LinkList q = p;
			p = DeleteRList(q,m);

		}
	}
	return p->num;
}

int main(){
	int n,k,m;
	cin>>n>>k>>m;
	printf("num:%d出列\n",Peaches(n,k,m));
	return 0;
}
最简单的队列

利用数组,再加上设置两个指针head,tail构成其基本元素。

出队:head++;

入队:q[tail]=x;tail++;

c 复制代码
//封装好的简易queue
struct queue
{
	int data[100];
	int head;
	int tail;
}
struct queue q;
void init_q(queue &q){
	q.head=1;
	q.tail=1;
}
int isfull_q(queue &q){
	if(q.tail>=99)
		return 1;
	else return 0;
}
int isempty_q(queue &q){
	if(q.head<=1)
		return 1;
	else return 0;
}
int insert_q(queue &q,int num)
{
	if(isfull_q(q)!=1)
	{	q.data[q.tail]=num;
		q.tail++;
	}
}
int qout_q(queue &q,int &n)
{
	if(isempty_q(q)!=1)
	{
		n=q.data[q.head];
		q.head++;
	}
}

/*c++ stl库中的queue实现
C++ STL中给出的stack和queue类的声明为:
由stack和queue的声明不难发现,它们本质上并不是容器,而是容器适配器,stack和queue的底层都是调用名为deque(双端队列)的容器的接口来实现的。
所谓适配器,通俗讲就是一种设计模式,即:一套被反复使用的、为多数人所知晓的、经过分类编目的代码设计经验的总结,设计模式可以将一个类接口转换为用户希望的其它类接口。

template <class T, class Container = deque<T>>  class stack;
template <class T, class Container = deque<T>>  class queue;
双端队列

deque是双端队列容器,它可以同时支持O(1)时间复杂度下的头部的插入删除和在尾部的插入和删除,但无法在O(1)时间复杂度的下实现在中间某个位置插入和删除数据。可以通过下标来访问任意位置处的元素。deque底层空间连续,不需要频繁地申请和释放小块的内存空间,申请和释放空间的消耗相对于list较低,不需要存储每个数据时都存储两个指针来指向前一个和后一个数据的位置。deque扩容时不需要复制数据,而且deque一次开辟一小块空间,空间利用率高。不适合遍历和随机访问,因为deuqe在进行遍历时,迭代器需要频繁地去检查是否移动到了某一小段空间的边界位置,效率低下。

对于vector,它可以支持O(1)时间复杂度下的尾插数据和尾删数据,但要实现头插和头删则需要O(N)的时间复杂度,vector头插和头删数据操作需要移动后面的所有数据,vector在扩容时需用复制已有数据到新的内存空间,并且每次扩容得到新空间的容量一般是原来容量的1.5倍或2倍,vector扩容时复制数据需要一定的消耗且空间浪费相对于deque较多。

对于list,它可以实现在O(1)时间复杂度下任意位置的插入和删除数据,但不能支持通过下标来进行随机访问。

所以,在某种程度上,我们可以认为deque兼具了vector和list的一些优点。

如果没有deuqe容器,那么,stack只能使用vector或list来实现,queue只能使用list来实现。

相对于vector实现stack:deque的扩容代价低,扩容不用拷贝数据,空间浪费较少。

相对于list实现stack:deque不用频繁申请和释放小块内存空间,CPU高速缓存命中率高,申请和释放内存空间的次数少。

相对于list实现queue:deque不用频繁申请和释放小块内存空间,CPU高速缓存命中率高,申请和释放内存空间的次数少。


版权声明:本文为CSDN博主「【Shine】光芒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_43908419/article/details/129871199

数组和广义表

打印转置矩阵(三元组的运算)

vector or list

一、vector结构上的优点:

由于底层结构是一块连续的空间,所以可以支持下标''[]''随机访问的形式。

cpu高速缓存命中率高。

尾插尾删除效率高,相比list

二、vector结构上的缺点:

越远离头部位置(除了尾部之外)的插入、删除效率低。

扩容有消耗,存在一定的空间的浪费,无论是手动扩还是自动扩,一般来说扩二倍比较合适。

三、list结构上的优点:

任何位置的插入删除的效率比较高,相比list。

按需申请释放节点,空间利用率高,不存在扩容操作。

四、list结构上的缺点:

不支持下标随机访问。

cpu高速缓存命中率低。

五、使用场景:

若存在大量的中间插入删除,考虑用list;否则用vector。list与vector互补配合。

vector

vector容器的功能和数组非常相似,使用时可以把它看成一个数组

vector和普通数组的区别:

1.数组是静态的,长度不可改变,而vector可以动态扩展,增加长度

2.数组内数据通常存储在栈上,而vector中数据存储在堆上

动态扩展:(这个概念很重要)

动态扩展并不是在原空间之后续接新空间,而是找到比原来更大的内存空间,将原数据拷贝到新空间,释放原空间

注意:使用vector之前必须包含头文件 #include < vector>

cpp 复制代码
1.构造
void TestVector()
{
    vector<int> v1;  //无参构造
    vector<int> v2(3, 0); //用3个0去初始化
    vector<int> v3(v1); //拷贝构造
    vector<int> v4(v1.begin(), v1.end()); //迭代器区间构造
}
2.判空、查询/修改大小,容量,清除
void text03()
{
	vector<int> v1;
	cout << v1.empty() << endl;
    cout << v1.size() << endl; //17
    cout << v1.capacity() << endl;//不同编译编译器的扩容机制不一样,因此空间的大小不一定
    v1.clear(); //调用clear
	//重新指定容器大小使其变长
	v1.resize(10);       //调用4,增加的长度默认值为0(不指定情况下默认为0)
	v1.resize(15, 9);    //调用5,增加的长度赋值为9
	//重新指定容器大小使其变短
	v1.resize(10);       //调用4,删除了上一步中最后赋值为9的5个长度
	v1.resize(5);     //调用5,删除了上一步中默认值为0的5个长度

}

3.赋值、插入、删除
void text02()
{
	vector<int> v1,v2;
	for (int i = 0; i < 5; ++i)
	{
		v1.push_back(i);
	}
	v2 = v1;                        //调用1,赋值运算符重载
	vector<int> v3,v4;
	v3.assign(v1.begin(), v1.end());//调用2,区间赋值
	v4.assign(5, 9);                //调用3,放入5个9
}
void text04()
{
	vector<int> v1;
	v1.push_back(6);//调用1,尾部插入元素6
	v1.pop_back();//调用2,删除最后一个元素
	v1.insert(v1.begin(),20);//调用3,在首位插入20
	v1.insert(v1.end(), 3, 20);//调用4,在尾部插入3个20
	v1.erase(v1.begin()); //调用5,在首位删除一个元素
	v1.erase(v1.begin(),v1.end()); //调用6,删除首位到末尾所有元素,也就是删除全部元素
	v1.clear();//调用7,清空所有元素

	v1.swap(v2);   //调用互换函数,容器v1,v2互换
}
4.查询数据
void text05()
{
	for (int i = 0; i < v.size(); ++i)
	{
		cout << v.at(i) << " ";//调用1
	}

	//利用[]访问v
	for (int i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";//调用2
	}
	
	cout << "容器中第一个元素是:" << v.front() << endl;//调用3
	cout << "容器中最后一个元素是:" << v.back() << endl;//调用4
}

5.迭代器操作
void TestVector()
{
    vector<int> v1(10, 1);
    vector<int>::iterator vt = v1.begin();//rbegin,rend反向起止迭代器
    while (vt != v1.end())
    {
        cout << *vt << " ";
        ++vt;
    }
}

树和二叉树

层次遍历

求叶子结点数

邻接表

十字链表

遍历图

查找

二叉树查找/排序/删除

折半查找的递归算法

template模版

在没有泛型编程时,只能使用函数重载逐个写,每次都要摘过来一段代码。

重载的函数仅仅是参数列表中的类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数代码的可维护性比较低,一个出错可能所有的重载均出错。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础模板,分为函数模板和类模板。函数模板只是一个蓝图,在实例化时,根据推导的参数的类型来生成特定的版本。

template<typename T1, typename T2..., typename Tn>typename是用来定义模板参数的关键字,也可以使用class(切记:不能使用struct代替class)

队列结构存在于两种算法

广度优先搜索

贝尔曼福特算法(Bellman-Ford)

典型最短路径算法,用于计算一个节点到其他节点的最短路径。(Dijkstra算法也是)

基本原理:逐遍的对图中每一个边去迭代计算起始点到其余各点的最短路径,执行N-1遍,最终得到起始点到其余各点的最短路径。(N为连通图结点数)

名词解释:

  1. 松弛操作:不断更新最短路径和前驱结点的操作。
  2. 负权回路:绕一圈绕回来发现到自己的距离从0变成了负数,到各结点的距离无限制的降低,停不下来。

与迪杰斯特拉算法的区别:

  1. 迪杰斯特拉算法是借助贪心思想,每次选取一个未处理的最近的结点,去对与他相连接的边进行松弛操作;贝尔曼福特算法是直接对所有边进行N-1遍松弛操作。

  2. 迪杰斯特拉算法要求边的权值不能是负数;贝尔曼福特算法边的权值可以为负数,并可检测负权回路。


版权声明:本文为CSDN博主「lzh1366」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_40347399/article/details/119879750

python 复制代码
//python代码乱入 转自上述博主
 #定义邻接矩阵 记录各城市之间的距离
    weight = [[INF if j!=i else 0 for j in range(N)] for i in range(N)]
 #判断负权回路    
    for i in range(N):
        for j in range(N):
            if dist[j] > dist[i] + weight[j][i]:
                raise ValueError("存在负权回路")
                
    #松弛n-1次,因为最短路径的深度最多是n-1,n为结点数目
    for i in range(N-1):
        change = False
        #分别遍历边的两个顶点,从而实现遍历所有边。
        for j in range(N):
            for k in range(N):
                if dist[j] > dist[k] + weight[j][k]:
                    dist[j] = dist[k] + weight[j][k]
                    #记录更新该结点的结点编号
                    last_update[j] = k
                    #标记更改状态
                    change = True
                    
        #如果本轮未作出更改,说明已完成
        if not change:
            break

最简单的优化

如果未发生更改,则跳出循环。这个最简单的优化运用广泛,见冒泡排序/Bellman-Ford算法等。

输入or输出的艺术

%d,%05d,%-5d,%.5d的区分

%d是普通的输出

%5d是将数字按宽度为5,采用右对齐方式输出,若数据位数不到5位,则左边补空格

%-5d就是左对齐

%05d,和%5d差不多,只不过左边补0

%.5d从执行效果来看,和%05d一样

%d %ld %lld

%d 输出的是 int,

%ld 输出的是 long,

%lld 输出的是 long long;

gets/puts/fgets

gets用来输入一行字符串(注意:gets识别换行符\'0'作为输入结束,因此 scanf完一个整数后,如果要使用gets,需要先用 getchar接收整数后的换行符),并将其存放于一维数组(或二维数组的一维)中;或者删去末尾的'0'

puts用来输出一行字符串,即将一维数组(或二维数组的一维)在界面上输出,并紧跟一个换行。

虽然用 gets() 时有空格也可以直接输入,但是 gets() 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。

fgets() 虽然比 gets() 安全,但安全是要付出代价的,代价就是它的使用比 gets() 要麻烦一点,有三个参数。它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。# include <stdio.h>

char *fgets(char *s, int size, FILE *stream);

其中:s 代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。size 代表的是读取字符串的长度。stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取,这个在后面讲文件的时候再详细介绍。标准输入流就是前面讲的输入缓冲区。所以如果是从键盘读取数据的话就是从输入缓冲区中读取数据,即从标准输入流 stdin 中读取数据,所以第三个参数为 stdin。

不用数字符长度!fget() 函数中的 size 如果小于字符串的长度,那么字符串将会被截取;如果 size 大于字符串的长度则多余的部分系统会自动用 '\0' 填充。所以假如你定义的字符数组长度为 n,那么 fgets() 中的 size 就指定为 n--1,留一个给 '\0' 就行了。

cin、cin.get()、cin.getline()、getline()

根据cin>>sth 中sth的变量类型读取数据,这里变量类型可以为int,float,char,char*,string等诸多类型。这一输入操作,在遇到结束符(Space、Tab、Enter)就结束,且对于结束符,并不保存到变量中。注意:最后一个enter也在缓冲区。

cin.get(字符数组名,接收长度,结束符)

其中结束符意味着遇到该符号结束字符串读取,默认为enter,读取的字符个数最多为(长度 - 1),因为最后一个为'\0'。要注意的是,cin.get(字符数组名,接收长度,结束符)操作遇到结束符停止读取,但并不会将结束符从缓冲区丢弃。

输入字符后,其结束符(如默认的Enter)会保留在缓冲区中,当下次读入时,又会再读入,此时就可以用到cin.get()读掉输入缓冲区不需要的字符

cin.getline(字符数组名,接收长度,结束符)

其用法与cin.get(字符数组名,接收长度,结束符)极为类似。cin.get()当输入的字符串超长时,不会引起cin函数的错误,后面若有cin操作,会继续执行,只是直接从缓冲区中取数据。但是cin.getline()当输入超长时,会引起cin函数的错误,后面的cin操作将不再执行。

cin.get()每次读取一整行并把由Enter键生成的换行符留在输入队列中,需要用cin.get(ch);读掉它。然而cin.getline()每次读取一整行并把由Enter键生成的换行符抛弃。

getline(istream is,string str,结束符)

同样,此处结束符为可选参数(默认依然为enter)。然而,getline()与前面的诸多存在的差别在于,它string库函数下,而非前面的istream流,所有调用前要在前面加入#include< string>。与之对应这一方法读入时第二个参数为string类型,而不再是char*,要注意区别。另外,该方法也不是遇到空白字符(tab, space, enter(当结束符不是默认enter时))就结束输入的,且会丢弃最后一个换行符。

while((c=getchar())!='\n'){ //注意这个是使getchar结束符为换行

练习1 L1-025 正整数A+B

题目: 求两个正整数A和B的和,其中A和B都在区间[1,1000]。稍微有点麻烦的是,输入并不保证是两个正整数。

输入格式:

输入在一行给出A和B,其间以空格分开。问题是A和B不一定是满足要求的正整数,有时候可能是超出范围的数字、负数、带小数点的实数、甚至是一堆乱码。

注意:我们把输入中出现的第1个空格认为是A和B的分隔。题目保证至少存在一个空格,并且B不是一个空字符串。

输出格式:

如果输入的确是两个正整数,则按格式A + B = 和输出。如果某个输入不合要求,则在相应位置输出?,显然此时和也是?。

输入样例1:

123 456

输出样例1:

123 + 456 = 579

输入样例2:

  1. 18

输出样例2:

? + 18 = ?

输入样例3:

-100 blabla bla...33

输出样例3:

? + ? = ?

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<algorithm>
#include<cmath>
using namespace std;

int f(string a){
	for(int i=0;i<a.length();i++)
	{
		if(a[i]<48||a[i]>57)return 0;
	}
	return 1;
}
int s(string a){
	int sum=0;
	int k=1;
	for(int j=a.length()-1;j>=0;j--)
	{
		int xs=0;
		xs+=(int)a[j];
		xs-=48;
		xs*=k;
		sum+=xs;
		k*=10;
	}
	return sum;
}
int main()
{
  string a,b;
  a="";
  int absum=0;
  char c;
  c=getchar();
  while(c!=' '&&c!='\n'){
  	a+=c;
  	c=getchar();
  }
  
  getline(cin,b);
  if(f(a)==0)a="?";
  if(f(b)==0)b="?";
  if(s(a)>1000||s(a)<1) a="?";
  if(s(b)>1000||s(b)<1) b="?";
  
  if(a!="?"&&b!="?"){
  
  	absum=s(a)+s(b);
  	cout<<a<<" + "<<b<<" = ";
  	cout<<absum<<endl;
  	
  }
  else 	{
  		cout<<a<<" + "<<b<<" = ?";
  		
  }
    return 0;
}
练习2 L1-032 Left-pad
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main() {
	int n; char a; string s;
	cin>>n>>a;
	getchar();//读掉回车
	getline(cin,s);//读字符串
	if(n>=s.length()) {
		for(int i=0;i<n-s.length();i++)
		cout<<a;
		cout<<s;	
	}
	else
		for(int i=s.length()-n;i<s.length();i++) cout<<s[i];		
return 0 ;}

ascii码

练习:输入一个字符串,求它包含多少个单词。单词间以一个或者多个空格分开。第一个单词前,最后一个单词后也可能有0到多个空格。比如:" abc xyz" 包含两个单词,"ab c xyz " 包含3个单词。

cpp 复制代码
int get_word_num(char* buf)
{
	int n = 0;   
	int tag = 1; 
	char* p = buf;
	// *p==0空 *p==13回车 *p==10换行
	for(;*p!=0 && *p!=13 && *p!=10;p++){
		if(*p==' ' && tag==0) tag=1;
		if( *p!=' ' && tag==1  ) { n++; tag=0; }  
		 //当前一位是空格,但后一位不是空格时就代表一个单词出现,n++。 
	}
	
	return n;
}

int main()
{
	char buf[1000];
	fgets(buf,1000,stdin);
	
	printf("%d\n", get_word_num(buf));
	return 0;
}

打印形状

打印沙漏
cpp 复制代码
***** 第一层 空格数0 
 ***  第二次 空格数1
  *   第三层 空格数2
 *** 
*****
给定任意N个符号,不一定能正好组成一个沙漏。要求打印出的沙漏能用掉尽可能多的符号。
1.首先确定最多用多少个符号?
2n^2-1 n逐渐递增,大于N时跳出,注意保存n,它是三角形的层数。
2.打印倒三角 倒三角层数是倒着排的,第三层,第二层,第一层。第n层符号数有2n-1个。当层数小于n层,假设为a层时需要先输出n-a个空格,再输出符号;行末尾的空格不用管,直接回车。
3.打印正三角 同理

二项式系数&杨辉三角

二项式的系数规律其排列规律:

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

1 5 10 10 5 1

1 6 15 20 15 6 1

1 7 21 35 35 21 7 1

要求建立N行的杨辉三角形。

cpp 复制代码
#define N 8
int main()
{
	int a[N][N];
	int i,j;
	
	for(i=0; i<N; i++){
		a[i][0] = 1;
		a[i][i] = 1;
	}
	
	for(i=1; i<N; i++){
		for(j=1; j<i; j++)  a[i][j]=a[i-1][j-1]+a[i-1][j];
	}
	
	for(i=0; i<N; i++){
		for(j=0; j<=i; j++)	printf("%-5d", a[i][j]);
		printf("\n");
	}
	
	return 0;
}

排序

直接插入排序

折半直找排序

快排序

选择排序

归并排序

希尔排序

各种数字运算

int的取值范围为: -2^31------2^31-1,即-2147483648------2147483647 是一个十位数

勾股定理

已知直角三角形的斜边是某个整数,并且要求另外两条边也必须是整数。

求满足这个条件的不同直角三角形的个数。

【数据格式】

输入一个整数 n (0<n<10000000) 表示直角三角形斜边的长度。

要求输出一个整数,表示满足条件的直角三角形个数。

例如,输入:

5

程序应该输出:

1

再例如,输入:

100

程序应该输出:

2

再例如,输入:

3

程序应该输出:

0

要注意的是平方之后容易超出int的范围,用上longlong,最好不要用double,double在判等的时候精度问题容易错。暴力的时候穷举到 c/sqrt(2)就行了,也是一个优化点。

cpp 复制代码
#include<stdio.h>
#include<math.h>
int main()
{
	long long aa,bb,cc;
	int a,b,c,count=0;
	scanf("%d",&c);
	cc = c*c;
	for(a=1;a<c/sqrt(2);a++)
	{
		aa = a*a;
		b = sqrt(cc-aa);//b等于斜边的平方-另一边的平方再开方
		if(b*b+aa==cc)
		{
			count++;//加1 
		} 
	}
	printf("%d\n",count);
	return 0; 
} 

有如下的加法算式。其中每个汉字代表一个数字,注意对齐。求"让我怎能过大年 "所代表的整数。所有数字连在一起,中间不要空格。

cpp 复制代码
               年
             大年
           过大年
         能过大年
       怎能过大年
     我怎能过大年
+  让我怎能过大年
------------------
   能能能能能能能

暴力? 套7层循环,复杂度太高。

膜运算

cpp 复制代码
#include<stdio.h>
int main()
{
	int sum;
	int i;
	int temp;
	int a,b,c,d,e,f,g;//代表每一位上的数字 
	for(i=9992299;i>=1000000;i--)
	{
		a = i%10;//个位
		b = i/10%10;//十位 
		c = i/100%10;//百位 
		d = i/1000%10; //千位
		e = i/10000%10;//万位
		f = i/100000%10;//十万位
		g = i/1000000%10;//百万位 
		
		//printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n",a,b,c,d,e,f,g);
		//break; 
		
		temp=d*1000000+d*100000+d*10000+d*1000+d*100+d*10+d*1; 
		//printf("%d\n",temp);
	    //break;
	    
		a=0+a*1;
		b=a+b*10;
		c=b+c*100;
		d=c+d*1000;
		e=d+e*10000; 
		f=e+f*100000;
		g=f+g*1000000;  //也可以直接g=i; 
		
		//printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n",a,b,c,d,e,f,g);
		//break; 
		
		sum=a+b+c+d+e+f+g;
		if(sum==temp)
		{
			printf("%d\n",i);
			break;
		}	
	}
	return 0;
}

连续因子

一个正整数 N 的因子中可能存在若干连续的数字。例如 630 可以分解为 3×5×6×7,其中 5、6、7 就是 3 个连续的数字。给定任一正整数 N,要求编写程序求出最长连续因子的个数,并输出最小的连续因子序列。

输入格式:

输入在一行中给出一个正整数 N(1<N<2​31​​)。

输出格式:

首先在第 1 行输出最长连续因子的个数;然后在第 2 行中按 因子1因子2......*因子k 的格式输出最小的连续因子序列,其中因子按递增顺序输出,1 不算在内。

输入样例:

630

输出样例:

3

567

思想:从1乘到N,每乘一次判定当前乘积prd是否为N的因子,即N%prd是否为0,若为0,比较上一次乘积因子序列的长度,若大于,则记录。

不断更新乘积因子序列长度,直到最后一个因子为N。当然,最需要注意的是最外层的循环变量i判定条件必须为i<=sqrt(n),如果是i*i<=n,则最后一个测试点会不过。本人猜测可能是平台的问题。虽然看上去很暴力,但此算法最后一个测试点耗时仅5ms。

最小的连续因子序列也就是说让start起始因子尽可能大。

cpp 复制代码
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
int main()
{
	ll n;
	cin>>n;
	ll prd=0;//定义乘积 
	int start=0,len=0;//定义最终得到序列开始的因子,序列的长度 
	for(int i=2;i<=sqrt(n);i++)//i从2到根号n 
	{
		prd=1;
		for(int j=i;prd*j<=n;j++)//从j开始一直乘到N为止,每次乘积判定是否小于等于N,若超过N,则结束循环 
		{
			prd*=j;//乘积迭代 
			if(n%prd==0&&j-i+1>len)//如果当前乘积为N的乘积因子且长度大于上一次 
			{//更新序列起始因子和长度 
				start=i;
				len=j-i+1;
			}
		}
	}
	if(start==0)//若起始因子为0,说明N为质数,因为质数=1*本身,而循环最多能表示1*本身的根号 
	{
		start=n;
		len=1;
	}
	cout<<len<<'\n'<<start;
	for(int i=start+1;i<start+len;i++)//输出,如果因子只有一个只输出一个 
		cout<<'*'<<i;
	return 0;
}

最简单的gcd(最大公约数)

cpp 复制代码
LL gcd(LL a,LL b)///求最大公约数  
{  
    return a%b==0?b:gcd(b,a%b);  //求出a和b的最大公约数,并返回 
}  

n个分数求和

本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数分子/分母的形式给出的,你输出的和也必须是有理数的形式。

输入格式:

输入第一行给出一个正整数N(≤100)。随后一行按格式a1/b1 a2/b2 ...给出N个有理数。题目保证所有分子和分母都在长整型范围内。另外,负数的符号一定出现在分子前面。

输出格式:

输出上述数字和的最简形式 ------ 即将结果写成整数部分 分数部分,其中分数部分写成分子/分母,要求分子小于分母,且它们没有公因子。如果结果的整数部分为0,则只输出分数部分。

输入1:

5

2/5 4/15 1/30 -2/60 8/3

输出:

3 1/3

cpp 复制代码
#include<cstdio>  
#include<cstring>  
#include<cmath>  
#include<algorithm>  
#define LL long long  //给long long类型起一个别名 LL 
using namespace std;  

LL gcd(LL a,LL b)///求最大公约数  
{  
    return a%b==0?b:gcd(b,a%b);  //求出a和b的最大公约数,并返回 
}  

int main()  
{  
    LL t,a[101],b[101],s1=0,s2=0;  
    scanf("%lld",&t);  //t:输入你要计算的是多少个分数求和 
    for(int i=0; i<t; i++)  
        scanf("%lld/%lld",&a[i],&b[i]);  //依次输入要计算的分数  3/8 1/4 1/16
    s2=b[0];  //第一个分数的分母,8 
    for(int i=1; i<t; i++)  
        s2=s2/gcd(s2,b[i])*b[i];///求出所有分数的公分母,即例如 3/8  1/4  gcd反回4   公分母就是(8*4)/4
        //s2是所有分数的公分母 
    for(int i=0; i<t; i++)  
        s1+=s2/b[i]*a[i];///求分子总和 例如:公分母16 ,(16/8)*3 就是6是第一个分数的分子  (公分母)/分母*分子 
        //是s1是总的分子 
    LL n=s1/s2,m=abs(s1%s2);  //n就是结果的整数部分, m是余数部分
    if(m==0) printf("%lld\n",n);  //结果为整数,没有小数部分,直接输出n 
    else  
    {  
        if(n!=0)printf("%lld ",n);  //n不为0,有整数部分,输出整数部分 
        if(s1<0&&n==0)printf("-");  //如果n等于0,且分子是负数,结果为一个负的真分数,输出负号 
        printf("%lld/%lld\n",m/gcd(s2,m),s2/gcd(s2,m));  //输出分数,例如m是6  6和18的最大公约数是6  6/6=1 18/6=3  即结果1/3 
    }  
    return 0;  
}

最简单的循环移位

cpp 复制代码
int fun(int n)//一次循环移位
{
	int a = n%10;//取出最后一位  比如12345得出a=5,同理%100取出倒数第二位
	int b = n/10;//取出前面4位                b=1234
	return a*10000+b; //经典的循环返回, 5*10000+1234=51234
} 

//封装好的多次循环移位
void roll(int n,int cnt)//n要循环移位的数,cnt循环移动几次
{
    int n_=n;
    int a,b,sum;
    int i=0;
    while(i<cnt)
    {
        a = n_%10;//取出最后一位  比如12345得出a=5,同理%100取出倒数第二位
        b = n_/10;//取出前面4位   b=1234
        sum=a*10000+b;
	    std::cout<<sum<<std::endl;
        n_=sum;
        i++;
    }
}

练习题 1193是个素数,对它循环移位后发现:1931,9311,3119也都是素数,这样特征的数叫:循环素数。你能找出具有这样特征的5位数的循环素数吗?当然,这样的数字可能有很多,请写出其中最大的一个。注意:答案是个5位数,不要填写任何多余的内容。

我的思路:99999 ~ 11111的n的降序循环 里面套一层五次循环移位的循环 顺便判断一个函数即判断是否是素数( 模2 ~ 根号n的循环 如果都模不等于0则返回它 )然后程序结束

6位小数double "%6lf"

如果x的x次幂结果为10(参见【图1.png】),你能计算出x的近似值吗?显然,这个值是介于2和3之间的一个数字。请把x的值计算到小数后6位(四舍五入),并填写这个小数值。

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<algorithm>
#include<cmath>
using namespace std;

int main()
{
	double a=2.0;
	for(;a<3;a+=0.0000001)//要比要求的6位小数多出一位才能四舍五入
	{
		if(fabs(pow(a,a)-10.0)<0.000001)
		break;
	}
	printf("%6lf",a); //lf是double格式 
    return 0;
}

单位分数之和

埃及分数(贪心法)

埃及分数:分子是1的分数,也叫单位分数。古代埃及人在进行分数运算时,只使用分子是1的分数。如:8/11=1/2+1/5+1/55+1/110。

真分数:分子比分母小的分数,叫做真分数。真分数的分数值小于1。如1/2,3/5,8/9等。输入一个真分数,请将该分数分解为埃及分数。一个真分数的埃及分数表示并不唯一。

贪心法,则是要让一个真分数被埃及分数表示的个数最少,每次选择不大于真分数的最大埃及分数。

假设需要求解真分数 A / B (A 与 B 不可约),

那么假设 B = A * C + D, B / A = C + D / A < C + 1,A / B > 1 / (C + 1);

按照贪心的思想,1 / (C + 1) 为 A / B 分解中最大的那个分子为 1 的真分数。

假设 E = (C + 1),那么相减以后得到 A / B - 1 / E = (A * E - B ) / B * E,

那么得到新的A = A * E - B,B = B * E,然后对新的 A / B 进行约分,保证下一轮的 A 与 B 不可约。

如此循环,当 A = 1 是表明结束循环,得到答案。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
void  EgyptFraction(int A,int B){
	
 	cout << A << '/' << B << '=';
 	
    int E,R;
    while(A != 1){
        E = B / A + 1;  //B / A = C.
        cout << "1/" << E << '+';
        A = A * E - B;
        B = B * E;
        R = __gcd(A,B);
        if(R > 1){
            A /= R;
            B /= R;
        }
    }
    cout << "1/" << B;//A 是 1 了直接输出 1 / B 即可,此时结束分解。
}
 
int main(){
	
	int A,B;
	cin >> A >> B;
	
	EgyptFraction(A,B);
	
	return 0;
}
进阶

形如:1/a 的分数称为单位分数。

可以把1分解为若干个互不相同的单位分数之和。

例如:

1 = 1/2 + 1/3 + 1/9 + 1/18

1 = 1/2 + 1/3 + 1/10 + 1/15

1 = 1/3 + 1/5 + 1/7 + 1/9 + 1/11 + 1/15 + 1/35 + 1/45 + 1/231

等等,类似这样的分解无穷无尽。

我们增加一个约束条件:最大的分母必须不超过30

请你求出分解为n项时的所有不同分解法。

数据格式要求:

输入一个整数n,表示要分解为n项(n<12)

输出分解后的单位分数项,中间用一个空格分开。

每种分解法占用一行,行间的顺序按照分母从小到大排序。

例如,

输入:

4

程序应该输出:

1/2 1/3 1/8 1/24

1/2 1/3 1/9 1/18

1/2 1/3 1/10 1/15

1/2 1/4 1/5 1/20

1/2 1/4 1/6 1/12

再例如,

输入:

5

程序应该输出:

1/2 1/3 1/12 1/21 1/28

1/2 1/4 1/6 1/21 1/28

1/2 1/4 1/7 1/14 1/28

1/2 1/4 1/8 1/12 1/24

1/2 1/4 1/9 1/12 1/18

1/2 1/4 1/10 1/12 1/15

1/2 1/5 1/6 1/12 1/20

1/3 1/4 1/5 1/6 1/20

资源约定:

峰值内存消耗 < 256M

CPU消耗 < 2000ms

cpp 复制代码
先研究一下数学原理
1=30/30
这个分母就是30,分子也是30,将分子的30做分解就可以了。列出1-29之内相加等于30的各种可能,然后将这些数和分母约分就可以了
比如:30=2+3+25
则1=(2+3+25)/30
1=1/15+1/10+5/6
加了限制项n 时,就更加好办了,就是求n个数相加等于30

等式可以认为是(a1+a2+...am)/K,很好理解,a1,...am必然是K的所有约数的和的组合。因此最后题目就是转化为求一个数K的所有约数,题目K最大才30,之后求出所有约数中,n个数的和等于K的组合。

比如:K=30,30=2*3*5,根据"约数个数定理",约数有8个,除去自己本身和1就剩6个,为2,3,5,6,10,15,
如果你输入n=4,那么就是求这6个约数中,哪4个相加正好等于30的所有组合,根据组合原理,C(7,4)才210种组合,因此循环不会很久。

题目没规定K值,因此,需要K从1到30循环,对每一次循环的K,找出所有所有约数,并对所有约数个数不小于n的情况,循环找出所有n个约数等于K的组合。
拆分步骤:
1、约数个数定理
2、求一个数的所有约数
3、从N个数中任选M个数相加的和等于K(循环应该就能解决)

第二种方法:dfs

dfs需要具备的基本元素:1.初值 2.什么时候跳出 3.迭代方式(搜索空间要包括全部可探索空间)

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;
 
int n,ans[15];
const double eps=1e-9;
 
void dfs(double sum,int cnt,double now)//sum 分数加和,cnt当前几个分数相加,now最大分母的值
{
    if(cnt==n){
        if(abs(sum-1)<eps){
            for(int i=0;i<n;i++)
                cout<<1<<'/'<<ans[i]<<' ';//ans数组存储依次相加分数的分母
            cout<<endl;
        }
        return ;
    }
    if(sum>=1||cnt>n||now>30)
        return ;
    dfs(sum,cnt,now+1);
    ans[cnt]=now;
    dfs(sum+1/now,cnt+1,now+1);
    return ;
}
 
int main()
{
    cin>>n;
    dfs(0,0,1);
    return 0;
}

dfs or 回溯法

DFS 英文名,Depth First Search,中文名 深度优先搜索,是图的一种搜索算法,每一个可能的分支路径深入到不能再深入为止,且每个节点只能访问一次。

深度优先搜索算法跟图结构紧密相关,任何涉及深度度优先搜索的问题,都伴随着图。深度度优先搜索的能够在图结构里搜索到通往特定终点的一条或者多条特定路径。

回溯算法是系统地搜索问题的解的方法。某个问题的所有可能解的称为问题的解空间,若解空间是有限的,则可将解空间映射成树结构。任何解空间可以映射成树结构的问题,都可以使用回溯法。回溯法是能够在树结构里搜索到通往特定终点的一条或者多条特定路径。

回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试,从而搜索到抵达特定终点的一条或者多条特定路径。

值得注意,回溯法以深度优先搜索的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

不能简单的说:回溯算法 = 深度优先搜索 + 剪枝函数。因为并不是所有图都是树。深度优先搜索适用于所有图。而回溯算法只适用于树结构。

练习1 数独游戏

玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。

数独的答案都是唯一的,所以,多个解也称为无解。

本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。

本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。

格式要求,输入9行,每行9个字符,0代表未知,其它数字为已知。

输出9行,每行9个数字表示数独的解。

例如:

输入(即图中题目):

005300000

800000020

070010500

400005300

010070006

003200080

060500009

004000030

000009700

程序应该输出:

145327698

839654127

672918543

496185372

218473956

753296481

367542819

984761235

521839764

再例如,输入:

800000000

003600000

070090200

050007000

000045700

000100030

001000068

008500010

090000400

程序应该输出:

812753649

943682175

675491283

154237896

369845721

287169534

521974368

438526917

796318452

资源约定:

峰值内存消耗 < 256M

CPU消耗 < 2000ms

cpp 复制代码
#include <stdio.h>
int a[9][9];
 
int place(int x, int y) //二者分别是数组对应的行地址和列地址,取值为0-8
{
	int up, down, left, right;
	int i,j;
 //算出同色格范围
	up=x/3*3;
	down=up+3;
	left=y/3*3;
	right=left+3;
 
	//以下分三种情况判断是否在x,y对应的位置放这个数,如果不可以放,返回0,如果可以放,返回1,会进一步迭代
	
	for(i=0;i<9;i++){
		if(a[x][y]==a[i][y] && i!=x && a[i][y]!=0)//行检查
			return 0;		
	}
 
	for(i=0;i<9;i++){
		if (a[x][y]==a[x][i] && i!=y && a[x][i]!=0)//列检查
			return 0;		
	}
 
	for(i=up;i<down;i++)//同色9宫格的情况
	{
		for(j=left;j<right;j++)
			if(i!=x || j!=y)//不是自己即可
			{
				if(a[i][j]==a[x][y] && a[i][j]!=0)
					return 0;
			}
	}
 
	return 1;	
}
 
 
void backtrack(int t)//第几个格子
{
	int i,j;
	int x,y;
 
	if(t==81)
	{
		
		for(i=0;i<9;i++)
		{
			for(j=0;j<9;j++)
				printf("%d",a[i][j]);		
 
			putchar('\n');
		}
	}
	else
	{
		x=t/9;
		y=t%9; //将这个转换为相应的数组行坐标和列坐标
 
		if(a[x][y]!=0)
		{
			backtrack(t+1);
		}
		else
		{
			for(i=1;i<10;i++)
			{
				a[x][y]=i;
				if(place(x,y)==1)
					backtrack(t+1);//探索在此基础上后续的解空间
				a[x][y]=0;//还要撤回 尝试该点的其余可能
			}
		}
	}
}
 
int main()
{
	char str[9][9];
	int i,j;
 
	for(i=0;i<9;i++)
		gets(str[i]);
 
	for(i=0;i<9;i++)
		for(j=0;j<9;j++)
			a[i][j]=str[i][j]-'0';
	
	backtrack(0);
 
	return 0;
 
}

练习2:排列数字

今有7对数字:两个1,两个2,两个3,...两个7,把它们排成一行。

要求,两个1间有1个其它数字,两个2间有2个其它数字,以此类推,两个7之间有7个其它数字。如下就是一个符合要求的排列:17126425374635当然,如果把它倒过来,也是符合要求的。请你找出另一种符合要求的排列法,并且这个排列法是以74开头的。

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<algorithm>
#include<cmath>
using namespace std;
int arr[16]={0};
int dfs(int n)
{
    if(n > 6) return 1;//因为7距离已经确定,所以最大距离就是6
    if(n == 4) n++;//因为当n=4,两者距离就是4,4的位置已经确定,所以跳过
    int i=3;
    for(;i <=14 ;i++)
    {
        if(i == 7 || i == 9) continue;//下标7,9位置值已经定了
        if(i+n+1 <=14 && arr[i]==0 &&arr[i+n+1]==0)//保证两个位置都没有数据
        {
            arr[i] = arr[i+n+1] = n;
            if(dfs(n+1))//继续在此基础上探索下一个即n+1距离解空间
                return 1;
            arr[i] = arr[i+n+1] = 0;//恢复上一次dfs状态,探索n的新可能
        }
    }
    return 0;
}
int main()
{
    arr[1] = 7,arr[2] = 4;//因为题目上已经给出两个开头,可以推出后面两个
    arr[9] = 7,arr[7] = 4;
    dfs(1);//先确定两个1
    for(int i = 1;i < 16;i++ )
    {
        cout <<arr[i];
    }
    cout <<"\n";

    return 0;
}
相关推荐
ac-er88882 分钟前
PHP弱类型安全问题
开发语言·安全·php
ac-er88883 分钟前
PHP网络爬虫常见的反爬策略
开发语言·爬虫·php
爱吃喵的鲤鱼12 分钟前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
懒惰才能让科技进步34 分钟前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
DARLING Zero two♡39 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
Gu Gu Study41 分钟前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
Ni-Guvara1 小时前
函数对象笔记
c++·算法
芊寻(嵌入式)1 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
泉崎1 小时前
11.7比赛总结
数据结构·算法