順序表練習(一):字符串區間操作的實現與詳解

前言

順序表其實就是數組,而字符串的區間操作,其實就是對一個字符串裏的一段進行操作,例如刪除一段字符、插入一段字符、獲取一段字符等。這原本是我的一次數據結構課程作業,來自李春葆的數據結構教程第五版第四章的上機實驗題,雖不難但細節較多,算是一次不錯的訓練編碼能力的機會。

 

數據定義

既然要實現字符串的區間操作,那麼肯定需要先手擼一個簡易的字符串,先實現一些常規的方法,例如創建字符串、銷燬字符串、判空等,由於書本上是用的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的時候學得不夠紮實,練得少,導致這種處理下標、區間端點的問題總是感覺很苦手,正好借這次數據結構作業的機會練習這部分的內容,同時也整理成博文跟大家分享,那麼本篇文章就到這裏結束,感謝閱讀。

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