MOOC数据结构 OJ小记录


很多代码都是参考这位博主的( 18Temp),真的是很简洁到… 后面第二大部分的题目得分析1小时才完全看懂自己能写的…

PA1-1 范围查询 (对应课程:绪论+向量)

描述
数轴上有n个点,对于任一闭区间 [a, b],试计算落在其内的点数。
输入
第一行包括两个整数:点的总数n,查询的次数m。
第二行包含n个数,为各个点的坐标。
以下m行,各包含两个整数:查询区间的左、右边界a和b。
输出
对每次查询,输出落在闭区间[a, b]内点的个数。

首先… 一开始没看OJ不能用库 /笑哭,虽然我没看课 看完C++就直接跳到编程去做了,然后乖乖看课(虽然以前看过一次程序设计基础 但是没有结合代码的,就是大概了解了有些啥方法
整体,贴代码:(PS:我认为如何想在效率进行改进:

  1. qsort() 是C语言的标准库,使用的是快速排序法,类似于冒泡但是是lo hi的对换,感兴趣可以百度一下,能看源码。 -> 所以引发了这里可以不用库,n大于一定数值采用归并排序(上课讲到过)
  2. 主要是排序与查找算法都采用最优应该就可以(查找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++也是学堂在线看到基础部分就跳到结构(上)来学,有些对于为什么一定生成新对象的做法还是有些疑惑,感觉如果自己单独只看课可能收获不会特别大,得带着题目和论坛(学堂在线的老版真的做的好,论坛直到现在都有人去回复,也能看到以前的帖子,那里面有很多大佬对一些课堂知识点的看法,也有老师提出的思考题的见解)

个人感觉最难的可能是没有适应无库编程吧,二叉树、链表和图的解法很多时候都是以递归形式,如果逻辑不理清很难写下去编程题,编程题极其有意思,希望各位不要只看答案,想不出来看看书,看看其他人代码,分析对比思路的不同会很有意思。

发布了11 篇原创文章 · 获赞 11 · 访问量 3741

猜你喜欢

转载自blog.csdn.net/qq_39537898/article/details/103755856