从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);
}
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;
}
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]);
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]);
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]);
#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; }
#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;
}
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;
}
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;
}