顺序表专题
文章目录
前言
在这一专题完成顺序表的遍历,移动线性表元素以及增删改查。
一、遍历顺序表
示例:有顺序表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个元素移动到顺序表最后。
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;
}
总结
在这个专题,我们学习了顺序表的遍历,移动,增删改查;然后我们思考了算法的时间复杂度降维,还是很有难度,需要不断复习。