408线性表——顺序表

顺序表的考察范围覆盖多指针, 排序, 散列表, 二分, 顺序表逆置算法

1.顺序表删除元素

题目:顺序表删除元素x

思路:

方法1:

双指针

遇到一个等于x就停下来,此时k的值即为要删除元素下标,直到元素去替代他的位置,非x与非x之间始终相差k个单位,然后k++更新, 顺序表的长度即为k的个数, 因为k是遇到一个非k的数++, 因为k的个数就是非x的个数。

代码:

#include<iostream>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn] = {1, 2, 2, 3, 4, 5, 5, 6};
};
//方法1
void DeleteElem(SqList &l, int x) {
    int  i = 0, k = 0;
    while(i < l.length) {
        if(l.data[i] != x) {
            l.data[k] = l.data[i];
            k++;
        }
        i++;
    }
    l.length = k;
}
  

int main () {
    SqList l;
    l.length = 8;
    int x;
    x = 2;
    DeleteElem(l, x);
    for(int i = 0; i < l.length; i++)
        cout << l.data[i] << " ";
     
        return 0;
}

方法2:

双指针

遇到一个x就记出来k++, 直到遇到不是x的元素,将后面不为k的元素整体往前推k个单位就想当于删除x了,k为x 的个数。

代码:

#include<iostream>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn] = {1, 2, 2, 3, 4, 5, 5, 6};
};
//方法2
void DeleteSameElem(SqList &l, int x) {
    int  i = 0, k = 0;
    while(i < l.length) {
    while(i < l.length) {
        if(l.data[i] == x) {
            k++;
        } else {
            l.data[i - k] = l.data[i];
        }
        i++;
    }
    l.length = l.length - k;
    }
}
  

int main () {
    SqList l;
    l.length = 8;
    int x;
    x = 2;
    DeleteSameElem(l, x);
    for(int i = 0; i < l.length; i++)
        cout << l.data[i] << " ";
     
        return 0;
}

//我更喜欢方法2

如果是有序表删除满足范围[s, t]内的元素, 就直接找到第一个大于t的元素, 把t前面的元素整体删除即可

2.删除有序顺序表相同的元素

题目:删除有序顺序表的相同元素, 只保留一个

思路:因为是有序顺序表, 所以相同的元素肯定是连续的,关键就是找到两个数不同的时候的临界点, 用两个指针, 都从左边走,i指向第一个相同元素, j往右边跑,当j和i指向元素不同的时候, 就可以让a[j]后面的元素往前移动了, 移动到i指向的元素后面一个的位置,因为要将相同元素删只剩一个。

代码:

#include<iostream>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn] = {1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5};
};

void DeleteSame(SqList &l) {
    int i = 0, j = 0;
    for(i,j; i < l.length && j < l.length; j++) {
        if(l.data[j] != l.data[i]) {//相同就不管他, 让j接着往前走直到两个不同的时候就可以换到i后面了, 绕过相同的元素; 
            l.data[++i] = l.data[j];
        }
    }
    l.length  = i + 1; //i都是指向要剩下的哪个唯一数, 因此i+1就是最终元素的个数; 
}



int main () {
    SqList l;
    l.length = 11;
    DeleteSame(l);
    for(int i = 0; i < l.length; i++)
        cout << l.data[i] << " ";
     
        return 0;
}

//感觉顺序表删除都是用双指针比较多, 时间和空间复杂度最低, 其实和哪个删除满足某个范围内的数的那题很像, 只不过是将范围内的数换成了相同的数;

3.归并排序

题目:将两个有序顺序表合并为一个有序顺序

代码:

#include<iostream>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn];
};

//两个有序序列合并为一个有序序列; 
void Merge(SqList &a, SqList &b, SqList &c) {
    int i = 0 , j = 0, k = 0;
    while(i < a.length && j < b.length) {
        if(a.data[i] < b.data[j])//不能写成while,写成while就成死循环了,其中一个数组会一直往右边走,因为0肯定比另一个数组的正整数小;
            c.data[k++] = a.data[i++];
        else 
            c.data[k++] = b.data[j++];
    }
    while(i < a.length) c.data[k++] = a.data[i++];
    while(j < b.length) c.data[k++] = b.data[j++];
    c.length = k;
}


int main () {
    SqList a, b, c;
    int n = 3;
    int m = 4;
    a.length = 3;
    b.length = 4;

/*
测试数据 
1 3 5
2 4 5 6
*/
    for(int i = 0; i < n; i++) cin >> a.data[i];
    for(int i = 0; i < m; i++) cin >> b.data[i];
    Merge(a, b, c);
    for(int i = 0; i < c.length; i++)
        cout << c.data[i] << " ";
     
        return 0;
}

//归并排序是一个先分再合并的排序, 合并后的区间是有序的, 最后实现整个数组有序;

4.

二分

题目:

在有序的顺序表找x并和后面的数交换,如果x是最后一个元素不用交换, 不存在就插入顺序表,插入后仍是有序的

思路:有序顺序表找某个元素,第一个应该想到使用二分法找到该元素的下标,

代码:

#include<iostream>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn] = {1, 2, 4, 5, 6};
};

//两个有序序列合并为一个有序序列; 
void FindElem(SqList &l, int x, int n) {
    int left = 0, right = n - 1, mid, temp;
    while(left <= right) {
        mid = (left + right) / 2;
        if(l.data[mid] == x) {
            if(mid != n - 1) {
            temp = l.data[mid];
            l.data[mid] = l.data[mid + 1];
            l.data[mid + 1] = temp;                
            }
            break;
        }
        if(l.data[mid] < x) {
            left = left + 1;    
        } else {
            right = right - 1;
        }
    }
    if(left > right) {
        for(int i = n - 1; i >= left; i--) {//left的位置就是x应该插入的位置, 这个位置要腾出来给x, 因此是要包括Left的; 
            l.data[i + 1] = l.data[i];
        } 
        l.data[left] = x;
        l.length++;
    }
}

int main () {
    SqList c;
    c.length = 5;
    int x;
    cin >> x;
    FindElem(c, x, c.length);
    
    for(int i = 0; i < c.length; i++)
        cout << c.data[i] << " ";
     
        return 0;
}

5.

顺序表逆置算法,时间复杂度为O(n), 空间复杂度为O(1)

题目:

有一个序列, 里面存放两个序列,分别是a和b,长度分别是n, m, 要求原来a在b前面改成b在a前面

思路:

1.将整个数组反转, a1a2变成a2a1

  1. 区间a2逆转变正序

  1. 区间a1变成正序

最后变成a2正序a1正序

#include<iostream>
#define ElemType int
using namespace std;
const int Asize = 50;


//将所有元素倒置,考元素逆置的时候也可以使用;
bool Reverse(ElemType A[], int left, int right, int Asize) {
    if(left >= right || right >= Asize)
        return false;
    int mid = (left + right)/2;//求mid是为了求交换次数; 
    for(int i = 0; i <= mid - left; i++) {//mid - left才是真正的交换次数,这是要等于; 
                                        //mid - left = (right - left)/2; 
                                        //序列个数为偶数两两交换, 奇数最后一次自己跟自己交换; 
        ElemType temp = A[left + i];
        A[left + i] = A[right - i]; 
        A[right - i] = temp; //上面三行的left, right加减i是为了限制区间; 
    }
}
void ReverseList(ElemType A[], int m, int n) {
    Reverse(A, 0, n + m - 1, Asize);//让整个数组内的元素反转; 
    Reverse(A, 0, n - 1, Asize);//让序列a2反转变成正序;
    Reverse(A, n, n + m - 1, Asize);//让序列a1反转变成正序; 
}

int main () {
    int m = 5;
    int n = 3;
    int a1[m]  = {1, 2, 3, 4, 5};
    int a2[n] = {6, 7, 8};
    int A[Asize] = {0};
    int k = 0;
    for(int i = 0; i < m; i++)
        A[k++] = a1[i];
    for(int i = 0; i < n; i++)
        A[k++] = a2[i];
    
    for(int i = 0; i < k; i++)
        cout << A[i]; 
    cout << "\n";
    ReverseList(A, m , n);
    for(int i = 0; i < k; i++)
        cout << A[i];      

    return 0;
} 
//该算法的时间复杂度为O(n), 空间复杂度为O(1); 

6.

二分法求两个长度相同的有序顺序表合并后的有序顺序表的中位数, 时间复杂度为O(log n ), 空间复杂度为O(1)

题目: 求两个长度相同的有序顺序表的中位数(2011统考真题)

思路:

不断求两个序列的中位数,通过两个中位数的值来缩小序列的范围大小,最后两种情况

  1. 当当前两个序列的中位数相等,则为合并后的序列的中位数。

  1. 两个缩小的序列都只剩最后一个元素,小的哪个就是合并后序列的中位数。

代码:

#include<iostream>

using namespace std;

int MidSearch(int a[], int b[], int n) {
    int s1 = 0, s2 =0, d1 = n - 1, d2 = n - 1;
    int m1, m2;
    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) {//如果为奇数的时候, m1, m2都有可能是中位数; 
                s1 = m1;
                d2 = m2;
            } else { //偶数的时候,小的mid不可能是中位数; 
                s1 = m1 + 1;
                d2 = m2;
            }
        } else {
            if((s1 + d1) % 2 == 0) {
                d1 = m1;
                s2 = m2;
            } else {
                d1 = m1;
                s2 = m2 + 1;
            }
        }
    }
    return a[s1] < b[s2] ? a[s1] : b[s2];//不是返回mid,因为mid是上次遍历的mid
}
 
int main() {
    int n = 5;
    int a[n] = {11, 13, 15, 17, 19};
    int b[n] = {2, 4, 6, 8, 20};//中位数为4
    int mid = MidSearch(a, b, n);
    cout << mid;
    return 0; 
}

//难点不是很理解序列的中位数和合并后的序列的中位数有啥关系??

//因为是升序序列, 可以使用归并排序, 成一个新序列, 然后返回其中位数,时间复杂是O(n), 空间复杂度为O(1), 因为整理出来得哪个数组是要返回得, 不属于辅助数组。

这不是一般人想得出来得, 两个有序序列合并为一个有序序列, 我们应该想到归并排序

代码:

#include<iostream>

using namespace std;

//返回第i个元素,使用得是向上取整,向上取整就是9/2 = 4/5 = 5
//对付向上取整可以用最后一个元素的下标/2得到就是第i个元素得下标; 
int Merge(int a[], int b[], int c[], int n, int m) {
    int i = 0, j = 0, k = 0;
    while(i < n && j < m) {
        if(a[i] < b[j])
            c[k++] = a[i++];
        else
            c[k++] = b[j++]; 
    }
    while(i < n) c[k++] = a[i++];
    while(j < m) c[k++] = b[j++];
    return c[(k-1)/2];
}

int main() {
    int n = 5, m = 5;
    int a[n] = {11, 13, 15, 17, 19};
    int b[m] = {2, 4, 6, 8, 20};
    int c[m + n] = {0};
    int num = Merge(a, b, c, m, n);
    for(int i = 0; i < m + n; i++) {
        if(i != 0)
            cout << " ";
        cout << c[i];
    }
    cout << "\n" << num;
    return 0;
}

7.

多指针求多个数组的元素的组合满足某个特征值

题目:给出3个递增序列,3个数列分别取一个值记作a, b, c,求d = abs(a - b) + abs(b - c) + abs(c - a)最小的d

思路:

利用三个指针指向三个数组最左侧

//分析

核心思想:

数轴上若a < b < c,则d1 = 2Lac, 若b < a < c, 则d2 =2Lbc

若 c < b < a, 则d3 = 2Lca等等等

可以看出d的值即为最大值和最小值之间的距离, 当且仅当最小值向右移动的时候d才有可能变小

如若 a < b < c, a最为最小值, 我们称a, b, c为一个组合, 在数轴上表示出来为组合结构

1. 如果a在a,b之间移动,a还是最小值, 没有改变结构还是a < b < c, 此时d肯定比上一次小;

2. 如果a在b, c之间移动,改变了结构,变成了 b < a < c, 最小值变成了b, 然后b再往右边移动看看,看看d有没有变小,但不一定比上一个结构的d小;

3. 如果a在b, c之间移动,和2同理;

因此只需要不断改变最小值,不断更新组合结构并找到其Dis,才能找到最小的Dis;

疑问:为什么遍历的边界为while(i < n1 && j < n2 && k < n3)

答:

倘若组合为A, B, C且A<B<C, A, B, C的最大值分别为Amax, Bmax ,Cmax

A B C Amax Bmax Cmax

——————————————————————————————>数轴

由图可知A只要在Amax之间枚举都是有效的,B, C同理, 因此边界就是Amax, Bmax,Cmax

因为是递增序列,所以也就是各个数组的最后一个值。

代码:

#include<iostream>
#include<cmath>

using namespace std;

//最好时间复杂度为是O(min(n1, n2, n3)),最差的时间复杂度为O(n1 + n2 + n3),空间复杂度为O(1); 
void MinDis(int s1[], int s2[], int s3[], int n1, int n2, int n3) {
    int i = 0, j = 0, k = 0;//三个指针分别指向数组的最左侧; 
    int Min_Dis = 0x7fffffff;//ox7fffffff是int范围内的最大值; 
    int ans_s1 = s1[0], ans_s2 = s2[0], ans_s3 = s3[0];//距离最短的时候的组合;
    while(i < n1 && j < n2 && k < n3) {  
    int Dis = abs(s1[i] - s2[j]) + abs(s2[j] - s3[k]) + abs(s3[k] - s1[i]);
    //因为每次变更组合,那么在数轴上的结构就会改变,因此每次改变都要算一次距离;
    if(Dis < Min_Dis) {
        Min_Dis = Dis;
        ans_s1 = s1[i];
        ans_s2 = s2[j];
        ans_s3 = s3[k];
    }//这个组合结构比上一次的组合算出来的距离更短;     
    
    //找出三个数的最小值, 移动最小值, 更新组合结构; 
    //只有最小值向右侧移动的时候才有可能使得距离变小, 但是也可能会改变组合结构
    /*假如a, b, c为三个数组选出的三个数, 且a < b < c(方便讨论而已,便于判断) 
    a最为最小值
    1. 如果a在a,b之间移动,没有改变结构还是a < b < c, 此时Dis肯定比上一次小; 
    2. 如果a在b, c之间移动,改变了结构,变成了 b < a < c, 最小值变成了b, 然后b再往右边移动,看看d有没有变小,但不一定比上一个结构的Dis小; 
    3. 如果a在b, c之间移动,和2同理; 
    因此只需要不断改变最小值,不断更新组合结构并找到其Dis,才能找到最小的Dis; 
    */ 
    if(s1[i] < s2[j] && s1[i] < s3[k])//s1是最小的时候, 最小值向右移动才可能Dis变小; 
        i++;
    else if(s2[j] < s1[i] && s2[j] < s3[k])//s2为最小的时候 
        j++;
    else //s3为最小值的情况; 
        k++;
    }
    cout << Min_Dis << endl;
    cout << ans_s1 << " " << ans_s2 << " " << ans_s3;
}



int main() {
    int n1 = 3 , n2 = 4, n3 = 5;
    int s1[n1] = {-1, 0, 9};
    int s2[n2] = {-25, -10, 10, 11};
    int s3[n3] = {2, 9, 17, 30 ,41};
    MinDis(s1, s2, s3, n1, n2, n3);
    
    return 0;
} 

8.

散列表

题目1:

找出顺序表内的主元素, 主元素是出现次数最多且出现次数 > n/2的值

思路:

散列表时间换空间

代码:

#include<iostream>
#include<cstring>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn] = {1, 2, 4, 5, 6};
};


int FindMajor(int A[], int n) {
    int Max = A[0], MaxCnt = -1, MaxNum;
    for(int i = 1; i < n; i++) {
        if(A[i] > Max) {
            Max = A[i];
        }
    }
    int *hashtable = new int[Max + 1];
    memset(hashtable, 0, sizeof(hashtable));//记得给开辟的空间初始化,否则开辟的空间内的值初始可能不为0; 
    for(int i = 0; i < n; i++) {
        hashtable[A[i]]++;
    }
    
    for(int i = 0; i < n; i++) {
        if(hashtable[A[i]] > MaxCnt) {
            MaxCnt = hashtable[A[i]];
            MaxNum = A[i];
        }
    }
    delete []hashtable;
    return MaxCnt > n / 2 ? MaxNum : -1;
}


int main () {
        int A[] = {0, 5, 5, 3, 5, 1, 5, 7};
        int num = FindMajor(A, 8);
        cout << num;        
        return 0;
}

题目2:

找出顺序表缺失的最小正整数(最小正整数不包括0), 如1, 3, 5, 7缺失的最小正整数即为2;

思路:

散列表除了可以统计出现的次数还可以用来判断某元素是否出现过, 通过这个性质, 遍历散列表,为值为0就说明没出现过, 说明缺失

代码:

#include<iostream>
#include<cstring>
#define ElemType int
using namespace std;

const int Maxn = 50;

struct SqList {
    int length; 
    ElemType data[Maxn] = {1, 2, 4, 5, 6};
};


int FindElem(int a[], int n) {
    int Max = a[0];
    for(int i = 1; i < n; i++) {
        if(Max < a[i])
            Max = a[i];
    }
    int hashtable[Max + 1 + 1] = {0};//第一个+1是因为开的下标的范围为[0,Max-1]因此要多开一个单位,第二个+1是因为要判断最后一个元素的后面一个为位置是否为最小正整数 
    for(int i = 0; i < n; i++) 
        hashtable[a[i]]++;
    for(int i = 1; i <= Max + 1; i++) {
        if(hashtable[i] == 0)
            return i;
    } 
} 


int main () {
        int n = 5;
        int a[n] = {1, 2, 3, 4, 5};
        int num = FindElem(a, n);
        cout << num;
        return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值