[项目005] 顺序表的综合应用与案例分析 隐藏答案 | 返回首页

作者:欧新宇(Xinyu OU)
当前版本:Release v1.0
开发平台:gcc 13.1.0, g++ 13.1.0, gdb 13.2
运行环境:Intel Core i7-13700KF CPU 3.4GHz, 32GB RAM
本教案所涉及的数据及代码仅用于教学和交流使用,请勿用作商用。

最后更新:2023年9月12日


作业要求:将【课后作业】的题目及程序代码整理成实验报告,并以PDF格式,或直接拍照上传到【雨课堂】(不要使用word文档进行上传)。

【课后作业】

‍1. 从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出运行。

算法提示:
1.搜索整个顺序表,查找最小值元素并记住其位置;
2.搜索结束后,用最后一个元素填补空出的最小值元素的位置。

bool DeleteMin(SqList &L, ElemType &e){
    // 在此处填入代码
}

【参考代码】

bool DeleteMin(SqList &L, ElemType &e){
    // 删除顺序表中的最小元素,并用引用参数e返回其值
    // 若删除成功,则返回true;否则返回false
    if (L.length == 0)                   // 1. 如果线性表为空,则返回Error
        return false;
    e = L.data[0];                       // 2.1 初始化flag变量,使其等于线性表的第一个元素
    int pos = 0;                         // 2.2 初始化位置变量,使其指向线性表的第一个元素怒
    for (int i = 1; i < L.length; i++)   // 3. 按元素依次扫描线性表
        if (L.data[i] < e){              //    若当前元素值小于flag变量 e,则:
            e = L.data[i];               // 3.1 将当前值复制给flag变量e 
            pos = i;                     // 3.2 更新位置变量pos为当前元素的位置
        }
    L.data[pos] = L.data[L.length - 1];  // 4. 使用最后一个元素填补空出的位置
    L.length--;                          // 5. 表长减一
    return true;                         // 6. 删除成功,返回true
}

‍2. 设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为O(1)。

算法提示: 扫描顺序表L的前半部分元素,对于这部分的元素 L.data[i] (0 <= i < L.length/2),使用后半部分的对应元素 L.data[L.length-1-i] 与之进行位置互换。

bool Reverse(SqList &L){
    // 在此处填入代码
}

【参考代码】

void Reverse(SqList &L){
    ElemTpye tmp;                             // 1. 定义中间变量,用于实现元素值互换
    for (int i = 0; i < L.length/2; i++)      // 2. 依次扫描顺序表前半部分的元素
        tmp = L.data[i];                      // 3. 以中位数为对称轴,将前后对应位置的元素进行互换
        L.data[i] = L.data[L.length-1-i];
        L.data[L.length-1-i] = tmp;
}

‍3. 从有序(递增)顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。

算法提示:
1.在递增顺序表中,处于s与t之间的元素必然是连续的,所以只需要删除区间[s,t]之间的元素即可。
2.查找区间下限s的位置,即第一个大于等于s的元素的位置ps
3.查找区间上限t的位置,即第一个大于t的元素的前一个位置pt
4.删除ps和pt之间的元素,并将pt后面的元素整体往前移动到位置ps

bool Delete_s2t(SqList &L, ElemType s, ElemType t){
    // 在此处填入代码
}

【参考代码】

bool Delete_s2t(SqList &L, ElemType s, ElemType t){
    // 删除有序顺序表L中所有值在s与t之间的所有元素
    int i j;
    if (s > t || L.length==0)                           // 1. 判断[s,t]区间的合理性及顺序表是否为空
        return false;
    for (i = 0; i < L.length && L.data[i]<s; i++);      // 2.  依次遍历顺序表,并查找第一个大于或等于s的元素
        if(i >= L.length)                               // 2.1 若所有元素均小于s,则直接返回Error,否则i被作为s的flag保存
            return false;
    for (j = i; j < L.length && L.data[j]<=t; j++);     // 3. 从j=i开始继续扫描顺序表,查找第一个大于t的元素,若找到则退出循环,j作为t的flag保存,若未找到,则将L.length的值复制给j,并作为t的flag保存
    for (int k = j; k < L.length; k++)                  // 4. 从j开始,依次将位置为j,j+1,...的元素赋值到位置为i,i+1,..
        L.data[i++] = L.data[k];                        //    前移替换相当于从s开始删除元素,直到t结束
    L.length = i;                                       // 5. 更新表长到i的新位置,此处i的长度为 s+(n-t)+1
    return true;                                        // 6. 操作完毕,返回true
}

【扩展练习】

‍1. 从有序递增顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。

算法提示:
1.在递增顺序表中,值相同的元素在逻辑上一定是连续的
2.初始时,将线性表的第一个元素定义非重复序列的flag元素
3.从第二个元素开始,依次判断当前元素与flag元素是否相同,若相同,则继续向后判断,若不同,则将当前元素插入到flag元素之后
4.依次扫描顺序表,直到表尾结束

bool DeleteDuplicate(SqList &L){
    // 在此处填入代码
}

【参考代码】

bool DeleteDuplicate(SqList &L){
    if (L.length == 0)
        return false;
    int i, j;                         // 1. 定义两个flag一个是非重复序列的尾元素i,另一个是检查元素指针j
    for (i=0, j=1; j<L.length; j++)   // 2. 逐元素扫描线性表
        if (L.data[i] != L.data[j])   // 2.1 若检查元素j不等于flag元素
            L.data[++i] = L.data[j];  //     则将检查元素j赋给flag元素,并将flag元素后移,否则继续向后扫描
    L.length = i+1;                   // 3. 更新表长
    return true;
}

‍2. 将两个有序递增顺序表合并为一个新的有序递增顺序表,并由函数返回结果顺序表。

算法提示:
1.本题的目标是将两个已知序列La和Lb合并到新顺序表Lc中,因此首先需要判断输入的线性表Lc的长度是否足够长,以满足La和Lb的输入
2.定义三个flag指针,分别指向La和Lb的第一个元素和新表Lc的第一个元素
3.从表头开始执行扫描,依次比较La和Lb的指针所指向的元素,将较小的一个插入到新表Lc中,同时将指向被移动元素的指针向后移动一位
4.当某个表线性表完成扫描后,将另外一个线性表中的元素依次插入到新表Lc中,直到表尾结束

bool MergeList(SqList La, SqList Lb, SqList &Lc){
    // 在此处填入代码
}

【参考代码】

bool MergeList(SqList La, SqList Lb, SqList &Lc){
    // 将有序顺序表La与Lb合并为一个新的有序顺序表Lc
    if (La.Length + Lb.Length > Lc.MaxSize)  // 1. 判断空表Lc的长度是否足够长,能够容纳La和Lb中的所有元素
        return;
    int i=0, j=0, k=0;                       // 2. 定义两个flag,一个是指向La的当前元素i,一个是指向Lb的当前元素j
    while (i<La.Length && j<Lb.Length){      // 3. 当La,Lb均未到达相应的表尾时,则依次比较i,j所指向的元素,并将较小的一个插入到Lc之后
        if (La.data[i] < Lb.data[j])         // 3.1 将i指向的值插入到Lc的末尾   
            Lc.data[k++] = La.data[i++];
        else
            Lc.data[k++] = Lb.data[j++];     // 3.2 将j指向的值插入到Lc的末尾
    }
    while (i<La.Length)                      // 4.1 若Lb先到达表尾,则依次将La剩余的元素插入到Lc的末尾
        Lc.data[k++] = La.data[i++];
    while (j<Lb.Length)                      // 4.2 若La先到达表尾,则依次将Lb剩余的元素插入到Lc的末尾
        Lc.data[k++] = Lb.data[j++];
    Lc.length = k;
    return true;
}

‍3. 从顺序表中删除其值在给定值s与t之间(包含s和t,要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。

算法提示:
1.依次扫描顺序表L,并使用k记录元素值在[s,t]之间的元素值的个数,默认k=0
2.对于当前扫描的元素,若其值不在[s,t]之间,则前移k个位置;否则执行k++
3.扫描结束后,将顺序表L的长度更新为L.length-k
4.每个元素只需要执行一次移动,因此算法复杂度为O(n)

bool Delete_s2tB(SqList &L, ElemType s, ElemType t){
    // 在此处填入代码
}

【参考代码】

bool Delete_s2tB(SqList &L, ElemType s, ElemType t){
    // 从顺序表L中删除其值在给定值s与t之间(包含s和t)的所有元素
    int i, k = 0;
    if (L.length == 0 || s>=t)                 // 1. 判断[s,t]区间的合理性及顺序表是否为空
        return false;                         
    for (i = 0; i < L.length; i++){            // 2. 依次扫描顺序表L   
        if (L.data[i] >= s && L.data[i] <= t)  // 2.1 若当前元素不在[s,t]之间,则k++
            k++;
        else
            L.data[i-k] = L.data[i];           // 2.2 若当前元素在[s,t]之间,则将当前元素前移k个位置
    }
    L.length -= k;                             // 2.3 更新线性表长度
    return true;
}

‍4. 给定一个含 n(n1)n(n \geq 1) 个整数的数组,请设计一个在时间上尽可能高效的算算法,找出数组中未出现的最小正整数。例如,数组 {-5, 3, 2, 3} 中未出现的最小正整数时 1;数组 {1, 2, 3} 中未出现的最小正整数时4。要求:
1). 给出算法的基本设计思想;
2). 根据设计思想,采用C或C++语言描述算法,并给出关键注释;
3). 说明你所设计算法的时间复杂度和空间复杂度。

算法提示:
1.要求在时间上尽可能高效,因此可以采用空间换时间的方法。
2.初始化一个全为零的数组B,其长度与原数组A相同。
3.检查数组A中的每一个元素,若存在正整数n,则将数组B[n-1]的值置为1。
4.待数组A中的每个元素都检查完后,遍历数组B。若能查找到以一个元素B[i]=0,则i+1即为目标最小正整数。

int FindMissMin(int A[], int n){
    // 在此处填入代码
}

【参考代码】

int FindMissMin(int A[], int n){
    int i, *B;                         // 标记一个新数组B
    B = (int *)malloc(sizeof(int)*n);  // 为新数组分配空间,其大小与原数组相同
    memset (B, 0, sizeof(int)*n);      // 初始化数组B,全部元素值为0
    for (i=0; i<n; i++){               // 扫描原数组A
        if (A[i] > 0 && A[i] <= n)     // 判断A[i]是否在[1,n]之间,若成立,则将新数组B的对应位置置为1
            B[A[i]-1] = 1;
    for (i=0; i<n; i++)                // 扫描新数组B,找到第一个值为0的下标i,并返回i+1即为结果
        if (B[i] == 0) 
            break;
    return i+1
}

[项目005] 顺序表的综合应用与案例分析 隐藏答案 | 返回首页