一、dfs(Depth First Search)
深度优先遍历属于图的一种遍历,其过程简要来说的话就是对每一条分支路径深入到不能深入为止
有几个经典的例题,我在前几天就做了一个题目,素数环
题目描述:输入正整数n,把整数1,2,3,.....n组成一个环,使得相邻两个整数之和均为素数。输出时从整数1开始逆时针排列。同一个环应恰好只输出一次。n<=16。
分析一下,它要我们找出所有可能,我们可以发现1可以和2匹配,又可以和4匹配,一个数有很多种选择,如果穷举的话太麻烦了,那我们应该怎么来写呢?
可以发现我上面的那段加粗的话,如果学过数据结构与算法的话,很容易就可以想到这是图的一种数据结构,那这就好办了,它要我们找全部可能,那就是遍历,图的遍历有两种,深度优先遍历和广度优先遍历,这里我们就讲一种,深度优先遍历。
先给代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <math.h>
#include<string.h>
int n;
int path[16];//每一次情况的路径
int flag[16]={0};//记录是否走过
int isPrime(int n)
{
int i, t, f = 1;
t = sqrt(n);
for (i = 2; i <= t; i++)
if (n % i == 0)
{
f = 0;
break;
}
return f;
}//判断素数
int backtrace(int i)
{
int j,a,b;
if (i == n)
{
if (isPrime(path[0] + path[n - 1]))
{
for (int k = 0; k < n; k++)
printf("%d ", path[k]);
printf("\n");
}//搜索到头还需判断是否和第一个加起来也是素数,若是,则记录起来
return;
}//如果你搜索到头了,就没有必要做了
for (a = 2; a <= n; a++)
{
if (flag[a] != 1 && isPrime(path[i - 1] + a))//没有走过,且他们相加为素数
{
flag[a] = 1;
path[i] = a;
backtrace(i + 1);
flag[a] = 0;//回溯,标记为0
path[i] = 0;
}
}
}
void main()
{
path[0] = 1;
int i;
scanf("%d", &n);
backtrace(1);
}
运用了递归的手法来写dfs,跟着程序走一遍就会发现其中的奥妙
接下来我给一份写dfs的模板,以后写的话就可以按照模板来
int search(int t)
{
if(满足输出条件)
{
输出解;
}
else
{
for(int i=1;i<=尝试方法数;i++)
if(满足进一步搜索条件)
{
为进一步搜索所需要的状态打上标记;
search(t+1);
恢复到打标记前的状态;//也就是说的{回溯一步}
}
}
}
二、希尔排序
希尔排序是一个很古老的排序算法了,是1959年D.L.Shell提出来的,在这之前的其他排序全是
O()但是它的排序时间复杂度为O(nlogn),第一批算法,很伟大了。
基本思想:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止
那增量怎么求呢?
这难倒了许多科学家,通过大量的研究的出增量k=最为合适,给上代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<math.h>
#define MAXNUM 10
void shellSort(int array[], int n, int t);//t为排序趟数
void main()
{
int array[MAXNUM], i;
for (i = 0; i < MAXNUM; i++)
scanf("%d", &array[i]);
shellSort(array, MAXNUM, (int)(log(MAXNUM + 1) / log(2)));//排序趟数应为log2(n+1)的整数部分
for (i = 0; i < MAXNUM; i++)
printf("%d ", array[i]);
printf("\n");
}
//根据当前增量进行插入法排序
void shellInsert(int array[], int n, int s)
{
int i, j, temp;
for (i = s; i < n; i++)//分别向每组的有序区域插入
{
temp = array[i];//暂存在temp中
for (j = i - s; (j >= i % s) && array[j] > temp; j -= s)//比较与记录后移同时进行
array[j + s] = array[j];
if (j != i - s)
array[j + s] = temp;//插入
}
}
//计算增量
int step(int t, int k)
{
return (int)(pow(2, t - k + 1) - 1);
}
//希尔排序
void shellSort(int array[], int n, int t)
{
void shellInsert(int array[], int n, int dk);
int i;
for (i = 1; i <= t; i++)
shellInsert(array, n, step(t, i));//在循环中,增量的值一直在改变,直到变成1
}
三、素数筛
先看素数的定义:
求素数的方法有几种
1.朴素法:一种比较简单的算法,就是一个个求,这里就不讲了
2.埃氏筛:在实际问题中我们总是要求一个区间内的素数,如果用上面的朴素法的话时间复杂度就太高了,所有我们就引入了今天学的,埃氏法。
上面的朴素算法,我们是尝试把数字拆分为因子相乘的形式,对范围区间内的数字进行判断时,,则需要对每个数字都进行拆分,那么我们是否可以使用另外一种方式呢?
我们选择另一种方法:反向来。数字可以分为素数和合数两大类,我们是不是把区间内的合数全找出来剩下的就是素数呢?很显然这是对的,就像筛子,把合数筛掉,剩下的就是素数了。
总结成算法就是
2.1.设定两个数组,一个数组包括范围内的所有数字,用来标记此数字是否被访问过;另外一个数组用来存储已经筛选出来的素数
2.2.将初始化访问数组全部为未访问过,素数组清空
2.3.从 2 开始循环,直到范围的上界,判断每一个数字是否被访问过,访问过的是非素数(合数),未访问过的是素数
2.4.接着对这个数字进行倍增操作,从两倍开始,将它的倍数全部筛掉,将它的倍数全部设置为访问过。重复三和四的步骤。
给代码。
int vis[100];//是否被访问过
int prime[100];//存素数
int aishi(int n)
{
int i, j, k;
k = 0;
vis[0] = vis[1] = 1;
for (i = 2; i <= n; i++)//从二开始
{
if (vis[i] == 0)
prime[k++] = i;//前面比它小的数都没有把它筛掉,那么它就是素数
for (j = i + i; j <= n; j += i)//这里j表示i的倍数结果,每次加i相当于加一倍
vis[j] = 1;
}
return k;
}
三、欧拉式筛法
基本思想:埃式筛有一个问题就是同一个数字可能会被多个素数重复筛除
欧拉筛就是要解决这个问题,保证每个合数只被筛选一次,从而提高效率和运行速度,那么怎么做呢?
根据唯一分解定理可知,每个合数都有一个最小素因子。而欧拉筛的基本思想是,让每个合数被其自身的最小素因子筛选,而不会被重复筛选。欧拉筛的框架和埃氏筛大致相同,区别点在于第二层循环对倍增过程的操作
直接给代码。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>//欧拉式筛法
#include<string.h>
#include <stdbool.h>
//欧拉筛的基本思想是,让每个合数被其自身的最小素因子筛选,而不会被重复筛选。
bool vis[1000000]={0};//标记访问数组,默认全没有访问过
int primes[1000000];//存放素数的数组
int main()
{
int n, len;
int i, j, k = 0;
for (i = 2; i <= 1000000; i++)
{
if (vis[i] == 0)//若它为素数
primes[k++] = i;
for (j = 0; primes[j] * i <= 100000; j++)
{
vis[primes[j] * i] = 1;//将素数的倍数删除
if (i % primes[j] == 0) break;
}
}
while (scanf("%d", &n) != EOF)
{
for (int i = 0; i < k; i++)
{
if (primes[i] > n) break;
printf("%d\n", primes[i]);
}
}
}
四、归并排序
这是我今天刚刚学的,周老师的区间问题我要哭了,明白了思想,但是写题目不知道是服务器编译器有问题还是咋,我在自己电脑上试可以,但上交就有问题了。
今天听了个大概,先将题目描述放出来。
周老师无聊时乱写了 n 个区间,但处女座的他随后又想将 n 个区间整理合并,但他发现区间太多了,于是他想请你帮帮他
输入
每次测试输入多组数据(小于100组),对于每组输入数据:
第一行为 n ,代表 n 个区间
接下来 n 行,每行两个数 s , t 代表区间 [s,t]
0 < n < 15000
0 <= s <= t < 10000000
输出
第一行输出一个数字 q ,代表合并后剩余的区间个数
随后 q 行 按从小到大的顺序输出区间
这就是题目描述了,我的思想就是
1.先将各个区间按升序排列,以便与后面进行处理。
2.弄一个结果数组anss和anst,它们分别记录上界和下界
3.我们区间问题有几种情况,全包含,半包含,不包含,接下来给代码进行理解。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
swap(int *a, int *b)
{
int t;
t = *a;
*a = *b;
*b = t;
}
main()
{
int n;
while (scanf("%d", &n) != EOF)
{
int s[15000], t[15000];
int anss[10000], anst[10000];
for (int i = 1; i <= n; i++)
{
scanf("%d %d", s + i, t + i);
}//将区间输入,s代表上区间,t代表下区间
for(int i=0;i<n-1;i++)
for (int j = i; j <= n - 1 - i; j++)
{
if (s[j] > s[j + 1])
{
swap(s + j, s + j + 1);
swap(t + j, t + j + 1);
}
else if (s[j] == s[j + 1])
if (t[j] > t[j + 1])
{
swap(s + j, s + j + 1);
swap(t + j, t + j + 1);
}
}//冒泡法排序
int lenth = 0;
lenth++;
anss[lenth] = s[1];
anst[lenth] = t[1];//记录第一个区间
for (int i = 2; i <= n; i++)
{
if (anss[lenth] <= s[i] && anst[lenth] >= s[i])//半包含的关系
{
if (anst[lenth] >= t[i]) {}//若你整个都在我的区间内,就不要做任何的动作
else { anst[lenth] = t[i]; }
}
else//独立区间的话
{
lenth++;
anss[lenth] = s[i];
anst[lenth] = t[i];
//将你记录下来,这就是先对它进行排序的好处,区间从小到大
}
}
printf("%d\n", lenth);
for (int i = 1; i <= lenth; i++)
{
printf("%d %d\n", anss[i], anst[i]);
}
}
}