2014-7月~2014-10月总结

本文总结了作者在算法学习过程中的收获,详细介绍了排序算法、高精度算法、并查集和最短路算法等核心内容,并分享了学习体会。

从7月份到现在,经过这三个月的训练学习,我的收获其实挺多的,也掌握了不少新的基础的算法,所以今天就在这总结一下:

等等,我先说明一下:动态规划和搜索,这两个最基础但是也是最最重要的算法,我自以为还没有吃透,所以在此就不做总结了,但会在这两周快速消化,并总结出来。

一、我先学习了各种排序算法:包括冒泡排序,选择排序,插入排序,双向冒泡排序, 桶排序,堆排序,希尔排序,快速排序,归并排序;

但是学习之后发现,真正在比赛中考到的其实无非就是快速排序,堆排序,归并排序。堆和归并其实也少,但也会用到,一定要掌握,因为有一些题目会故意卡快排。倒也不是说,冒泡,选择那些学了没用。比如有的题,用插入排序对数据微调整就是要比用快排,归并什么的重排效率高,还有一些简单题,没必要打什么归并的,一个冒泡打出去照样解决,还很简单。而且老话说的好:技多不压身。会了总比不会强很多,而且这也是最基础的,都必须掌握。

接下来,我就来总结一下代码(主要是快排和归并)

1、快排

void q_sort(int a[], int l, int r)
{
     int i = l, j = r, x = a[(l + r) / 2];     
     while (i <= j){
          while (a[i] < x) i ++;
          while (a[j] > x) j --;
          if (i <= j) 
		  {
			 swap(a[i], a[j]);
			 i ++; j --;
		  }      
     }
     if (i < r) q_sort(a,i, r);
     if (l < j) q_sort(a,l, j);
}
快速排序是冒泡排序的一种改进。它的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。快速排序的时间复杂度是O(nlogn),速度快,但它是不稳定的排序算法。就平均时间而言,快速排序时目前被认为是最好的一种内部排序方法。
2、归并排序
void mergesort(int a[], int left, int right)
{
	int mid, i, j, k;
	if (left == right) return;
	mid = (left + right) >> 1;
	mergesort(a, left, mid);
	mergesort(a, mid + 1, right);
	i = left; j = mid + 1; k = left;
	while (i <= mid && j <= right)
	{
		if (a[i] <= a[j]) tmp[k ++] = a[i ++];
		else tmp[k ++] = a[j ++];
    }
    while (i <= mid) tmp[k ++] = a[i ++];
    while (j <= right) tmp[k ++] = a[j ++];
    for (int i = left; i <= right; i ++) a[i] = tmp[i];
}

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。

其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?

可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

3、sort函数

这个函数就是简单版的快排,有了这个函数,就不用再打代码了

#include 
    
     
#include 
     
      
using namespace std;
bool cmp(int a, int b)
{
     return a > b;
}
int main()
{
    int n;
    cin >> n;
    int a[n];
    for (int i = 0; i < n; i ++) cin >> a[i];
    sort(a, a + n); // 这里a为待排序数组,n为数组长度,排出来之后是从小到大的顺序
    for (int i = 0; i < n; i ++) cout << a[i] << ' ';
    cout << endl;
    sort(a, a + n, cmp);//这样救赎从大到小了
    for (int i = 0; i < n; i ++) cout << a[i] << ' ';
    return 0;
}

     
    
二、高精度算法
高精度算法其实很好理解,完全就是一个竖式计算的模拟,所以原理什么的不用多说,直接上代码
首先我们需要将读进来的字符串转化成数组储存
#include 
     
      
#include 
      
       
#include 
       
        
using namespace std;
int main()
{
	char a[1000], b[1000];
	int inta[1000], intb[1000], intc[1000], lena, lenb, lenc, x;
	memset(inta, 0, sizeof(inta));
	memset(intb, 0, sizeof(intb));
	memset(intc, 0, sizeof(intc));
	gets(a); gets(b);
	lena = strlen(a), lenb = strlen(b);
	for (int i = 0; i <= lena - 1; i ++) inta[lena - i] = a[i] - 48;
	for (int i = 0; i <= lenb - 1; i ++) intb[lenb - i] = b[i] - 48;
	return 0;
}

       
      
     
1、加法
    lenc = 1;
	x = 0;
	while (lenc <= lena || lenc <= lenb)
	{
		intc[lenc] = inta[lenc] + intb[lenc] + x;
		x = intc[lenc] / 10;
		intc[lenc] %= 10;
		lenc ++;
    }
    intc[lenc] = x;
    if (intc[lenc] == 0) lenc --;
    for (int i = lenc; i >= 1; i --) printf("%d", intc[i]);
2、减法
    char c[1000];
	if (strlen(a) < strlen(b) || strlen(a) == strlen(b) && strcmp(a, b) < 0)
	{
		strcpy(c, a); strcpy(a, b); strcpy(b, c);
		printf("-");
    }
	lena = strlen(a), lenb = strlen(b);
	for (int i = 0; i <= lena - 1; i ++) inta[lena - i] = a[i] - 48;
	for (int i = 0; i <= lenb - 1; i ++) intb[lenb - i] = b[i] - 48;
	while (lenc <= lena || lenc <= lenb)
	{
		if (inta[lenc] < intb[lenc])
		{
			inta[lenc] += 10;
			inta[lenc + 1] --;
	    }
	    intc[lenc] = inta[lenc] - intb[lenc];
	    lenc ++;
    }
    while (intc[lenc] == 0 && lenc > 1) lenc --;
	for (int i = lenc; i >= 1; i --) printf("%d", intc[i]); 
3、乘法
    for (int i = 1; i <= lena; i ++)
	{
		for (int j = 1; j <= lenb; j ++)
		{
			intc[i + j - 1] = inta[i] * intb[j] + x + intc[i + j - 1];
			x = intc[i + j - 1] / 10;
			intc[i + j - 1] %= 10;
	    }
	    intc[i + lenb] = x;
    }
    lenc = lena + lenb;
    while (intc[lenc] == 0 && lenc > 1) lenc--;
    for (int i = lenc; i >= 1; i --) printf("%d", intc[i]);
4、除法
(1)高精除以低精
   
#include 
      
       
#include 
       
        
#include 
        
         
#include 
         
          
#include 
          
           
#include 
           
             using namespace std; char s[10000]; int a[10000]; int main() { int i, j, k, n, p; cin >> s; cin >> p; strrev(s); n = strlen(s); for (i = 0; i < n; i ++) a[i] = s[i] - 48; for (k = 0, i = n - 1; i >= 0; i --) { k = k * 10 + a[i]; a[i] = k / p ; k %= p; } while (!a[n-1] && n > 0) n --; for (i = n - 1; i >= 0; i --) cout << a[i]; cout << endl; cout << k << endl; system("pause"); return 0; } 
           
          
         
        
       
      
(2)高精除以高精
   
#include 
      
       
using namespace std;
const int n=1000;
char s1[n], s2[n], s3[n], yu[n];
int a[n] = {0}, b[n] = {0}, c[n] = {0}, d[n] = {0};
int la, lb, lc, k = 0, sum = 0;
      
int cmp(int n)
{
    int i;
    int k = 0;
    while (a[k] == 0) k ++;
    k = la - k - lb;
    if (k > 0) return 1;
    if (k < 0) return -1;
    
    for (i = 0;i < lb; i ++){
       if (a[i + n] < b[i]) return -1; 
       if (a[i + n] > b[i]) return 1; 
       }
    return 1;
     
    }
void init()
{
     int i, j, t = 0;
     cin >> s1;
     cin >> s2;
     la = strlen(s1); lb = strlen(s2);
     for (i = 0; i < la; i ++) a[i] = s1[i] - 48;
     for (i = 0; i < lb; i ++) b[i] = s2[i] - 48;
     for (i = 0; i <= la - lb; i ++){
         k = 0;
         do{      
              if (cmp(i) > 0){
                 for (j = lb - 1; j >= 0; j --){
                     c[j] = a[j + i] - b[j];
                     if (c[j] < 0) {c[j] += 10; a[i + j - 1]--;}
                 }k ++;
                 for (j = 0; j <= lb - 1; j ++) {a[j + i] = c[j];}
               }
               else break;
         }while (1);
         memset(c, 0, sizeof(c));
         d[i] = k;
     }     
     return ;
}
 
void out()
{
     int i, j, k = 0;
     while (d[k] == 0) k ++;
     for (i = k; i <= la - lb; i ++) cout << d[i];
     if (la < lb) cout << 0;
     cout << endl;
     n = la - lb;
     while (a[n] == 0 && n > 0) n --;
     for (i = n; i < la; i ++) cout << a[i];
     cout << endl;
     return ;
}
int main()
{
   // freopen("gaochu.in","r",stdin);
  //  freopen("gaochu.out","w",stdout);
    init();
    out();
    system("pause");
    return 0;
} 

      
三、并查集
     

并查集

维护一些不相交的集合,它是一个集合的集合。每个元素恰好属于一个集合,好比每条鱼装在一个鱼缸里。每个集合S有一个元素作为集合代表"rep[S],好比每个鱼缸选出一条"鱼王"。并查集提供三种操作:

MakeSet(x):建立一个新集合x。x应该不在现有的任何一个集合中出现。
Find(S, x):返回x所在集合的代表元素。
Union(x, y):把x所在的集合和y所在的集合合并。

森林表示法

可以用一棵森林表示并查集,森林里的每棵树表示一个集合,树根就是集合的代表元素。一个集合还可以用很多种树表示,只要树中的结点不变,表示的都是同一个集合。

合并操作

只需要将一棵树的根设为另一棵即可。这一步显然是常数的。一个优化是:把小的树合并到大树中,这样会让深度不太大。这个优化称为启发式合并.

查找操作

只需要不断的找父亲,根就是集合代表。一个优化是把沿途上所有结点的父亲改成根。这一步是顺便的,不增加时间复杂度,却使得今后的操作比较快。这个优化称为路径压缩。

代码:

#include 
       
        
#include 
        
         
#include 
         
          
#include 
          
           
#include 
           
            
using namespace std;
    int n, m, x, y, q;
    int fa[1000];
int find(int x)
{
	if (fa[x] == x) return x;
	else return (find(fa[x]));
}
int unionn(int rx, int ry)
{
	fa[ry] = rx;
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		int rx = find(x), ry = find(y);
		if (rx != ry) unionn(rx, ry);
    }
    scanf("%d", &q);
	for (int i = 1; i <= q; i++)
	{
		scanf("%d%d", &x, &y);
		if (find(x) == find(y)) printf("Yes");
		else printf("No");
    } 
    //system("pause");
    return 0;
}

           
          
         
        
       
四、最短路
(1)spfa算法

    SPFA(Shortest Path Faster Algorithm)Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。

算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。

设Dist代表S到I点的当前最短距离,Fa代表S到I的当前最短路径中I点之前的一个点的编号。开始时Dist全部为+∞,只有Dist[S]=0,Fa全部为0。

维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点S。用一个布尔数组记录每个点是否处在队列中。

每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断Dist[v]+len是否小于Dist[u],若小于则改进Dist[u],将Fa[u]记为v,并且由于S到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是S到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环。

改进: 1、第二步,不是枚举所有节点,而是通过队列来进行优化 设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。 2、同时除了通过判断队列是否为空来结束循环,还可以通过下面的方法: 判断有无负环:如果某个点进入队列的次数超过V次则存在负环(SPFA无法处理带负环的图)。

#include 
       
        
#include 
        
         
#include 
         
          
#define maxn 10050
using namespace std;
int n, m;
int u, v, w, top = 0, head[maxn];
struct EDGE
{
	int v, w, next;
}edge[maxn];
void add(int u, int v, int next)
{
	top ++;
	edge[top].v = v;
	edge[top].w = w;
	edge[top].next = head[u];
	head[u] = top;
}
int dis[maxn], queue[maxn], outque[maxn];
int spfa()
{
	memset(dis, -1, sizeof(dis));
	int begin, tail;
	begin = 0; tail = 1;
	queue[begin]  = 1; dis[1] = 0;
	while (begin != tail)
	{
		int x = queue[begin];
		for (int i = head[x]; i != 0; i = edge[i].next)
		{
			if (dis[edge[i].v] == -1 || dis[edge[i].v] > dis[x] + edge[i].w)
			{
				dis[edge[i].v] = dis[x] + edge[i].w;
				if (!outque[edge[x].v])
				{
					outque[edge[i].v] = 1;
					queue[tail] = edge[i].v;
					tail = (tail + 1) % maxn;
			    }
			}
		}
		outque[x] = 0;
		begin = (begin + 1) % maxn;
    }
    return dis[n];
}
					
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i ++)
	{
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
    }
    printf("%d\n", spfa());
    //system("pause");
    return 0;
}
	

         
        
       
(2)Floyd算法

1)算法思想原理:

     Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)

      从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

2).算法描述:

a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   

b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

#include 
       
        
#include 
        
         
#include 
         
          
#define maxn 100
using namespace std;
int n, m;
int a[maxn][maxn];
double dis[maxn][maxn];
int x, y;
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) scanf("%d%d", &a[i][1], &a[i][2]);
	scanf("%d", &m);
	for (int i = 1; i <= m; i ++)
	{
		scanf("%d%d", &x, &y);
		dis[x][y] = dis[y][x] = sqrt(pow(double(a[x][1] - a[y][1]), 2) + pow(double(a[x][2] - a[y][2]), 2));
    }
    scanf("%d%d", &x, &y);
    for (int i = 1; i <= n; i ++)
    for (int j = 1; j <= n; j ++)
    for (int k = 1; k <= n; k ++)
    {
		if ((i != j) && (j != k) && (k != i) && (dis[i][k] + dis[k][j] < dis[i][j]))
		{
			dis[i][j] = dis[i][k] + dis[k][j];
		}
	}
	printf("%.2f", &dis[x][y]);
	system("pause");
	return 0;
}
 

         
        
       

以上就是这段时间真正掌握的,看起来好少的样子,不过没关系,这类题只要出现我差不多就能做对。另外这段时间我还学了搜索,状态压缩DP,树形DP,线段树,树状数组,只是这几类做题太少,或者说并没有完全理解,所以这次总结就没有把他们算在内。接下来两周(就是NOIP之前),在做往年NOIP题的同时,我还会继续深入的学习上面集中算法,吃透弄熟,备战NOIP2014。














 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值