【数据结构】线性表 个人笔记 习题版

顺序表

一、抽象数据类型和节点结构图

typedef struct{
    elemtype data[maxsize];
    int length;
}

二、习题

(1)顺序表的递增有序,插入元素x,仍递增有序。

1、算法思想:先顺序查找到x的插入位置,然后对顺序表进行插入操作。

2、代码

//方法一
int find(Sqlist L, int x){
    for(int i = 0; i < L.length; i++){
        if(x < L.data[i]) //查找成功
            return i;
    }
}

void insert(Sqlist &L, int x){
    int p = find(L, x);  //调用find函数查找插入的位置
    for(int i = L.length - 1; i >= p; i--){
        L.data[i+1] = L.data[i];  //一次后移操作,为插入元素腾出空间
    }
    L.data[p] = x;  //插入元素
    ++L.length;   //增加顺序表的长度
}
//方法二
void insert(Sqlist &L, int x){
    int i;
    for(i = L.length - 1; i > 0; i--){
        if(L.data[i] > x) {
            L.data[i+1] = L.data[i]; //元素后移
        }
        else {          
            break; //跳出查找
        }
    }
    L.data[i+1] = x; //插入元素
    L.length ++;  //修改表长
}

3、时间复杂度:O(n)

(2)删除顺序表L中所有值为x的数据元素

方法一

1、算法思想:设置下标k = 0来记录不等于x的元素个数;在原来顺序表之上遍历,将不等于x的数据元素从头依次存入k所在位置的原顺序表中,并且k++,最终改顺序表长度为k。

2、代码

void delete(Sqlist &L, int x){
    int k = 0;  //记录不等于x的元素个数
    for(int i = 0; i < L.length; i++){
        if(L.data[i] != x){
            L.data[k++] = L.data[i];  //不等于x的元素存到k位置
        }
    }
    L.length = k; //修改表长为k
}

3、时间复杂度:O(n)

方法二

1、算法思想:用k记录顺序表L中等于x的元素个数,一边遍历顺序表一边记录k,并且将每一个扫到的不等于x的元素前移k个位置。

2、代码

void delete(Sqlist &L, int x){
    int k = 0; //记录等于x的元素个数
    for(int i = 0; i < L.length; i++){
        if(L.data[i] == x){
            k++; 
        }
        else{
            L.data[i - k] = L.data[i]; //将不等于x的元素前移k个位置
        }
    }
    L.length = L.length - k; //修改表长
}
    

3、时间复杂度:O(n)

(3)两个递增有序表合并成一个递增有序表

1、算法思想:设置两个下标指针 i 、j ,分别对这两个有序表遍历,同时对他们指向的元素进行比较,将二者中更小的元素存入新建的顺序表C中,并同时让该指针加一,直到有一个有序表遍历完,再将未遍历完的有序表元素依次存入顺序表C中。

2、代码

Sqlist *C = (int *)malloc(sizeof(int) * maxsize); //新建顺序表C,maxsize是动态分配的空间大小
C.length = 0;

bool merge(Sqlist A, Sqlist B, Sqlist &C) {
    if(A.length + B.length > C.maxsize) //要合并的两个有序表长度和大于申请的顺序表C的空间
        return false;
    int i,j,k; //i记录A的数据下标,j记录B的数据下标,k记录C的数据下标
    i = j = k = 0;
    while(i < A.length && j < B.length) { //遍历A和B两个有序表,直至其中一个表遍历完
        if(A.data[i] < B.data[j]) { //存入较小的数据到新表C中
            C.data[k++] = A.data[i++];
        }
        else {
            C.data[k++] = B.data[j++];
        }
    }
    //将未遍历完的另一个表剩余的元素依次存入C中
    while(i < A.length) {
        C.data[k++] = A.data[i++];
    }
    while(j < B.length) {
        C.data[k++] = B.data[j++];
    }
    C.length = k; //修改C表的长度
    return ture;
}

3、时间复杂度:O(n + m),n为A表长度,m为B表长度。

(4)从有序表L中删除所有重复的值

1、算法思想:该表是有序表,则重复的元素一定是在连续的位置上,设置两个快慢指针 i、j 。当 i 和 j 指向的元素值相同时,i 的值不变,j 的值加一;当 i 和 j 指向的元素不同时,将快指针 j 指向的元素赋值给慢指针 i 后一个位置,同时 i 加一。

2、代码

bool delete(Sqlist &L) {
    if(L.length == 0) {
        return false; //顺序表长度值不合法,返回false
    }
    int i, j;
    for(i = 0, j = 1; j < L.length; j++){
        if(L.data[i] != L.data[j]){
            L.data[i++] = L.data[j]; //快慢指针的值不相等,需要保留,将快指针指向的元素放到慢指针的位置
    }
    L.length = i + 1; //修改表的长度
    return ture;
}

3、时间复杂度:O(n)

(5)将A(a1, a2, ..., am, b1, b2, ..., bn)转换成A(b1, b2, ..., bn, a1, a2, ..., am)。

1、算法思想:先对整个序列进行逆置操作(此函数导入的元素为要逆置的数组以及逆置位置的初始下标和终止下标),然后对前n个元素逆置,再对后m个元素逆置。

2、代码

void revert(int &A[], int x, int y) { //逆置函数,将数组A从x到y部分数据进行逆置
    int mid = (x + y) / 2;
    for(int i = 0, j = 0; i + x <= mid; i++, j++){ //前后元素进行对称交换
        int temp = A[i + x];
        A[i + x] = A[y - j];
        A[y - j] = temp; //第一个数(x+i)与最后一个数(y-j)交换
    }
}

void change(int &A[], int m, int n){
    revert(A, 0, m + n - 1); //对整个表进行逆置
    revert(A, 0, n - 1); //对前部分进行逆置
    revert(A, n, m + n - 1); //对后部分进行逆置
)

3、时间复杂度:O(n)

(6)设计一个时间复杂度尽可能小的算法,找出大小为n的数组A中未出现的最小正整数。

1、算法思想:应用了空间换时间的思想。首先使用动态分配的方法分配一个数组 B[n + 1] 用于记录A中是否出现了 1~n 中的正整数,即用B的下标来记录A的元素值。先把B数组元素初始化为0,然后扫描A,当元素大于0小于n的时候,将B相应的下标位置的值从0改为1,即 B[ A[i] ] = 0 ,最后将B遍历,将第一个 B[i] 为0的位置返回(也就是返回数组A中未出现的最小正整数)。

如果按一般的思想,先对数组A进行排序,然后再遍历找未出现的最小正整数,那么时间复杂度最小为O(nlogn)(堆排序、归并排序、快速排序等)。

2、代码

int find(int A[], int n){
    int *B = (int *)malloc(sizeof(int) * (n + 1)); //动态分配数组B
    int i;
    for(i = 0; i < n + 1; i++){ //对数组B初始化为0
        B[i] = 0;
    }
    for(i = 0; i < n; i++){
        B[A[i]] = 1;  //用B数组的下标存储A数组的值
    }
    for(i = 0; i < n + 1; i++){
        if(B[i] == 0){
            break; //找到最小未出现的正整数
        }
    }
    free(B); //释放B数组空间资源
    return i;
}

3、时间复杂度:O(n)

(7)若一个整数序列中有过半的相同元素,则称该元素为主元素,设计算法找出数组A[n]中的主元素(其中0<A[i]<n),若存在主元素则输出,若不存在则返回-1,要求时间复杂度尽可能小。

1、算法思想:空间换时间的思想,动态分配一个数组B[n],用于对A的元素出现频次进行记录,用B的下标来记录A中元素值,将B中元素值先初始化为0,对A进行遍历,每扫描到一个元素则将B中对应的下标位置的值加一,最终对B从1到n进行遍历,使用一个参数max来记录B中的最大值,并用参数k来记录max值对应的B的下标位置,遍历完之后判断max是否大于n/2,是则输出k,否则返回-1。

(2)代码:

int fun(int A[], int n){
    int *B=(int *)malloc(sizeof(int) * (1 + n));//动态分配数组B
    for(int i = 0; i < n; i++){
        B[i] = 0; //对B数组初始化为0
    }
    int k, max = 0; //设置k记录最大值时的i值,max记录B中的最大值
    for(int i = 0; i < n; i++){
        if(A[i] >= 0 && A[i] <= n){ //A[i]不合法则不存入B中
            B[A[i]]++;//用B的下标记录A中元素出现的频次
        }
    }
    for(int i = 0; i < n; i++){
        if(B[i] > max){
            k = i;  //用k记录最大值max时的i值
            max = B[i];  //用max记录B中的最大值
        }
    }
    free(B);
    if(max > n / 2)
        return k; //是主元素,返回主元素,即B[i]最大值的下标(用k存储)
    else
        return -1; //不是主元素则返回-1
}

3、时间复杂度:O(n)

三、顺序表思想

1.已经遍历过的顺序表可以加以运用,以此来完成算法原地运作。

2.快慢指针 i,j 来解决删除问题。

3.设置k来记录最终修改后的顺序表长度,算法最后别忘了修改顺序表长度length。

4.函数模块化思想解决逆置。

5.用空间换时间的思想解决问题:分配一个数组用它的下标来存储另外一个数组的元素值。

6.一切算法的编写都是基于输入元素(类型,个数,内容),这一点在算法模块化中极为重要,返回值的类型也需要注意,以后的学习中将多次提及。

四.顺序表课后习题:

1.将一个元素为n个的有序表L分为两个有序表,其中一个只含有奇数,另外一个只含有偶数,要求最多分配n个空间。

2.从有序表L中删除值在s到t之间(包含s,t)的所有元素。

3.将有n个元素的数组A中的所以元素循环右移k个位置,要求只用一个元素大小的附加存储。

4.商家有一堆水果混合在一起,每种水果类型用编号记录,要求只购买两种类型的水果,但是要求水果数量最多的两种水果,这一堆水果以数组A[n]=(a1,a2,…,an)这种形式存储,算法最终返回两种水果的编号之和(水果编号都小于n)。

5.删除无序表L中所有重复的元素(元素值都小于n)。

(注:习题解答之后会更新.....)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值