一位软科大一的周结(week 1)

一、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(n^{2})但是它的排序时间复杂度为O(nlogn),第一批算法,很伟大了。

基本思想:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止

那增量怎么求呢?

这难倒了许多科学家,通过大量的研究的出增量k=2^{t-k+1}-1最为合适,给上代码

#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的自然数中,除了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]);
		}
	}
}

 

五、冒泡法

六、桶排

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值