王道数据结构——第二章顺序表算法题

第二章 表

顺序表算法题

1.

寻找最小元素返回其值并删除,同时该位置由最后一个元素补充

思路:遍历整个顺序表,记录下最小值的位置,将其删除。

bool Del_Min(Sqlist &L,int &e){
    //判空
    if(L.length == 0){
        return false;
    }
    int pos = 0;
    for(int i = 0;iL.data[i]){//最小值比较,若当前最小值大于当前遍历值,则更改标记位置。
            pos = i;
        }
    }
    e = L.data[pos];
    L.data[pos] = L.data[length-1];
    L.length--;
    return true;
}

2.

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

思路:采用二分法,将前面的元素和后面的元素互换,因为要求空间复杂度为O(1),因此不能重新开辟一个顺序表逆序赋值。

bool Reverve(SqList &L){
    if(L.length == 0){
        return false;
    }
    int temp;
    for(int i = 0;i

3.

对于长度为n的线性表L,设计一个时间复杂度为O(n),空间复杂度为O(1)的算法,删除线性表中所有值为x的数据元素。

思路:

(一)建立一个新表存储表中存储不等于X的元素。

bool del_X_1(SqList &L,int x){
    if(L.length == 0){
        return false;
    }
    int i,k=0;
    for(i = 0;i

(二)用k记录顺序表L中的x的元素个数,边扫描边统计k,将不等于x的元素往前移动k个位置。

bool del_x_2(SqList &L,int x){
    if(L.length == 0);
    int i,k=0;
    for(i = 0;i

4.

从有序顺序表中删除其值在s和t之间的元素(s

思路:有序顺序表,意味着必然是后面的元素大于前面的元素,因而必然不能出现t>s的情况,这是不合理的情况。应该找到s的位置(第一个删除的元素)和t的后一个位置(移动的第一个元素)。

bool Delete_s_t(SqList &L,int s,int t){
    if(s>=t||L.length == 0){
        return false;
    }
    for(int i=0;i=length){//所有元素都大于s
        return false;
    }
    for(int j = i;j=length){//所有元素都大于t
        return false;
    }
    for(;j

5.

从顺序表中删除其值在s和t之间的元素(包含s和t,s

思路:与上一题不同的是,这道题并不是有序顺序表,因此无法求出一个区间。可以与前面删除值为x的方法一样,用k记录值在s和t之间的个数,边扫描边统计,当扫描到一个值不属于这个区间的元素时,往前移动k个元素。

bool Delete_s_t_1(SqList &L,int s,int t){
    //判断空表和取值的合理性
    if(s>=t||L.length == 0){
        return false;
    }
    int k =0;
    for(int i=0;i=s&&L.data[i]<=t){
            k++;
        }
        else{
            L.data[i-k] = L.data[i];
        }
    }
    L.length = L.length - k;
    return true;
}

事实上,也可以采用上面删除值为x的方法,构造一个新表,当扫描到一个值不属于s到t这个区间的元素时,就将他加入新表中。

bool Delete_s_t_2(SqList &L,int s,int t){
    //判断合理性。
    if(s>=t||L.length == 0){
        return false;
    }
    int k = 0;
    for(int i = 0;it){
            L.data[k++] = L.data[i];
        }
    }
    L.length = k+1;//数组下标和位序的关系。
    return true;
}

6.

从有序顺序表中删除所有值重复的,使表中所有的元素都不重复

思路:有序顺序表,因此知道重复值只能是一片连续的空间,

(一) 可以用k扫描整个表,边扫描边统计,如果当前元素的下一个元素相同,k加1,继续扫描,直到扫描不是重复元素时,将其元素往前移动k个位置。

bool Delete_rep(SqList &L){
    //判空
    if(L.length == 0){
        return false;
    }
    int k = 0;
    for(int i = 1;i

(二)构建一个新表,将不重复的值添加进去

bool Delete_rep(SqList &L){
    //判空
    if(L.length == 0){
        return false;
    }
    int k = 0;
    for(int i = 1;i

(三) 用i,j两个游标,当j扫描到与L.data[i]不同的值时,就令L.data[i+1]=L.data[j];

bool Delete_rep(SqList &L){
    if(L.length == 0){
        return false;
    }
    int i ,j;
    for(i = 0,j = 1;i

7.

将两个有序顺序表合为一个有序顺序表,并由函数返回结果顺序表

思路:构造一个新表,按顺序不断取两个顺序表中小的部分加入到表中,然后,如果还有剩余,将剩余的部分加入到表尾

bool Merge(SeqList a,SeqList b,SeqList &c){
    //有点问题,这里需要说明是静态分配还是动态分配,因为MaxSize这个成员变量在静态分配中是不存在的。
    if(a.length+b.length>c.MaxSize){
        IncreaseSize(c,10);
    }
    int i=0,j=0,k=0;
    while(ib.data[j]){
            c.data[k++]=b.data[j++];
        }
    }
    //当a还有剩余时
    while(i

8.

已知在一维数组A[m+n]中依次存放两个线性表(a1,a2...am)(b1,b2...bn)。编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2...bn)放在(a1,a2...am)前面。

思路:前面使用了二分法逆置过表,这题也一样,先将数组逆置为[bn…a1],再对两个线性表再逆置一次即可所得

typedef int DataType;
bool Reverse(DataType A[],int left,int right,int arraySize){
    //左大于右,或者右大于数组长度时不合法
    if(left>=right||right>=arraySize){
        return false;
    }
    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){
    //注意a1..am,a数组下标是从0到m-1,同样,b数组下标是从m到m+n-1
    Reverse(A,0,m+n-1,arraySize);//对整个数组逆置
    Reverse(A,0,n-1,arraySize);//对b逆置
    Reverse(A,n,m+n-1,arraySize);//对a逆置
}

注意这里二分法区间的选择有点不同。

i <= mid - left
因为mid指的是某个区间的中间那个数,但是这个区间实际的元素个数应该为(mid - left)*2,而根据算法能够明白我们需要处理的是当前区间的前半段,那么自然是从i = 0 --> i <= mid - left

在前面我们知道顺序表的逆置时,left必定等于0,因此此时mid=(0+length-1)=length/2;i

但是在这里,我们并不确定left的起点,right的起点,假设有这么一个数组a[1,2,3,4,5],假设从a[1]到a[3],中间数应该是a[2],显然mid=(left+right)/2

i=2-1=1只需要逆置一次,记住结论。

9.

线性表中的元素递增有序且按顺序存储在计算机内,要求设计一个算法;完成用最少时间在表中查找数值为x的元素,若找到,则将其与后继元素相交换,若找不到,则将其插入表中并使表中元素仍递增有序。

思路:显然,遍历元素的时间复杂度为O(n)会很慢,当然也很暴力,但是题目要求是最少时间,因此我们可以使用折半查找的思路,若中间点值大于x,则x的范围在左边,若中间点的值小于x,则x的范围在右边。不断的二分查找下去缩小范围,即可确定x的范围,因为这是一个递增有序的线性表,因此我们知道x的元素必然在找到的点的右边。这样的时间复杂度为O(log2n)

bool SearchExChangeInsert(int a[],int x){
    int left=0,right=n-1,mid;
    while(left<=right){
        mid = (left+right)/2;
        //等于,直接找到
        if(a[mid]==x){
            break;
        }
        //小于,x比中间值大,递增,因此x在中间点的右边,令left=mid
        else if(a[mid]x){
            right=mid-1;
        }
    }
    //循环结束,确认最终的中间点。注意,如果是最后一个元素是没有后继节点的
    if(a[mid]==x&&mid!=n-1){
        int temp = a[mid];
        a[mid]=a[mid+1];
        a[mid+1]=temp;
    }
    //查找失败,插入元素。插入位置在left的右边。即需要将left+1到n-1的元素都往后移动一位。这里需要分清楚顺序表的插入操作。
    if(left>right){
        for(i=n-1;i>left;i--){
            a[i+1]=a[i];
        }
        a[i+1]=x;
    }
}

10.

设将n(n>1)个整数存放到一维数组R中,设计 一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0(X0,X1...,Xn-1)变换为(Xp,Xp+1,..,Xn-1,X0,X1,..,Xp-1)

(1)给出算法的的基本设计思想

这是一道移动元素的题目,是将第p个元素及其p元素之后的元素往前移动p个单位。可将这个数组看做是数组ab转换成数组b a(a代表数组的前p个元素,b数组代表数组的剩下的n-p个元素,可以先将a逆置得到a逆b,再将b逆置得到a逆b逆,最后对两个数组逆置得到b a。设Reverse函数执行数组元素逆置的操作,对abcdefgh向左循环移动3个位置的过程如下:

Reverse(0,p-1)得到cbadefgh;//此时数组为(xp-1,xp-2,…,x0,xp,xp+1,…,xn-1)

Reverse(p,n-1)得到cbahgfed;//此时数组为(xp-1,xp-2,…,x0,xn-1,xn-2,…,xp+1,xp)

Reverse(0,n-1)得到defghabc;//次数数组为(xp,xp+1,…,xn-1,x0,…,xp-1)

也可以先对ab整体逆置,再分别对两部分进行逆置。跟第8题思路一样。

Reverse(0,n-1);//此时数组为(xn-1,xn-2,…,xp,xp-1,…,x0)

Reverse(0,n-p-1);//此时数组为(xp,xp+1,…xn-1,xp-1,…x0)

Reserve(n-p,n-1);//此时数组为(xp,…xn-1,x0,…,xp-1)

(2)根据设计思想,采用c或c++或java语言描述算法,关键之处给出注释;

typedef int DataType;
bool Reverse(DataType a[],int left,int right,int arraySize){
    //判断输入位置是否合法
    if(left>=right||right>arraySize){
        return false;
    }
    int mid = (left+right)/2;
    int temp;
    for(int i = 0;i

相比较于第一种写法来说,因为并不像是第8题那样直接告诉了前面数组元素个数和后面数组元素个数,因此在使用第二种写法的时候,很容易混淆数组下标的关系。

(3)说明你所设计算法的时间复杂度和空间复杂度。

时间复杂度:上述三个Reverse函数的时间复杂度分别为O(p/2),O(n-p),O(n/2).

空间复杂度:显然我们没有申请新的空间进行操作,因此这是一个常数,空间复杂度为O(1)

(4)另解

思路:设计一个辅助数组s,存放a数组的前p个元素,同时将a数组往前移动,最后再将辅助数组s中的元素放入a数组。

typedef int DataType
bool Exchange(DataType a[],int p,int arraySize){
    DataType s[]=0;
    int k=0;
    for(int i = 0;i

在这种算法下,时间复杂度为O(n),由于开辟了p个内存空间,因此空间复杂度为O§;

11.

一个长度为L(L>=1)的升序序列,处在第[L/2]个位置的数成为S的中位数。假如S1=(11,13,15,17,19),则S的中位数是15,两个序列的中位数是含他们所有元素的升序序列的中位数。现在有两个等长序列A和B,试设计一个在时间和空间上都尽可能高效的算法,找出两个序列A和B的中位数,要求:

(1)给出算法的基本设计思想。

这是两个等长的有序序列,则中位数必然在序列中间。如果我们分别求出两个序列的a和b,

如果a==b,则代表着a或者b必然是两个序列的中位数。

如果a

如果a>b,跟上面一样,将a看做b,b看做a,那么就需要将a缩小,b放大。

因此算法思路如下:

先分别求两个升序序列A,B的中位数,设为a,b,求序列A、B的中位数的过程如下

(一)若a = b,即a或b即所求的中位数,算法结束

(二)若a

(三)若a>b,舍弃a的较大的一半以及b较小的一半,要求两次舍弃的长度相等。

在保留的两个升序序列中,重复过程第一到第三步,知道两个序列均只含一个元素时为止,较小者即为所要求的的中位数。

(2)根据设计思想,采用c或者c++或java语言描述算法,关键之处给出注释。

int M_Search(int a[],int b[],int n){
    int left1 = 0,right1 = n-1,m1,left2 = 0,right2 = n-1,m2;
    //扫描表,如果已经扫描完了则退出,退出条件很好理解,当扫描到中间值时,序列只含一个元素,left必然等于right,但是要判断两个,因为他可能在a中也可能在b中
    while(left1!=right1||left2!=right2){
        m1 = left1+right1/2;
        m2 = left2+right2/2;
        //满足条件1
        if(a[m1] == b[m2]){
            return a[m1];
        }
        //满足条件2
        else if(a[m1]b[m1]){
            if((left2+right2)%==0){
                left2 = m2;//舍弃掉b中间点以前的部分且保留中间点
                right1  = m1;//舍弃掉a中间点以后的部分且保留中间点
            }
            else{
                left2 = mid+1;//舍弃掉b中间点以前的部分且不保留中间点
                right1 = m1;//舍弃掉a中间点以后的部分且保留中间点
            }
        }
    }
    return a[left2]

(3)说明你所设计的算法的时间复杂度和空间复杂度

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

(4)另解

显然,王道书上的答案是最优解,但是却并不容易理解,在考场上如果想不到最优解的话,可以暴力破解试试。

第一种思路:

显然,在看到这个题的时候,我脑海里想的就是归并排序了,注意到这是两个有序表,注意到没有,这不就是第7题的合并吗,合并之后再求中位数就简单了。

int Merge(SeqList A,SeqList B,SeqList &C){
    if(A.length+B.length >C.MaxSize){
        IncreaseSize(C,10);
    }
    int i = 0,j = 0,k = 0 
    while(i

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

第二种思路

我们只需要按顺序找到中间位置的那个元素即可

int FindMid(int *a,int *b,int len){
    int i = 0,j = 0,k = 0;
    //数组下标从0开始,我们找到第len-1个元素
    for(;k

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

12.

已知一个整数序列A=(a0,a1,…,an-1),其中0<=ain/2,则称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。要求:
(1)给出算法的基本设计思想

读懂该题,该题是找出主元素,主元素的意思是有一个元素在数组中出现的次数超过数组元素的个数的一半。

算法的策略是从前向后扫描数组元素,标记处一个可能成为主元素的元素num,然后重新计数,确认NUM是否是主元素。

算法分为两步:

1、选取候选的主元素。依次扫描所给的数组中的每个整数,将第一个遇到的整数NUM保存到c中,记录NUM的出现次数为1.若遇到的下一个整数仍等于NUM,则计数+1,否则计数减1;当计数减到0的时候,将遇到的下一个整数保存到c中,计数重新即为1,开始新一轮计数,即从当前位置重复上述过程,直到扫描完全部数组元素。

2、判断c中元素是否是真真的主元素。再次扫描该数组,统计c中元素出现的次数,若次数大于n/2,则为主元素,否则,序列中不存在主元素。

简化版本:看起来很复杂的解释,实际上可以这么看,若A中存在主元素,则主元素出现的次数必然大于n/2,而这个算法的意义是,将两个不相同的数一直抵消,直到最后剩下的数即为可能的主元素。因为主元素的次数必然大于n/2,因此在两两抵消后,主元素必然可能剩下一个或多个。

(2)算法实现

int Majority(int A[],int n){
    int i,c,count = 1;//设置计数器
    c = A[0];//默认第一个元素为候选主元素
    for(i = 1;i0){
        //计数这个元素是否大于n/2;
        for(i = count = 0;in/2){
        return c;
    }
    else{
        return -1;
    }
}

(3)时间复杂度和空间复杂度

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

(4)显然,这是最优解法,但是考场上如果想不到的话,可以暴力破解

思路一:穷举,找出每一个元素的出现次数,时间复杂度为O(n2)

int Majority(int A[],int x){
    for i,j,c,count1 = count2 = 0;
    c = A[0];//默认第一个元素为候选主元素
    for(int i = 0;icount1){
            c = A[i];
        }
        else{
            c = A;
        }
    }
    //判断当前主元素是否为主元素
    for(int i = count1 = 0;in/2){
        return c;
    }
    else{
        return -1;
    }
}

思路2:也是进行计数,开辟一个新的数组b,数组b的下标为数组a中各个元素的数值。时间复杂度为O(n),但是有个缺点,如果有负数的话就不适用了。

int Majority(int A[],int n){
    int i,c,flag = -1;
    c = A[0];
    int B[];
    int max = A[0];//标记最大元素,以便确认数组b的长度
    for(int i = 1;in/2){
            c = i;
            flag = 0;
        }
    }
    if(flag == 0){
        return c;
    }
    else{
        return -1;
    }
}

13.

给定一个含(n>=1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。例如数组{-5,3,2,3,}中未出现的最小正整数是1,数组{1,2,3}中未出现的最小正整数是4,要求:

(1)给出算法的基本设计思想

要求在时间上尽可能高效,因此采用空间换时间的办法。分配一个用于标记B[n],用来记录A是否出现了1-n中正整数,B[0]对应正整数1,B[n-1]对应正整数n,初始化B中全部为0.由于A中含有n个整数,因此可能返回的值是1-n+1;当A中n个数签好为1-n是返回n+1.当数组A中出现了小于等于0或大于n的值时,会使1-n中出现空余位置,返回的结果必然在1-n中,因此对于A中出现了小于等于0或大于n的值,可以不采取任何操作。

算法流程:

1、从A[0]开始遍历A,如果0

简化版本:和我的思路差不多,不过感觉好蠢,为啥一定要让i处于0到n-1之间啊。0本身就不属于正整数,直接将b[0]设为0不就可以了吗,这样,a元素的值即为b数组的下标。

(2)实现算法

int FindMissMin(int A,int n){
    int i,*B;//标记数组
    B = (int *)malloc(sizeof(int)*n);//分配n个空间
    memset(B,0,sizeof(int)*n);//这个函数可以学一下,这样就不需要循环赋值了。
    for(i = 0;i=1&&A[i]<=n){
            B[A[i]-1] = 1;
        }
    }
    for(i = 0;i

(3)时间复杂度

时间复杂度:遍历A一次,遍历B一次,两次循环内操作步骤为O(1)量级,因此时间复杂度为O(n)。空间复杂度:额外分配了B[n],空间复杂度为O(n);

(4)暴力破解:设一个数组b,数组b的下标为数组a的元素的值,数组b的值为该元素值出现的次数。在统计完之后,遍历数组b,找到第一个值为0的值即为最小正整数,时间复杂度为O(n)

int FindMissMin(int A[],int n){
    int b[],flag;
    flag = -1;
    int max = A[0];
    //找出最大元素,确定b的长度。
    for(int i = 0;i

14.

定义三元组(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)中的最小距离。要求:

(1)给出算法的基本设计思想。

显然,若a=b=c,距离最小。

若a<=b<=c,则D = D=|a-b| + |b-c| + |c-a| 求的就是 2|c-a|即c和a之间的距离,如下图所示

所以要使得D最小,就是求|c-a|的距离最小,于是算法思路就是固定住c,找a,使得|c-a|最小。

算法思路是:

  1. 使用Dmin记录所有已处理的三元组的最小距离,初值为一个足够大的整数。
  2. 使用集合s1,s2,s3分别保存在数组A,B,C。数组的下标变量i=j=k=0,当i<|s1|,j<|s2|,k<|s3|时(s表示元素个数),循环执行下面的3~6;
  3. 计算(A[I],B[J],C[K])的距离D(计算D)
  4. 若D
  5. 将A[i],B[j],C[k]中的最小值的下标+1(对照分析:最小值a,最大值c,这是c不变而更新a,试图寻找更小的距离D)
  6. 输出Dmin,结束
  7. 简单来说,我们只需要固定住最大的点不动,一直找能使当前距离变得更小的最小点即可。但是为什么是+1呢,我之前想了很久,之后我突然发现,这是三个升序数组,因此在保持最大点不懂,最小值向后遍历一位就是增大。。。也就是最小值在靠近最大值,距离自然在减小。

(2)根据设计思想,采用 C 或 C++语言描述算法,关键之处给出注释。

#define INT_MAX 0X7fffffff
int abs_(int a){
    //取绝对值
    if(a<0)
        return -a;
    else
        return a;
}
bool xls_min(int a,int b,int c){
    //a是否是这三个参数中最小的
    if(a<=b&&a<=c)
        return true;
    else
        return false;
}
int findMinTrip(int A[],int B[],int C[],int n,int m,int p){
    //Dmin用来记录最小值,i,j,k用来遍历三个数组
    int i = 0,j =  0,k = 0,Dmin = INT_MAX;
    while(i

(3)说明你所设计算法的时间复杂度和空间复杂度。

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

你可能感兴趣的:(排序算法,算法,数据结构)