数据结构复习(三)线性表②

一些用来复习和提高的题,包括选择题、算法设计题、统考真题等,不定期更新

注:代码部分为伪代码

目录

一、选择题

二、算法设计题

三、真题


一、选择题

(1)在一个长度为 n 的顺序表中删除第 i 个元素时(1 <= i <= n)需前移()个元素

A. n                                                                      B. i - 1

C. n - i                                                                  D. n - i + 1

答案:C

(2)在长度为n的非空线性表中实现插入操作,不是动态分配存储的情况下,i 的合法取值范围为()

A. 1<= i <= n                                                       B. 1 <= i <= n + 1

C. 0 <= i <= n - 1                                                 D. 0 <= i <= n

答案:B

二、算法设计题

(1)LA,LB为两个集合A和B,现求一个集合A = A ∪ B(数据结构C语言版(严蔚敏)P20

void union(List &La, List &Lb){
    La_len = ListLengh(La);
    Lb_len = ListLengh(Lb);          //获取长度
    for(i = 1; i <= Lb_length; i++){
        e = GetElem(Lb, i);       //获取Lb中的一个元素
        if(LocateElem(La ,e) == -1){     //判断La中是否有该元素,如果没有,则插入La中
            ListInsert(La, ++La_len, e);
        }
    }
}

(2)已知LA,LB中的数据元素按值非递减排列,合并LA,LB为一个新的线性表LC,且LC中的数据元素仍然非递减有序,如LA  =(3,5,8,11),LB  =(2,6,8,9,11,15,20),则LC  =(2,3,5,6,8,8,9,11,11,15,20)(数据结构C语言版(严蔚敏)P20

void MergeList(List &La, List &Lb, List &Lc){
    initList(Lc);
    i = j = 1;
    k = 0;    //i指La,j指Lb,k指Lc
    La_length = ListLength(La);
    Lb_length = ListLength(Lb);
    while((i < La_length) && (j < Lb_length)){//La,Lb非空
        GetElem(La, i, ei);
        GetElem(La, j, ej);
        if(ei < ej){     //比较哪个大,谁小就将其插入线性表LC
            ListInsert(Lc, ++k, ei);
            i++;
        } else{
            ListInsert(Lc, ++k, ej);
            j++;
        }
    }
    while(i <= La_len){   //La还没插入完
        GetElem(La, i, ei);
        ListInsert(Lc, ++k, ei);
    }
    while(j <= Ln_len){    //Lb还没插入完
        GetElem(Lb, i, ej);
        ListInsert(Lc, ++k, ej);
    }
}

(3)实现顺序表插入算法(数据结构C语言版(严蔚敏)P24

Status ListInsert_Sq(SqList &L, int i, ElemType e){
    if(i < 1 || i > L.length + 1) return ERROR;          //判断插入位置是否合法
    if(L.length >= L.MaxSize){     
        //判断顺序表是否满,此处动态分配,若不能动态分配,直接ERROR
        newbase = (ElemType*)realloc(L.data, 
                                      (L.MaxSize + LISTINCREASEMENT) * sizeof(LNode));
        //LISTINCREASEMENT是每次再分配内存时的大小增量,如一次再分配10个节点长度的内存
        if(!newbase) exit(OVERFLOW);                    //存储分配失败
        L.data= newbase;                                //分配新基址
        L.MaxSize += LISTINCREASEMENT;
        //return ERROR                                  //如果是静态存储分配的话
    }
    q = &(L.data[i-1]);                                 //获取插入位置
    for(p = &(L.data[L.length-1]); p >= q; --p){
        *(p + 1) = * p;                                 //插入位置及之后位置后移
    }
    *q = e;                                             //此处是地址
    ++L.length;
    return OK;
}

(4)实现顺序表删除算法,删除第i个元素(数据结构C语言版(严蔚敏)P24

Status ListDelete_Sq(SqList &L, int i, ElemType &e){
    if((i < 1) || (i > L.length)) return ERROR   //删除位置合法
    p = &(L/elem[i-1]);  //p为被删除的元素的位置
    e = *p;
    q = L.elem + L.length -1;   // q为表尾元素的位置
    for(++p; p <= q; ++p){
        *(p - 1) = *p;
    }
    --L.length;
    return OK;
}

(5)从顺序表中删除具有最小值的元素(唯一),并由函数进行返回,空出的位置由最后一个元素填补,若顺序表为空,则提示错误并退出运行(王道2023数据结构 P18

bool Del_Min(sqList & L, ElemType &e){
    if(L.length == 0){
        return false;
    }
    e = L.data[0]; //初始化最小值为顺序表第一个元素
    int pos = 0; //设置位置指针
    for(int i = 1; i < L.length; i++){
        if(L.data[i] < e){
            e = L.data[i];
            pos = i
        }
    }
    L.data[pos] = L.data[L.length-1]; 用最后一位填充
    L.length--; 减去长度,默认原有的最后一位元素不在顺序表中了,但实际物理存储中仍然在那个位置
    return true;
}

(6)逆置顺序表L,要求算法时间复杂度为O(1)(王道2023数据结构 P18

void Reverse(SqList &L){
    ElemType temp;
    for(i = 0; i < L.length / 2; i++){
        temp = L.data[i];
        L.data[i] = L.data[L.length-i-1];
        L.data[L.length-i-1] = temp;
    }
}

(7)从有序顺序表中删除值在s与t之间的所有元素(s<t),若s、t不合理或顺序表为空,则返回(王道2023数据结构 P18

bool Del_s_t(SqList &L, ElemType s, ElemType t){
    int i, j;
    if(s > t || L.length == 0){
        return false;
    }
    for(i = 0; i < L.length && L.data[i] < s; i++){  //寻找大于s的第一个值
        if(i > L.length){
            return false;
        }
    }
    for(j = i; j <L.length && L.data[j] <= t; j++)  //寻找大于t的第一个元素
    for(; j < L.length; i++, j++){
        L.data[i] = L.data[j];
    }
    L.length = i;
    return true;
}

(8)对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,删除顺序表中值为x的所有节点(王道2023数据结构 P18

void del_x_1(SqList &L, ElemType x){    //直接跳过值为x的值,用不为x的节点进行填充
    int k = 0, i;
    for(i = 0; i < L.length; i++){
        if(L.data[i] != x){
            L.data[k] = L.data[i];
            k++;
        }
    }
    L.length = k;
}

void del_x_2(SqList &L, ElemType x){    //k记录所有值为k的元素
    int k = 0, i = 0;
    while(i < L.length){
        if(L.data[i] == x){
            k++;
        } else{
            L.data[i-k] = L.data[i];
        }
        i++;
    }
    L.length = L.length - k;
}
void del_x_3(SqList &L, ElemType){   //设置头尾指针往中间遍历
    int i = 0;
    int j = L.length - 1;
    int k = 0;       //记录值为x的数量
    while(i < j){    //循环结束条件
        if(L.data[i] == x){
            k++;
            while(L.data[j] == x){  //找到右侧第一个不为x的值
                k++;
                j--;
            }
            L.data[i] = L.data[j];
            i++;
            j--;
        } else{
            i++;
        }
    }
    L.length = L.length - k;
}
//这是我自己写的算法,可能会有错误,欢迎指正

(9)在一维数组A[m+n]中依次存放两个线性表(a1,a2,a3,...,am)个(b1,b2,b3,...bn)。编写一个函数,让数组中两个顺序表位置互换,即(b1,b2,b3...,bn,a1,a2,a3,...am)(王道2023数据结构 P18

typedef int DataType;
void Reverse(DataType A[], int left, int right, int arraySize){
    //逆转从left到right间A中的元素
    if(left >= right || right >= arraySize){
        return;
    }
    int mid = (left + right) / 2;
    for(int i = 0; i <= mid - left; i++){
        DataType temp = A[left + i];
        A[left + i] = A[right - i];
        A[right - i] = temp;
    }
}

void Exchange(DataType A[], int m, int n, int arraySize){
    //先整体逆序,再前n个逆序,后m个再逆序
    Reverse(A, 0, m+n-1, arraySize);
    Reverse(A, 0, n-1, arraySize);
    Reverse(A, n, m+n-1, arraySize);
}

(10)线性表(a1,a2,a3,...an)中元素递增有序且顺序存储到一维数组中,设计一个算法,在较短时间内查找值为x的元素,找到后将其与其后继元素互换位置,若没有找到,则插入该x元素,并保持线性表仍然递增有序(王道2023数据结构 P18

void SearchExchangeInsert(ElemType A[], ElemType x){
    //时间要好,就折半查找,再插入
    int low = 0, high = n - 1, mid;
    while(low <= high){   //折半查找
        mid = (low + high) / 2;
        if(A[mid] == x) break;
        else if(A[mid] < x) low = mid + 1;
        else high = mid - 1;
    }
    if(A[mid] == x && mid != n-1){   //如果最后一个元素与x相等,则不用交换后继
        t = A[mid];
        A[mid] = A[mid+1];
        A[mid+1 = t;
    }
    if(low < high){   //没有该元素,则从high开始后移元素,再插入mid位置一个x
        for(i = n-1; i > high; i--) A[i+1] = A[i];
        A[i+1] = x;
    }
}

三、真题

(1)设将n(n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0,X1…Xn-1)变换为(Xp,Xp+1…Xn-1,X0,…Xp-1)。要求:(【2010统考真题】
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。

1)        

        u1s1,这个其实和算法设计题第9题类似。从原顺序表变化到现在的顺序表,同样可以先将现有的顺序表逆置,然后前n-p个逆置,后p个再逆置,就得到题中的结果,过程应为:

        Reverse(0, n-1);

        Reverse(0, n-p-1);

        Reverse(n-p, n-1);

        或者先逆置前p个元素,再逆置后n-p个元素,再逆置整个顺序表,效果是一样的

2)

        代码如下:

void Reverse(int R[], int from, int to){
    int i, temp;
    for(i = 0; i < (to - from + 1) / 2; i++){
        temp = R[from+i];
        R[from+i] = R[to-i];
        R[to-i]= temp;
    }
}

void Converse(int R[], int n, int p){
    Reverse(0, n-1);
    Reverse(0, n-p-1);
    Reverse(n-p, n-1);
}

3)

        三个Reverse函数的时间复杂度分别为O(n/2)、O((n-p)/2)、O(p/2),故时间复杂度应为O(n),仅采用了一个temp作为辅助空间,所以空间复杂度为O(1)

另解:

        使用辅助数组来实现,使用一个长度为p的辅助数组存放需要左移的p个元素,再将原数组之后的n-p个元素全部左移,最后将辅助数组中的元素再填充回去即可,时间复杂度也为O(n),但空间复杂度为O(p)

        代码如下:(也是自己写的,可能有错误,欢迎指正)

void Converse_2(int R[], int n, int p){
    int i;
    int t[p];  //定义辅助数组
    for(i = 0; i < p; i++){
        t[i] = R[i];   //存储前p个的值
    }
    for(i = 0; i < n-p; i++){
        R[i] = R[i+p];  //左移后n-p个元素
    } 
    for(i = 0; i < p; i++){
        R[i+n-p] = t[i];  //重新赋值
    }
    
}

 (2)一个长度为L(L ≥ 1)的升序序列S,处在第 [L/2] 个位置的数称为S的中位数。例如,若序列S=(11,13, 15, 17, 19)。则S的中位数是15,两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若S=(2,4,6,8,20),则S和S的中位数是11。现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数,要求:(【2011统考真
题】
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。

1)

        直接合并两个序列再找其中位数固然可行,但显然不是最优解,于是可以考虑中位数的特点,可分如下三种情况进行讨论

        ① 若a = b,则a或b即为所求的中位数,算法结束

        ② 若a < b,则证明所求的中位数肯定在a,b之间,故舍弃A中较小的一半,舍弃B中较大的一半,要求舍去的长度相等

        ③ 若a > b,则证明所求中位数在b、a之间,则与②相反即可

2)代码如下

int M_Search(int A[], int B[], int n){
    int s1=0, d1 = n-1, m1, s2 = 0, d2 = n-1, m2;
    //分别表示A、B中的首位数、末位数和中位数
    while(s1 != d1 || s2 != d2){   //循环找
        m1 = (s1 + d1) / 2;
        m2 = (s2 + d2) / 2;
        if(A[m1] == B[m2])
            return A[m1];
        if(A[m1] < B[m2]){   //情况②
            if((s1 + d1) % 2 == 0){  //若元素个数为奇数
                s1 = m1;
                d2 = m2;
            } else{
                s1 = m1 + 1;
                d2 = m2;
            }
        } else{    //情况③
            if((s2 + d2) % 2 == 0){  //若元素个数为奇数
                d1 = m1;
                s2 = m2;
            } else{
                d1 = m1;
                s2 = m2 + 1;
            }
        }
    }
    return A[s1] < B[s2] ? A[s1] : B[s2];
} 

3)

        时间复杂度为O(log2n),空间复杂度为O(1)

(3)已知一个整数序列A=(a0,a1,···,an-1),其中0<=ai<n(0<=i<n)。若存在ap1=ap2=···=apm=x且m>n/2(0<=pk<n,1<=k<=m),则称x为A的主元素。例如A=(0,5,5,3,5,7,5,5),则5为主元素;又如A=(0,5,5,3,5,1,5,7),则A中没有主元素。假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素;否则输出-1。要求:(【2013统考真题】
1)给出算法的基本设计思想。代码注释中
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释
3)说明你所设计算法的时间复杂度和空间复杂度。时间复杂度:O(n);空间复杂度:O(1)。

1)

        算法目标即找到数组中数目超过一半的元素是哪一个,大体流程应该是先标记出一个可能成为主元素的元素,然后判断其是否为主元素。故分为两步

        ① 选取候选的主元素,由于主元素需要超过一半,故平均来说,每两个元素中应该必有一个主元素,故采取如下方法进行选取:依次扫描数组,将遇到的第一个整数记为候选元素,同时计数器记为1,再往后遍历,如果下一个元素与之相等,则计数器加1,如果不相等,则减1,若计数器值为0,则选取下一个元素作为候选元素,从当前位置开始重复上述过过程。在这种选取的情况下,只要一个元素是主元素,则它必会被选中,即使它分布不均匀,当然被选中的元素却未必一定是主元素,所以需要进行第二步的判断。

        ② 再次扫描数组,统计候选主元素出现的次数,如果大于n / 2,则说明它是主元素,否则不是。

2)代码如下

int Majority(int A[], int n){
    int i, c, count = 1;
    c = A[0]
    for(i = 1; i < n; i++){ //第一次遍历整个数组
        if(A[i] != c){
            count--;
        } else{
            count++;
        }
        if(count < 1){
            c = A[i];
            count = 1;
        }
    }
    //计算是否为主元素
    if(count > 0){ // <0代表没有主元素
        for(i = count = 0; i < n; i++){   //第二次遍历整个数组
            if(A[i] == c){
                count++;
            }
        }
    }
    if(count > n / 2) return c;
    else return -1;
}

3)

        时间复杂度为O(n),空间复杂度为O(1)

        此外,如果采用先排序再统计的方法(快排),时间复杂度为O(log2n),先排序再统计,可以在排序后只扫描一次数组即可得出答案,因为每次遇到新的元素,count只需重置即可,若重置前count大于了n / 2,即可直接得到主元素,无需继续遍历。

        算法题只要解答正确,也能拿分,就算此题采用了O(n2)时间复杂度的算法,也能拿个七八分,所以统考无需花费大量时间在优化算法上,容易得不偿失。

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

1)

        时间上尽量高效,但未对空间做要求,可以定义一个动态分配存储的数组,直接映射原数组中的值,即新数组的下标对应原数组的值,原数组该值存在,则新数组中此处的值大于0,然后遍历新数组得到第一个值等于0的元素的下标即可。

2)代码如下

int findMissMin(int A[], int n){
    int i, *B;
    B = (int*)malloc(sizeof(int) *n);
    memset(B, 0, sizeof(int)*n);
    for(i = 0; i < n; i++){
        if(A[i] > 0 && A[i] < n){
            B[A[i]-1] = 1;
        }
    }
    for(i = 0; i < n; i++){
        if(B[i] == 0) break;
    }
    return i+1;
}

3)

        时间复杂度为O(n),空间复杂度为O(n)

(5)定义三元组(a, b, c)(其中a, b, c均为正数)的距离D=|a-b| + |b-c| + |c-a|。给定三个非空整数集合S1、S2和S3,按升序分别存储在3个数组中。设计一个尽可能高效的算法,计算并输出所有可能的三元组(a, b, c)(a∈S1, b∈S2, c∈S3)中的最小距离。例如S1 = {-1,0,9}, S2 = {-25,-10,10,11},S3 = {2,9,17,30,41},则最小距离为2,相应的三元组为(9,10,9)。要求:(【2020统考真题】
1)给出算法的基本设计思想。
2)根据设计思想,采用 C 或 C++语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。

1)

        观察题目易得当a = b = c时,距离最小

        其余情况,假设a < b < c,不失一般性,如下图所示

        L1 = |a - b|

        L2 = |b - c|

        L3 = |c - a|

        D = L1 + L2 + L3 = 2L3,其余情况与之类似,即问题的关键在于最大值和最小值之间的距离,故每次固定c找一个a,或固定a找一个c,使L3 = |c - a|最小即可

        故基本思想为:

        ① 适应Dmin记录最小距离

        ② S1,S2,S3的下标分别为i,j,k,循环遍历三个数组,执行如下操作,直至遍历结束

        a)计算A[i]、B[j]、C[k]的距离D,并与Dmin比较

        b)将A[i]、B[j]、C[k]中最小值的下标加1

        ③ 输出Dmin

2)代码如下

#define INT_MAX 0x7ffffff

int abs(int a){
    return a < 0 ? -a : a;
}

bool xls_min(int a, int b, int c){  //判断a是否为a,b,c中的最小值
    if(a <= b && a <= c) return true;
    return false;
}

int findMinofTrip(int A[], int n, int B[], int m, int C[], int p){
    //n,m,p分别为数组长度
    int i = 0. j = 0, k = 0, D_Min = INT_MAX;
    while(i < n && j < m && k < p && D_min > 0) {
        D = abs(A[i] - B[j]) + abs(A[i] - C[k]) + abs(B[j] - C[k]);  //计算距离
        if(D < D_min) D_min = D;
        if(xls_min(A[i], B[j], C[k])) i++;   //如果a最小
        else if(xls_min(B[j], A[i], C[k])) j++;  //如果b最小
        else k++;  //如果c最小
    }
    return D_min;
}

3)

        时间复杂度为O(n),空间复杂度为O(1)

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值