凉城c

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、本章内容小结

  本章主要讲解的知识点有:串、数组、和广义表。其中包括串的定义、案例引入、存储结构、模式匹配算法;数组的类型定义以及存储方式;广义表的定义和存储结构。

  串,即字符串。它是一种特殊的线性表,它的特殊性表现在其数据元素是一个字符,这也意味着它是一种内容受限的线性表。但是在日常应用中,字符串是一种使用率非常高的线性表,因此我觉得学好字符串相关的知识是非常重要的。现在我们的浏览器都有 “搜索” 的功能,只要我们输入我们想查找的信息,浏览器就能够帮我们找到我们想要的内容。如果我们使用过百度、搜狗等软件,我们就可以在查询结果中发现,我们输入的字符信息会被标注为红色,如下图所示:

 

 

   这其中就运用了本章所学的知识:字符串匹配。而它的实现有两种算法:BF算法和KMP算法。这里以BF算法为例,它的实现思路就是通过主串与字串之间的字符一个一个比对,当比对失败时,子串就相对主串原本的位置整体后移一个字符,重新开始依次比对,以此类推。

  接下来是数组,数组对于我们而言接触得非常多,尤其是我以前做过ACM中的基础题中,运用数组得次数非常多,比如行列式的计算、进行连续的加减乘除工作等。而这一章的数组知识主要讲述的是创建数组时压缩存储的方法,以此来节省内存空间。我们知道,数组常用顺序存储结构,并且数组一般不进行插入或删除工作,因此,查找元素会非常便捷。如果我们已知元素个数,那么我们用数组是非常节约内存的。

  关于广义表,它其实就是线性表的推广,也被称为列表。我们可以发现,在描述广义表时,又会用到广义表的概念。因此,广义表的定义是一个递归的定义。广义表的数据元素可以有不同的结构,因此通常采用链式存储结构。

 

二、完成作业实践时的心得体会

(1)关于第四章作业

  第四章作业的编程题的要求是:给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置。

  为了追求内存占用少,在这道题中,我定义了两个结构体

#define SLENMAX 1000000
#define TLENMAX 100000

typedef struct
{
    char ch[SLENMAX + 1];
    int len;
}SString;

typedef struct
{
    char ch[TLENMAX + 1];
    int len;
}TString;
 

我觉得这样操作可以有效节约内存空间,同时将主串S和模式T更好地区别开来,使程序拥有更好的可读性。

  解决这道题的算法是KMP算法,相对于BF算法而言,KMP算法拥有更高的执行效率,其包含的函数如下:

void get_next(TString T, int next[])
{
    int j,k;
j=0;
k=-1;
 
next[0]=-1;
while(j<T.len)
{
if(k==-1||T.ch[j]==T.ch[k])
{
j++;
k++;
 
if(T.ch[j]!=T.ch[k])
next[j]=k;
else
next[j]=next[k];
}
else
k=next[k];
}
}

int Index_KMP(SString S, TString T, int p)
{
    int i, j;
    int next[TLENMAX + 1];
    get_next(T, next);

    i = p-1;
    j = 0;

    while (i < S.len&&j < T.len)
    {
        if (j==-1||S.ch[i] == T.ch[j])
        {
            ++i;
            ++j;
        }
        else
            j = next[j];
    }

    if (j >= T.len)
        return i - T.len + 1;

    else
        return -1;
}
 

调用这些函数可以直接得出字符串是否匹配以及它们出现的位置,int main()函数中的调用如下:

int main()
{
    SString S; TString T;

    cin.getline(S.ch, SLENMAX+1);
    S.len = strlen(S.ch);
    if (S.len<1 || S.len>SLENMAX)
        return 0;

    cin.getline(T.ch, TLENMAX+1);
    T.len = strlen(T.ch);
    if (T.len<1 || T.len>TLENMAX)
        return 0;
    
    if(S.len<T.len)
     return 0;
    
    if(S.len == 1)
    {
     if(S.ch[0]==T.ch[0])
     {
     cout << "1";
     return 0;
     }
    
     cout << "0";
     return 0;
    }

int m = Index_KMP(S, T, 1);
if(m != -1)
{
cout << m;
}
else
cout << "0";
 
return 0;
}

  在这道题目的实际操作中,如果主串包含的字符只有一个,我发现KMP算法试用效果不佳,因此我单独将主串中只有一个元素的情况独立出来操作。因为Index_KMP(S, T, 1)能够直接得出字符串是否匹配以及它们出现的位置,所以我们只需在主函数中创建一个int型变量,并将函数值赋给该变量即可得出答案。

 

(2)关于第四章实践

 (a)稀疏矩阵

  这道题我采用的方法是创建三个一元数组,分别存储稀疏矩阵的非零元素所在的行、列号和非零元素的值。

int m, n, N;
    cin >> m >> n >> N;

    if (N<1 || N>500)
        return 0;

    int* a = new int[N];
    int* pos1 = new int[N];
    int* pos2 = new int[N];
 
  先输入了非零元素的个数N,我们就可以定义三个包含N个元素的一维数组,在这道题目中,我令a为非零元素的值,pos1为行号值,pos2为列号值
 
  关于判断,我的方法是:
    int i, j;
    for (i = 0; i < N; i++)
    {
        cin >> pos1[i] >> pos2[i] >> a[i];
        if (pos1[i]<1 || pos1[i]>m || pos2[i]<1 || pos2[i]>n || a[i] == 0)
        {
            delete a;
            delete pos1;
            delete pos2;
            return 0;
        }
    }

    for (i = 1; i < N; i++)
    {
        for (j = 0; j < i; j++)
        {
            if (pos1[i] == pos1[j] && pos2[i] == pos2[j])
            {
                delete a;
                delete pos1;
                delete pos2;
                return 0;
            }
        }
    }
 
  第一个for循环用于判断行列值是否出界和是否输入的元素为0,第二个for循环用于判断是否存在元素位置重合的情况。剩下的事情我们只需输入我们要找的元素即可,即若在a数组中有元素a[x]与输入值匹配,则同时输出与该元素数组下标相同的行数组、列数组元素pos1[x]、pos2[x]。
    int i, j;
    for (i = 0; i < N; i++)
    {
        cin >> pos1[i] >> pos2[i] >> a[i];
        if (pos1[i]<1 || pos1[i]>m || pos2[i]<1 || pos2[i]>n || a[i] == 0)
        {
            delete a;
            delete pos1;
            delete pos2;
            return 0;
        }
    }

    for (i = 1; i < N; i++)
    {
        for (j = 0; j < i; j++)
        {
            if (pos1[i] == pos1[j] && pos2[i] == pos2[j])
            {
                delete a;
                delete pos1;
                delete pos2;
                return 0;
            }
        }
    }
 
 (b)Al核心代码
  这题要求你实现一个简易版的 AI 英文问答程序,主要规则包括:
  1. 消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉;
  2. 把原文中所有大写英文字母变成小写,除了 I;
  3. 把原文中所有独立的 I 和 me 换成 you;
  4. 把原文中所有的问号 ? 换成惊叹号 !;
  5. 把原文中所有独立的 can you 换成 I can —— 这里“独立”是指被空格或标点符号分隔开的单词;
  6. 在一行中输出替换后的句子作为 AI 的回答。

  做这道题时,我和老师的思路不一样,我多创建了一个int型数组,用于判断该字符是否输出,代码如下:

int N;
    cin >> N;

    if (N<1 || N>10)
        return 0;

    char c[10][1001];
    int i, j, len[10], k, x;

    for (i = 0; i < N; i++)
    {
        if (!i)
            getchar();

        cin.getline(c[i], 1001);
        len[i] = strlen(c[i]);

        if (len[i]<1 || len[i]>1000)
            return 0;
    }

    for (i = 0; i < N; i++)
    {
        for (j = 0; j < len[i]; j++)
        {
            if (c[i][j]<0 || c[i][j]>127)
                return 0;
            cout << c[i][j];
        }
            
        cout << endl;

        int* a = new int[len[i]];
        for (j = 0; j < len[i]; j++)
            a[j] = 1;
 
  我默认输入的所有字符都输出,即a数组中的各个元素值均为1。我们要想改变输出结果,只需改变c[i][j]对应的是否输出的int型数组元素a[j]即可,不改变c[i]字符串中的值(c[i]表示第i+1句对话)。实现结果的代码如下:
j = 0;
        while (c[i][j] == ' ')
        {
            a[j] = 0;
            j++;
        }

        while (j < len[i])
        {
            if (c[i][j] >= 'A'&&c[i][j] <= 'Z'&&c[i][j] != 'I')
                c[i][j] = c[i][j] + 32;
            j++;
        }

        j = len[i] - 1;
        while (c[i][j] == ' ')
        {
            a[j] = 0;
            j--;
        }

        for (j = 0; j < len[i]; j++)
        {
            if (c[i][j] == '?')
                c[i][j] = '!';
        }

        for (j = 0; j < len[i]; j++)
        {
            if (a[j] == 1)
            {
                if (c[i][j] == ' ')
                {
                    k = j + 1;
                    while (c[i][k] == ' ')
                    {
                        a[k] = 0;
                        k++;
                    }
                }
            }
        }

        for (j = 0; j < len[i]; j++)
        {
            if (a[j] == 1)
            {
                if ((c[i][j] >= 33 && c[i][j] <= 47) || (c[i][j] >= 91 && c[i][j] <= 96) || (c[i][j] >= 123 && c[i][j] <= 126))
                {
                    k = j - 1;
                    while (c[i][k] == ' ')
                    {
                        a[k] = 0;
                        k--;
                    }
                }
            }
        }

        for (j = 0; j < len[i]; j++)
        {
            if (a[j] == 1)
            {
                if ((j == 0 || (c[i][j - 1] >= 32 && c[i][j - 1] <= 47) || (c[i][j - 1] >= 91 && c[i][j - 1] <= 96) || (c[i][j - 1] >= 123 && c[i][j - 1] <= 126)) && (j == len[i] - 1 || (c[i][j + 1] >= 32 && c[i][j + 1] <= 47) || (c[i][j + 1] >= 91 && c[i][j + 1] <= 96) || (c[i][j + 1] >= 123 && c[i][j + 1] <= 126)) && (c[i][j] == 'I'))
                    a[j] = 2;

                if ((j == 0 || (c[i][j - 1] >= 32 && c[i][j - 1] <= 47) || (c[i][j - 1] >= 91 && c[i][j - 1] <= 96) || (c[i][j - 1] >= 123 && c[i][j - 1] <= 126)) && (j + 1 == len[i] - 1 || (c[i][j + 2] >= 32 && c[i][j + 2] <= 47) || (c[i][j + 2] >= 91 && c[i][j + 2] <= 96) || (c[i][j + 2] >= 123 && c[i][j + 2] <= 126)) && (c[i][j] == 'm') && (c[i][j + 1] == 'e'))
                {
                    a[j] = 2;
                    a[j + 1] = 0;
                }
            }
        }

        for (j = 0; j < len[i]; j++)
        {
            if (a[j] == 1)
            {
                if ((c[i][j] == 'c') && (c[i][j + 1] == 'a') && (c[i][j + 2] == 'n'))
                {
                    if (j == 0 || (c[i][j - 1] >= 32 && c[i][j - 1] <= 47) || (c[i][j - 1] >= 91 && c[i][j - 1] <= 96) || (c[i][j - 1] >= 123 && c[i][j - 1] <= 126))
                    {
                        k = j + 3;

                        while (c[i][k] == ' ')
                            k++;

                        if ((c[i][k] == 'y') && (c[i][k + 1] == 'o') && (c[i][k + 2] == 'u') && ((c[i][k + 3] >= 32 && c[i][k + 3] <= 47) || (c[i][k + 3] >= 91 && c[i][k + 3] <= 96) || (c[i][k + 3] >= 123 && c[i][k + 3] <= 126)))
                        {
                            a[j] = 3;

                            for (x = j + 1; x < k + 3; x++)
                                a[x] = 0;
                        }
                    }
                }
            }
        }
 
  实现去空格的思路就是将这些空格的输出数组a[j]赋值为0;大写改小写、?变!直接操作字符;I变you、can you变I can的操作我将它们的首字母的a数组值分别赋值为2和3,而其余字符的值赋值为0,那么定义2和3这样的值好处就是直接通过if语句输出you、I can等单词。
for (j = 0; j < len[i]; j++)
        {
            if (a[j] == 1)
                cout << c[i][j];
            if (a[j] == 2)
                cout << "you";
            if (a[j] == 3)
                cout << "I can";
        }
 
  以上就是我的主要解题思路。
 
三、参考的资料
  本章的学习主要参考书本上的知识,主要是关于KMP算法,而Al核心代码一题我使用的是自己的解题思路。
 

四、待改进的问题

  关于KMP算法了解的还是有疑惑的地方,希望能再抽出一点时间了解。

 

五、接下来的目标

  多打题,改进思维方式,养成课前预习课后复习的好习惯。自己的想法未必不好,也许它是一种更优的思路,多讨论解题思路,就可以从中举一反三,解决更多的问题。

 
posted on 2019-04-14 23:47  凉城c  阅读(325)  评论(1编辑  收藏  举报