顺序表练习(一):字符串区间操作的实现与详解

前言

顺序表其实就是数组,而字符串的区间操作,其实就是对一个字符串里的一段进行操作,例如删除一段字符、插入一段字符、获取一段字符等。这原本是我的一次数据结构课程作业,来自李春葆的数据结构教程第五版第四章的上机实验题,虽不难但细节较多,算是一次不错的训练编码能力的机会。

 

数据定义

既然要实现字符串的区间操作,那么肯定需要先手撸一个简易的字符串,先实现一些常规的方法,例如创建字符串、销毁字符串、判空等,由于书本上是用的c语法来实现的,也就是结构体+全局函数的形式,没有包装成C++类的形式,索性我也按照这种方式实现。

先定义一个结构体,按照数据结构教材里惯用的结构体定义方式,由于这次要写的是SequenceString教材里简称为SqString,这个结构体我就简称为SS吧,后面写起来也方便点:

#define MAXSIZE 20

typedef struct SqList
{
    char *arr;// 字符数组
    int size;// 代表整个字符串的长度,同时也代表着数组的第一个空位置的下标
} SS; // Sequence String

 

基础函数的实现

一些常规、必须的方法的实现:

SS *createSS()// 创建空字符串
{
    SS *ss = new SS;
    ss->arr = new char[MAXSIZE];
    ss->size = 0;//没存任何字符的时候为0,代表长度为0,同时代表第一个空位置下标为0
    memset(ss->arr, '0', MAXSIZE * sizeof(char));
    return ss;
}

SS *createSS(const char *str)//根据一个已有的c风格字符串来创建我们的字符串
{
    SS *ss = new SS;
    ss->arr = new char[MAXSIZE];
    memset(ss->arr, '0', MAXSIZE * sizeof(char));
    strncpy(ss->arr, str, strlen(str));
    ss->size = strlen(str);
    ss->arr[ss->size] = '\0';
    return ss;
}

void destroySS(SS *ss)//销毁字符串
{
    if (!ss)
        return;
    delete[] ss->arr;// new [] 对 delete []
    delete ss; // new 对 delete
}

bool isEmpty(SS *ss)//判空
{
    return !ss->size;
}

bool isFull(SS *ss)//判满
{
    return ss->size == MAXSIZE;
}

int getSize(SS *ss)//取长度
{
    return ss->size;
}

void print(SS *ss)//打印字符串
{
    if (!ss)
    {
        cout << "print(NULL)." << endl;
        return;
    }
    for (int i = 0; i < ss->size; i++)
        cout << ss->arr[i];
    cout << endl;
}

SS *copy(SS *ori)// 返回一个深拷贝的ori,其实就是把ori完整复制一遍
{
    SS *ss = createSS();
    for (int i = 0; i < ori->size; i++)
        ss->arr[i] = ori->arr[i];
    ss->size = ori->size;
    return ss;
}

以上是一些基本的函数实现,在create函数中我为了调试方便,直接将数组的空间格式化成了字符'0',并且重载了一个根据已有c风格string构建我们的SqString的方法。这些都不是本篇的重点,简单略过就行,如果有不太清楚的可以看看代码中的注释。

 

区间插入

其实就是在字符串中插入一个子串,思路很简单,先给要插入的子串挪出空位,然后插入,当然,要先经过繁琐的下标合法检查。函数定义:

SS *insert_range(SS *ori, int startIndex, const char *str, bool returnNewSS)

这个ori就是即将被插的SqString,要插入的是后面的str,startIndex是插入位置,从1开始,而这个returnNewSS代表是否要返回一个新的对象,如true,ori不会被改动,返回新的修改好的对象,如false,则返回修改后的ori。

代码实现:

SS *insert_range(SS *ori, int startIndex, const char *str, bool returnNewSS)
{
    int length = strlen(str);//因为是c风格字符串,所以可以先用strlen计算出长度
    if (ori->size + length >= MAXSIZE)//结果长度大于数组的最大长度
    {
        cout << "The length of result SqString is longer than the MaxSize." << endl;
        return NULL;
    }
    if (startIndex < 1 || startIndex > ori->size + 1)//插入位置为负或者超过字符串长度
    {
        cout << "Index illegal." << endl;
        return NULL;
    }
    SS *ss = (returnNewSS ? copy(ori) : ori);//根据参数决定是否复制
    //下面就是挪出空间给串str的代码,从尾部开始到startIndex指向的下标为止向后挪length位
    for (int i = ss->size - 1; i >= startIndex - 1; i--)
    {
        ss->arr[i + length] = ss->arr[i];
    }
    ss->size += length;//别忘了维护size
    //将str赋到ss中空出的位置去
    int j = 0;
    for (int i = 0; i < length; i++, j++)
    {
        ss->arr[startIndex - 1 + i] = str[j];
    }
    return ss;
}

思路是:先将 从startIndex开始到数组尾部 的这些字符往后移length位腾出length个位置,这个length就是要插入的子串str的长度;然后再把str赋到这length个空位上就可以了。

代码中有很详细的注释,另外要注意的是:

1.startIndex是从1开始的,转成数组下标时需要-1

2.size作为下标的话指向的是数组尾部第一个为空的字符,如果要指向最后一个有效字符需要-1,例如size为3的数组,最后一个有效字符的下标就是2,而下标3就是尾部第一个为空的字符

3.这个插入位置的含义需要多注意一下,例如对于串"abc",我们在第一个位置插入子串"111"的话,得到的是"111abc",第一个位置的字符被挤到后面去了

4.我的这个实现是支持在尾部插入的,例如对于长度为3的串"abc",调用函数在第4个位置插入子串"111"的话是可以得到串"abc111"的, 而如果在第5个位置插入就会报错

要是还搞不懂的话,建议拿个草稿纸画画,容易理清思路。

 

区间删除

函数定义:

SS *del_range(SS *ori, int startIndex, int delLength, bool returnNewSS)

参数的定义同上,只不过这次是从串ori中删除一段字符,只需要开始位置和要删除的子串的长度就可以了。

代码实现:

SS *del_range(SS *ori, int startIndex, int delLength, bool returnNewSS)
{
    int endIndex = startIndex - 1 + delLength - 1;//这里计算要删除的区间的最后一个字符的下标
    if (startIndex < 1 || startIndex > ori->size)
    {
        cout << "startIndex illegal." << endl;
        return NULL;
    }
    if (endIndex > ori->size -1)//endIndex是下标,size也应该转成下标来比较
    {
        cout << "endIndex illegal." << endl;
        return NULL;
    }
    SS *ss = (returnNewSS ? copy(ori) : ori);
    //下面是区间删除的具体实现
    for (int i = 0; i < ss->size - 1 - endIndex; i++)
        ss->arr[i + startIndex - 1] = ss->arr[i + startIndex - 1 + delLength];
    ss->size -= delLength;

    return ss;
}

函数开始和上面区间插入的实现差不多,先判断参数是否合法,然后根据是否返回新对象拷贝对象,不同的是endIndex的计算和最后删除区间的具体实现,待我慢慢解释。

首先,startIndex是待删除区间的起始位置,是从1开始的,且是包含在删除区间里的,length是待删除区间的长度,那么要计算待删除区间的结束下标(结束下标也是包含在结束区间里的,整个区间为闭区间),就应该是 起始下标 + 区间长度 - 1,而起始下标又等于startIndex-1,所以最后 endIndex = startIndex -1 + length -1,第一个 -1 来自startIndex和下标的差值,第二个-1是因为startIndex所指下标是包含在待删除区间里的。

刚好手头有画板,以在第2个位置开始删除3个字符为例:

献丑了,哈哈..

endIndex的计算解释完了,然后就是解释最后的那个for了:

    for (int i = 0; i < ss->size - 1 - endIndex; i++)
        ss->arr[i + startIndex - 1] = ss->arr[i + startIndex - 1 + delLength];

其实这里的思路是把待删除区间后面的字符往前挪,覆盖掉待删除区间的字符就可以了,然后维护一下ss的size就行。

这段代码做的就是 先计算出实际要挪的字符数量 ss->size - 1 - endIndex(其实就是从endIndex到数组尾的长度),然后从0开始for,for里面用偏移(就是待删除区间的长度)把待删除的字符用待删除区间后面的字符覆盖

这段代码的大致思路就是这样,如果还不理解的话同样推荐画图理一下思路。

 

区间提取

其实就是根据开始位置和长度来截取一段字符作为子串,函数实现起来要比上面两个操作简单不少,直接上代码:

SS *get_range(SS *ori, int startIndex, int length)
{
    int endIndex = startIndex - 1 + length - 1;
    if (startIndex < 1 || startIndex > ori->size)
    {
        cout << "startIndex illegal." << endl;
        return NULL;
    }
    if (endIndex > ori->size - 1 || length < 0)
    {
        cout << "endIndex illegal." << endl;
        return NULL;
    }
    SS *ss = createSS();
    for (int i = 0; i < length; i++)
    {
        ss->arr[i] = ori->arr[startIndex - 1 + i];
    }
    ss->size = length;
    return ss;
}

就是根据长度将目的区间的字符赋值到新串里就是了,没什么难点,至于区间的set啥的也跟这个很类似,就不讲了。

 

完整代码

还包含了一些作业要求的函数,这里就不再解释了。

#include <iostream>
#include <cstring>

#define MAXSIZE 20

using namespace std;

typedef struct SqList
{
    char *arr;
    int size;
} SS;

SS *createSS()
{
    SS *ss = new SS;
    ss->arr = new char[MAXSIZE];
    ss->size = 0;
    memset(ss->arr, '0', MAXSIZE * sizeof(char));
    return ss;
}

SS *createSS(const char *str)
{
    SS *ss = new SS;
    ss->arr = new char[MAXSIZE];
    memset(ss->arr, '0', MAXSIZE * sizeof(char));
    strncpy(ss->arr, str, strlen(str));
    ss->size = strlen(str);
    ss->arr[ss->size] = '\0';
    return ss;
}

void destroySS(SS *ss)
{
    if (!ss)
        return;
    delete[] ss->arr;
    delete ss;
}

bool isEmpty(SS *ss)
{
    return !ss->size;
}

bool isFull(SS *ss)
{
    return ss->size == MAXSIZE;
}

int getSize(SS *ss)
{
    return ss->size;
}

void print(SS *ss)
{
    if (!ss)
    {
        cout << "print(NULL)." << endl;
        return;
    }
    for (int i = 0; i < ss->size; i++)
        cout << ss->arr[i];
    cout << endl;
}

SS *copy(SS *ori)
{
    SS *ss = createSS();
    for (int i = 0; i < ori->size; i++)
        ss->arr[i] = ori->arr[i];
    ss->size = ori->size;
    return ss;
}

void add(SS *ss, int index, char data)
{
    if (isFull(ss))
    {
        cout << "SqString is full." << endl;
        return;
    }
    if (index < 1 || index > ss->size + 1)
    {
        cout << "index illegal." << endl;
        return;
    }
    for (int i = ss->size; i > index - 1; i++)
    {
        ss->arr[i] = ss->arr[i - 1];
    }
    ss->arr[index - 1] = data;
    ss->size++;
}

int del(SS *ss, int index)
{
    if (isEmpty(ss))
    {
        cout << "SqString is empty." << endl;
        return INT_MIN;
    }
    if (index < 1 || index > ss->size)
    {
        cout << "index illegal." << endl;
        return INT_MIN;
    }
    int res = ss->arr[index - 1];
    for (int i = index - 1; i < ss->size - 1; i++)
    {
        ss->arr[i] = ss->arr[i + 1];
    }
    ss->size--;
    return res;
}

void set(SS *ss, int index, char data)
{
    if (isEmpty(ss))
    {
        cout << "SqString is empty." << endl;
        return;
    }
    if (index < 1 || index > ss->size)
    {
        cout << "index illegal." << endl;
        return;
    }
    ss->arr[index - 1] = data;
}

SS *insert_range(SS *ori, int startIndex, const char *str, bool returnNewSS)
{
    int length = strlen(str);
    if (ori->size + length >= MAXSIZE)
    {
        cout << "The length of result SqString is longer than the MaxSize." << endl;
        return NULL;
    }
    if (startIndex < 1 || startIndex > ori->size + 1)
    {
        cout << "Index illegal." << endl;
        return NULL;
    }
    SS *ss = (returnNewSS ? copy(ori) : ori);

    for (int i = ss->size - 1; i >= startIndex - 1; i--)
    {
        ss->arr[i + length] = ss->arr[i];
    }
    ss->size += length;

    int j = 0;
    for (int i = 0; i < length; i++, j++)
    {
        ss->arr[startIndex - 1 + i] = str[j];
    }
    return ss;
}

SS *del_range(SS *ori, int startIndex, int delLength, bool returnNewSS)
{
    int endIndex = startIndex - 1 + delLength - 1;
    if (startIndex < 1 || startIndex > ori->size)
    {
        cout << "startIndex illegal." << endl;
        return NULL;
    }
    if (endIndex > ori->size - 1)
    {
        cout << "endIndex illegal." << endl;
        return NULL;
    }
    SS *ss = (returnNewSS ? copy(ori) : ori);

    for (int i = 0; i < ss->size - 1 - endIndex; i++)
        ss->arr[i + startIndex - 1] = ss->arr[i + startIndex - 1 + delLength];
    ss->size -= delLength;

    return ss;
}

SS *get_range(SS *ori, int startIndex, int length)
{
    int endIndex = startIndex - 1 + length - 1;
    if (startIndex < 1 || startIndex > ori->size)
    {
        cout << "startIndex illegal." << endl;
        return NULL;
    }
    if (endIndex > ori->size - 1 || length < 0)
    {
        cout << "endIndex illegal." << endl;
        return NULL;
    }
    SS *ss = createSS();
    for (int i = 0; i < length; i++)
    {
        ss->arr[i] = ori->arr[startIndex - 1 + i];
    }
    ss->size = length;
    return ss;
}

SS *set_range(SS *ori, int startIndex, const char *str, bool returnNewSS)
{
    int length = strlen(str);
    int endIndex = startIndex - 1 + length;
    if (startIndex < 1 || startIndex > ori->size)
    {
        cout << "startIndex illegal." << endl;
        return NULL;
    }
    if (endIndex > ori->size - 1)
    {
        cout << "The length of str is illegal." << endl;
        return NULL;
    }
    SS *ss = (returnNewSS ? copy(ori) : ori);

    for (int i = 0; i < length; i++)
    {
        ss->arr[i + startIndex - 1] = str[i];
    }
    return ss;
}

SS *append(SS *s1, SS *s2, bool returnNewSS) // Link s1 and the s2
{
    if (s1->size + s2->size > MAXSIZE)
    {
        cout << "The length of result string is longer than MaxSize." << endl;
        return NULL;
    }
    SS *ss = (returnNewSS ? copy(s1) : s1);
    for (int i = 0; i < s2->size; i++)
    {
        ss->arr[i + ss->size] = s2->arr[i];
    }
    ss->size += s2->size;
    return ss;
}

SS *replace(SS *ori, int startIndex, int length, const char *str, bool returnNewSS)
{
    if (length < strlen(str))
    {
        cout << "replacing length is shorter than the length of str." << endl;
        return NULL;
    }
    SS *ss = del_range(ori, startIndex, length, true);
    insert_range(ss, startIndex, str, false);
    return ss;
}

int main()
{
    SS *ss = createSS("abcdef");
    print(ss);
    SS *s1 = get_range(ss, 2, -1);
    print(s1);

    destroySS(ss);
    destroySS(s1);
    return 0;
}

 

总结

数组的区间操作其实没什么很难的内容,无非就是两个要注意的地方:

一是起始位置是从0开始的还是从1开始的问题

要解决这个问题就是要理清这里面的关系,如果传进来的索引是从0开始的,那就直接当下标用,而且在计算区间长度、计算区间端点的时候也要与相应的下标相减才行,不然一个是从1开始的索引,一个是从0开始的下标,计算得到的长度就是错的;如果是从1开始的,那么转换成下标的时候就要记得-1。

 

二是区间端点问题

本文中的区间全都是闭区间,对于起始位置为startIndex,长度为length的区间来说,范围就是

[startIndex ,startIndex + length],要是以数组下标表示的话,则是[startIndex -1 ,startIndex -1 + length] 。但是由于区间的起始位置是包含在区间里面的,我们计算终点座标的时候应该是  终点座标 = startIndex -1 + length -1  才对;

举个例子,对于串“abcde”,长度为5,问起始位置(位置,不是数组下标,从1开始)为2,长度为3的区间终点的位置?位置为2的字符是‘b’,从‘b’开始数三个字符,是“bcd”,那么最后一个区间最后一个字符就是‘d’,‘d’的位置是4,顺着数第四个字符嘛,所以要是直接2 + 3 = 5就得到区间终点的位置是不对的,还需要-1才行。

 

区间操作的代码实现起来其实没什么难的,主要是因为我自己以前学C的时候学得不够扎实,练得少,导致这种处理下标、区间端点的问题总是感觉很苦手,正好借这次数据结构作业的机会练习这部分的内容,同时也整理成博文跟大家分享,那么本篇文章就到这里结束,感谢阅读。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章