线性结构——顺序表

本文详细探讨了顺序表的遍历、元素移动(赋值式和交换式)以及查找、删除和插入操作。在移动元素时,通过交换式移动实现了从O(n²)到O(n)的时间复杂度优化。同时,介绍了无序无重复值和有重复值情况下的删除操作,并提出更高效的算法。总结了顺序表操作的关键点和时间复杂度降低的方法。
摘要由CSDN通过智能技术生成

顺序表专题


前言

在这一专题完成顺序表的遍历,移动线性表元素以及增删改查。


一、遍历顺序表

示例:有顺序表a={3,2,5,8,4,7,6,9},遍历三遍。

#include<stdio.h>
void printArray(int a[],int n,int k){
    for(int i = 0;i<k;++i){
        for(int j = 0;j<n;++j){
            printf("%d ",a[j]);
        }
        printf("\n");
    }
}
int main(){
    int k,n,a[8]={3,2,5,8,4,7,6,9};
    printf("遍历圈k=");
    scanf("%d",&k);
    n=sizeof(a)/sizeof(a[0]);
    printArray(a,n,k);
    return 0;
}

这个遍历函数时间复杂度为O(n²)时间复杂度还是太高,我们要考虑如何降低时间复杂度,是这个算法更加高级。
假如我们要再建立一个新的大的数组,而这个数组恰好是原来数组的k被,然后如果我们给这个新的数组赋值k遍,不就完美的将时间复杂度降到了O(n)。

注意
(1)我们不真正的建立新的数组,只是逻辑上建立数组,不能增加空间复杂度,只需在循环的时候将i<n改成i<n*k,然后打印的时候将a[i]改成a[i%n]就做到了。
(2)遍历一边完成后如何换行?只需要我们提前判断i%n是不是为0,若i%n=0则我们就循环一遍结束,就需要换行。

改良后代码实现:

void printArray(int a[],int n,int k){
    for(int i = 0;i<k*n;++i){
        if(i%n==0){
            printf("\n");
        }       
        printf("%3d",a[i%n]);
    }
}//即没有拿空间换时间,时间复杂度还降低,这才是好算法

二、移动线性表元素

实质:修改线性表数据元素之间的逻辑次序

(一)赋值式移动

示例:有顺序表a={3,2,5,8,4,7,6,9}要求:将第k个元素移动到顺序表最后。
k=是

void MoveElem(int a[],int n,int k){
    int i,j,x;
    for(i=0;i < k;++i){
        x = a[0];//将第一个元素赋给x
        for(j=0;j < n-1;++j){
            a[j]=a[j+1];
        }
        a[j]=x;//将表头元素赋给表尾
    }
}

int main(){
    int k,n,a[8]={3,2,5,8,4,7,6,9};
    printf("k=");
    scanf("%d",&k);
    n=sizeof(a)/sizeof(a[0]);
    MoveElem( a, n, k);
    for(int i=0;i<n;i++){
        printf("%3d",a[i]);
    }  
    return 0;
}

易错点:
将第一个元素赋给x, x = a[0],不要写成x=a[i],每次内循环完了之后,当时第二个元素已经是表头元素,所以一直要赋a[0]而不是a[i]。

内循环还可以进一步优化,提高效率—>将循环体写到条件内,让for空转。

void MoveElem(int a[],int n,int k){
    int i,j;
    for(i=0;i < k;++i){
    	int x = a[0];//将第一个元素赋给x
        for(j=0;j<n-1&&(a[j]=a[j+1],1);++j);
        a[j]=x;//将表头元素赋给表尾
    }
}

思考:现在的时间复杂度还是O(n²),能不能优化算法将时间复杂度降到O(n)。所以我们开始研究交换式移动。

(二)交换式移动

1.通过不断的两两交换,让首节点挪到尾节点。

代码如下(示例):

//两两交换
for(int i=0;i<k;++i){
	for(int j=0;j<n-1;++j){
	    int t =a[j];
	    a[j]=a[j+1];
	    a[j+1]=t;
	}
}

通过两两交换式移动,我们还是O(n),并且执行次数还是多了的,所以我们继续研究。

2.从两边向中间渐进式(逆置)移动

例:逆置a[0]~a[n-1],再将逆置后的前n-k个逆置,后k个逆置。
第一种思路://第一种,传三个变量,a[],low,high

void exchange(int a[],int low,int high){
    for (int i=low,j=high; i<j; ++i,--j){
        int t =a[i];
        a[i]=a[j];
        a[j]=t;
    }
int main(){
    int k,n,a[8]={3,2,5,8,4,7,6,9};
    printf("k=");
    scanf("%d",&k);
    n=sizeof(a)/sizeof(a[0]);
    exchange(a,0,n-1);//整个逆置
    exchange(a,0,n-k-1);//a[0]~a[n-k]逆置
    exchange(a,n-k,n-1);//a[n-k]~a[n-1]逆置
    for(int i=0;i<n;i++){
        printf("%3d",a[i]);
    }  
    return 0;
}

//第二种,传两个变量,一个a[](指针a指的地址),一个n(长度)

void exchange2(int a[],int n){
    for (int i=0,j=n-1; i<j; ++i,--j){
        int t =a[i];
        a[i]=a[j];
        a[j]=t;
    }
    
}

int main(){
    int k,n,a[8]={3,2,5,8,4,7,6,9};
    printf("k=");
    scanf("%d",&k);
    n=sizeof(a)/sizeof(a[0]);
    exchange2(a,n);//整个逆置
    exchange2(a,n-k);//a[0]~a[n-k]逆置
    exchange2(a+n-k,k);//a[n-k]~a[n-1]逆置,让被调函数指针指到n-k,而此时的n-k的位置就是现在新的a[0],k就是现在新的数组的长度。
    for(int i=0;i<n;i++){
        printf("%3d",a[i]);
    }  
    return 0;
}

根据上面两种思路可见,我们实现线性表的移动,代码时间复杂度从O(n²),降到了O(n),并且空间复杂度并没有升高,算是比较好的算法了。


三、顺序表的查找

定义:在顺序表中按给定的关键字查找某一数据元素。

例:设顺序表a={3,2,5,8,4,7,6,9},无序无重复,给定一个关键字key,在顺序表a中查找关键字,若找到,返回下标,若未找到返回-1;

int Search(int a[],int n,int key){
    int i;
    for (i = 0; i < n&&a[i]-key; i++);
    if(i==n)
        i=-1;
    return i;
}

四、顺序表的删除

定义:在一个顺序表中删除一个关键字为key的数据元素
注意:删除一个数据元素,顺序表长度-1

1.无序无重复值

算法过程:
(1)查找key的数据元素for(int i=0;i<n&&a[i]-key;++i);
(2)删除找到的数据元素:
①i==n,没找到
②i<n,找到了,删除(建议不用i,在设置一个新的变量,如果有重复值,i还要继续跑)
for(int j=i;j<n-1&&(a[j]=a[j+1]);j++);
(3)n–;

代码实现:

int Delete(int a[],int n,int key){
    int i,j;
    for(i=0;i<n&&a[i]-key;++i);
    if(i==n){
        printf("没找到删除的节点");
        return n;
    }
    for(j=i;j<n&&(a[j]=a[j+1],1);++j);
    n--;
    return n; 
}

2.无序有重复值

重点:边跑边删

int del2(int a[],int n,int key){
    int i,j;
    for(i=0;i<n;++i){
        if(a[i]==key){
            for(j=i;j<n&&(a[j]=a[j+1],1);++j);
            n--;
        }else{
            i++;
        }
    }
    return n;
}

我们可以看到,上述代码虽然实现的又重复值的删除操作,但是时间复杂度是O(n²),我们要考虑怎么样修改算法可以将时间复杂度降到O(n)。
我们可以想到,我们要删除要删的,可以把思路转换成留下要留的
思路:我们在逻辑上新建一个数组,将a[i]!=key的值,留在数组内(使用两指针联动思想

int Delkey(int a[],int n,int key){
    int i,j;
    for(i=0,j=0;i<n;i++){
        if(a[i]-key){
            a[j]=a[i];
            j++;
        }
    }
    return j;
}

以上代码还可以一步简化:

int Delkey(int a[],int n,int key){
    int i,j;
    for(i=0,j=0;i<n&&(a[i]==key||(a[j++]=a[j],1));i++);
    return j;
}

五、顺序表的插入

注意:顺序表存储空间要足够大。
不可避免的问题:顺序表插入会带动大量的元素移动,时间复杂度O(n)。

void Initkey(int a[],int i,int n,int key){//待插位置i
    for(int j=n-1;j>i&&(a[j]=a[j++],1);--j);
    a[i]=key;
}

总结

在这个专题,我们学习了顺序表的遍历,移动,增删改查;然后我们思考了算法的时间复杂度降维,还是很有难度,需要不断复习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值