MOOC数据结构 OJ题记录
很多代码都是参考这位博主的( 18Temp),真的是很简洁到… 后面第二大部分的题目得分析1小时才完全看懂自己能写的…
PA1-1 范围查询 (对应课程:绪论+向量)
描述
数轴上有n个点,对于任一闭区间 [a, b],试计算落在其内的点数。
输入
第一行包括两个整数:点的总数n,查询的次数m。
第二行包含n个数,为各个点的坐标。
以下m行,各包含两个整数:查询区间的左、右边界a和b。
输出
对每次查询,输出落在闭区间[a, b]内点的个数。
首先… 一开始没看OJ不能用库 /笑哭,虽然我没看课 看完C++就直接跳到编程去做了,然后乖乖看课(虽然以前看过一次程序设计基础 但是没有结合代码的,就是大概了解了有些啥方法
整体,贴代码:(PS:我认为如何想在效率进行改进:
- qsort() 是C语言的标准库,使用的是快速排序法,类似于冒泡但是是lo hi的对换,感兴趣可以百度一下,能看源码。 -> 所以引发了这里可以不用库,n大于一定数值采用归并排序(上课讲到过)
- 主要是排序与查找算法都采用最优应该就可以(查找xx
以下为博主的代码(看了看课,看着伪代码 差不多写了)
//#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#define L 500000
int arr[L];
//qsort需要一个比较两个元素的函数
int cmpfunc(const void *a, const void *b)
{
return *(int*)a - *(int*)b;
}
//此处使用二分查找法 C版本
int binsearch(int *a, int lo, int hi,int e)
{
while (lo<hi)
{
int mi = (lo + hi) >> 1;// 左移的除2效率更高
(e < a[mi]) ? hi = mi : lo = mi + 1;
}
return --lo;
}
int main()
{
int n, m;
int a, b;
scanf("%d %d", &n, &m);//比cin的效率高
for (int i = 0; i < n; i++)
{
scanf("%d", arr + i);
}
qsort(arr, n, sizeof(int), cmpfunc);//查询了一下C语言库中的qsort快速排序法,目前最好的内部排序(但是看复杂度似乎没有归并的效率高)
for (int i = 0; i < m; i++)
{
scanf("%d %d", &a, &b);
int nleft = binsearch(arr, 0, n, a);
int nright = binsearch(arr, 0, n, b);
if (arr[nleft]==a & nleft>=0)//数组中有闭合区间的 左侧值,需要在整体多加1
{
nleft--;
}
printf("%d\n", nright - nleft);//比cout效率高
}
return 0;
}
PA1-2 祖玛问题 (对应课程:列表)
描述
祖玛是一款曾经风靡全球的游戏,其玩法是:在一条轨道上初始排列着若干个彩色珠子,其中任意三个相邻的珠子不会完全同色。此后,你可以发射珠子到轨道上并加入原有序列中。一旦有三个或更多同色的珠子变成相邻,它们就会立即消失。这类消除现象可能会连锁式发生,其间你将暂时不能发射珠子。开发商最近准备为玩家写一个游戏过程的回放工具。他们已经在游戏内完成了过程记录的功能,而回放功能的实现则委托你来完成。游戏过程的记录中,首先是轨道上初始的珠子序列,然后是玩家接下来所做的一系列操作。你的任务是,在各次操作之后及时计算出新的珠子序列。
输入
第一行是一个由大写字母’A’~'Z’组成的字符串,表示轨道上初始的珠子序列,不同的字母表示不同的颜色。第二行是一个数字n,表示整个回放过程共有n次操作。接下来的n行依次对应于各次操作。每次操作由一个数字k和一个大写字母Σ描述,以空格分隔。其中,Σ为新珠子的颜色。若插入前共有m颗珠子,则k ∈ [0, m]表示新珠子嵌入之后(尚未发生消除之前)在轨道上的位序。
输出
输出共n行,依次给出各次操作(及可能随即发生的消除现象)之后轨道上的珠子序列。如果轨道上已没有珠子,则以“-”表示。
此处是一位大神拿向量做的:大家感兴趣可以看一看 Tsinghua MOOC 祖玛(Zuma)
//#include "stdafx.h"
#include <iostream>
#include <cstring>
#include <cstdio>#define L 20000
char ch[L];
int chsize = 0;
char temp[L];
int pos;
bool judg(int tpos)
{
int head = tpos, last = tpos;
char elem = ch[tpos];
while (ch[head]==elem && head)
{
head--;
}
if (ch[head]!=elem)
{
head++;
}
while (ch[last]==elem)
{
last++;
if (last==chsize)
{
break;
}
} if (last-head>2)
{
strcpy(temp, ch + last);
strcpy(ch + head, temp);
chsize = chsize + head - last;
pos = head;
return 1;
}
else
{
return 0;
}
}int main()
{
char tch;
gets(ch);
while (ch[chsize] >= 'A' && ch[chsize] <= 'Z')
{
chsize++;
} int n;
scanf("%d", &n);
while (n--)
{
scanf("%d %c", &pos,&tch); strcpy(temp, ch + pos);
strcpy(ch + pos + 1, temp);
ch[pos] = tch;
chsize++;
while (chsize&&judg(pos));
if (chsize>0)
{
puts(ch);
}
else
{
puts("-");
}
} return 0;
}
PA 1-3 灯塔(归并)
描述
海上有许多灯塔,为过路船只照明。
图一)如图一所示,每个灯塔都配有一盏探照灯,照亮其东北、西南两个对顶的直角区域。探照灯的功率之大,足以覆盖任何距离。灯塔本身是如此之小,可以假定它们不会彼此遮挡。
(图二)若灯塔A、B均在对方的照亮范围内,则称它们能够照亮彼此。比如在图二的实例中,蓝、红灯塔可照亮彼此,蓝、绿灯塔则不是,红、绿灯塔也不是。现在,对于任何一组给定的灯塔,请计算出其中有多少对灯塔能够照亮彼此。
输入
共n+1行。第1行为1个整数n,表示灯塔的总数。第2到n+1行每行包含2个整数x, y,分别表示各灯塔的横、纵坐标。
输出
1个整数,表示可照亮彼此的灯塔对的数量。
实际题目
//#include "stdafx.h"
#include <cstdio>
using namespace std;
#define ll long long
#define L 4000000
struct Point {
ll x, y;
}points[L];
Point *bs = new Point[L];
ll *b = new ll[L];
ll y[L], countsum = 0;
void MergeS(Point *elem, int sta, int mid, int end)
{
Point *lo = elem + sta, *mi = elem + mid;
int left = mid - sta, right = end - mid;
for (int i = 0; i < left; bs[i] = lo[i++]);
for (int i = 0, j = 0, k = 0; j < left;)
{
if ((right<=k)|| (bs[j].x<mi[k].x))
{
lo[i++] = bs[j++];
}
if ((k < right) && (mi[k].x < bs[j].x))
{
lo[i++] = mi[k++];
}
}
}
void Mergell(ll *elem, int sta, int mid, int end)
{
ll *a = elem + sta, *c = elem + mid;
int left = mid - sta, right = end - mid;
for (int i = 0; i < left; b[i] = a[i++]);
for (int i = 0, j = 0, k = 0; j < left;)
{
if ((right <= k )|| (b[j]<c[k]))
{
a[i++] = b[j++];
if (k<right)
{
countsum += right - k;
}
}
if ((k < right) && (c[k] < b[j]))
{
a[i++] = c[k++];
}
}
}
void MergeSort(Point *elem, int sta, int end)
{
if (end-sta<2)
{
return;
}
int mid = (sta + end) >> 1;;
MergeSort(elem,sta, mid);
MergeSort(elem,mid, end);
MergeS(elem,sta, mid, end);
}
void MergeSortY(ll *elem, int sta, int end)
{
if (end - sta<2)
{
return;
}
int mid = (sta + end) >> 1;
MergeSortY(elem,sta, mid);
MergeSortY(elem,mid, end);
Mergell(elem,sta, mid, end);
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%lld %lld", &points[i].x, &points[i].y);
}
MergeSort(points, 0, n);
for (int i = 0; i < n; i++)
{
y[i] = points[i].y;
}
MergeSortY(y, 0, n);
printf("%lld\n", countsum);
return 0;
}
PA 2-1 列车调度(栈)
描述
某列车调度站的铁道联接结构如Figure 1所示。其中,A为入口,B为出口,S为中转盲端。所有铁道均为单轨单向式:列车行驶的方向只能是从A到S,再从S到B;另外,不允许超车。因为车厢可在S中驻留,所以它们从B端驶出的次序,可能与从A端驶入的次序不同。不过S的容量有限,同时驻留的车厢不得超过m节。设某列车由编号依次为{1, 2, …, n}的n节车厢组成。调度员希望知道,按照以上交通规则,这些车厢能否以{a1, a2, …, an}的次序,重新排列后从B端驶出。如果可行,应该以怎样的次序操作?
输入
共两行。第一行为两个整数n,m。第二行为以空格分隔的n个整数,保证为{1, 2, …, n}的一个排列,表示待判断可行性的驶出序列{a1,a2,…,an}。
输出
若驶出序列可行,则输出操作序列,其中push表示车厢从A进入S,pop表示车厢从S进入B,每个操作占一行。若不可行,则输出No。
实际题目
这一篇是我写的时候参考的,一目了然,非常好的一个解法,虽然可能内存大了 列车调度参考
可以改进之处可能就是(提前判断是否有一个数后面比他小的数不是倒序的,例如:1,2,3,4中1,4,2,3是无法构成栈混洗的,因为4后面的2,3均比它小而且顺序)
下图为思路图:
此处是代码
//#include "stdafx.h"
#include <cstdio>
using namespace std;
#define MAXL 1600000
struct mystack
{
int n = 0;
int sstack[MAXL];
void push(int a)
{
n++;
sstack[n] = a;
}
void pop()
{
n--;
}
int top()
{
return sstack[n];
}
bool empty()
{
if (n == 0)
{
return true;
}
else
return false;
}
int size()
{
return n;
}
};
mystack a, b, s;
int real[MAXL];
bool action[MAXL];
int main()
{
int n, m;
scanf("%d %d", &n, &m);
for (int i = n; i >= 1; i--)
{
a.push(i);
}
for (int i = 0; i < n; i++)
{
scanf("%d", real + i);
}
int step = 0, numbertotal = 0;
bool done = false;
while (!done)
{
if (numbertotal==n)
{
done = true;
break;
}
else if (s.empty())
{
s.push(a.top());
a.pop();
action[step] = true;
}
else if (s.top()==real[numbertotal])
{
b.push(s.top());
s.pop();
numbertotal++;
action[step] = false;
}
else if (!a.empty())
{
s.push(a.top());
a.pop();
action[step] = true;
}
else
{
printf("No\n");
break;
}
if (s.size() > m)
{
printf("No\n");
break;
}
step++;
}
if (done)
{
for (int i = 0; i < step; i++)
{
if (action[i]) printf("push\n");
else printf("pop\n");
}
}
return 0;
}
PA 2-2 真二叉树重构
描述
一般来说,给定二叉树的先序遍历序列和后序遍历序列,并不能确定唯一确定该二叉树。
(图一)比如图一中的两棵二叉树,虽然它们是不同二叉树,但是它们的先序、后序遍历序列都是相同的。但是对于“真二叉树”(每个内部节点都有两个孩子的二叉树),给定它的先序、后序遍历序列足以完全确定它的结构。将二叉树的n个节点用[1, n]内的整数进行编号,输入一棵真二叉树的先序、后序遍历序列,请输出它的中序遍历序列。
输入
第一行为一个整数n,即二叉树中节点的个数。
第二、三行为已知的先序、后序遍历序列。
输出
仅一行,给定真二叉树的中序遍历序列。
实际题目
大概递归思路,不过其实可以用栈的方式也能做(循环)… 就是没想完全这个方法,就是这个节点怎么去深入是一个问题。
//#include "stdafx.h"
#include<iostream>
#define L 4000000
using namespace std;
struct node
{
int data;
node *lc, *rc;
};
int pre_tree[L], post_tree[L];node *buildtree(int pre_l, int pre_h, int post_l, int post_h)
{
node *root = new node;
root->data = pre_tree[pre_l];//前序排序的左端顶点(即根节点)
root->lc = root->rc = NULL;
int pos = 0, number_sl = 0;
if (post_l == post_h) return root;//后面没有了即返回root
for (int i= post_l; i < post_h; i++)
{
if (pre_tree[pre_l+1]==post_tree[i])
{
pos = i;//记录左子树的分界
break;
}
}
number_sl = pos - post_l + 1;
root->lc = buildtree(pre_l + 1, number_sl + pre_l, post_l, pos);
root->rc = buildtree(pre_l + number_sl + 1, pre_h, pos + 1, post_h - 1); return root;
}
void show_inoder(node *root)
{
if (!root) return;//root 不为空的时候一直走到左孩子的底端打印,随后走右孩子
show_inoder(root->lc);
printf("%d ", root->data);
show_inoder(root->rc);
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", pre_tree + i);
for (int i = 0; i < n; i++)
scanf("%d", post_tree + i);
node *root = buildtree(0, n - 1, 0, n - 1);
show_inoder(root);
return 0;
}
PA2-3 旅行商(TSP)
描述
Shrek是一个大山里的邮递员,每天负责给所在地区的n个村庄派发信件。但杯具的是,由于道路狭窄,年久失修,村庄间的道路都只能单向通过,甚至有些村庄无法从任意一个村庄到达。这样我们只能希望尽可能多的村庄可以收到投递的信件。
Shrek希望知道如何选定一个村庄A作为起点(我们将他空投到该村庄),依次经过尽可能多的村庄,路途中的每个村庄都经过仅一次,最终到达终点村庄B,完成整个送信过程。这个任务交给你来完成。
输入
第一行包括两个整数n,m,分别表示村庄的个数以及可以通行的道路的数目。
以下共m行,每行用两个整数v1和v2表示一条道路,两个整数分别为道路连接的村庄号,道路的方向为从v1至v2,n个村庄编号为[1, n]。
输出
输出一个数字,表示符合条件的最长道路经过的村庄数。
实际题目
//#include "stdafx.h"
#include<iostream>
#define L 1000000
#define GetMax(a,b) a>b?a:b
using namespace std;
struct NNode
{
int Psub;//下一个点的序号
NNode* succ;//连接下下个的点信息(例如0->1 0->4,那么 1先进入front 后4进入front,1就成了succ的部分)
};
struct PNode
{
int in;//顶点的入度
int len;//到达此点所需要经过的边长数
NNode* front;//此点去往的点信息
};
PNode List[L];
int stack[L];
int maxlen;
NNode *t;
void ToloSort(int n,int m)
{
int top = 0;
for (int k = 0; k < n; k++)
{
if (!List[k].in)
{
stack[++top] = k;
}
}
while (top)
{
//直到原先内部第一次入度为0的顶点都被访问了一遍
int v = stack[top--];
/* 此处用到了拓扑排序,for 入度为0的开始,访问前面的点信息,往后p为NULL结束for循环 */
for (NNode* p = List[v].front; p; p = p->succ)
{
//此处不是拓扑排序内容 仅用来记录来到这个点需要经过的边长
List[p->Psub].len = GetMax(List[v].len + 1, List[p->Psub].len);//是入度为0的序号点+1 即这次走的路长,还是内部有的长
maxlen = GetMax(List[p->Psub].len, maxlen);//更新更长的边数 /* 此处看不懂请参考书上的拓扑排序原理 DAG必有零点入度顶点的特性实现拓扑排序 */
if (!(--List[p->Psub].in))//先进行相减操作操作后如果入度为0,弹出栈顶序号
{
stack[++top] = p->Psub;
}
}
}
}
int main()
{
int n, m;//题目的要求n,m
scanf("%d %d", &n, &m);
int tems, teme;
for (int i = 0; i < m; i++)
{
scanf("%d %d", &tems, &teme);
tems--, teme--;//使点的序号按数组的要求从0开始表示
t = new NNode;//新生成一个对象
t->Psub = teme;
List[teme].in++;//代表后面的点是有点入进来的
t->succ = List[tems].front;//此处当front为空 即为NULL指针
List[tems].front = t;//将下个点的序号,下下个点(直接连接)的信息传入
}
ToloSort(n, m);
printf("%d\n", maxlen + 1);//经过的顶点数=边数+1
return 0;
}
数据结构(上)总结:
对于C++也是学堂在线看到基础部分就跳到结构(上)来学,有些对于为什么一定生成新对象的做法还是有些疑惑,感觉如果自己单独只看课可能收获不会特别大,得带着题目和论坛(学堂在线的老版真的做的好,论坛直到现在都有人去回复,也能看到以前的帖子,那里面有很多大佬对一些课堂知识点的看法,也有老师提出的思考题的见解)
个人感觉最难的可能是没有适应无库编程吧,二叉树、链表和图的解法很多时候都是以递归形式,如果逻辑不理清很难写下去编程题,编程题极其有意思,希望各位不要只看答案,想不出来看看书,看看其他人代码,分析对比思路的不同会很有意思。