沈逸飞 任春龙 胡云飞 王丽丽
摘要:在计算机高级编程语言中,数组是一种最常见且应用广泛的数据结构。不同的程序设计语言在数据合并上采取不同的实现方式,其合并程序在时间和空间效率也存在很大的差别。该文主要研究了C语言、Java、python三种语言中数组合并的实现方法,并通过程序实例进行演示,对其时间和空间复杂度分别进行了详细的分析。实验结果表明,C语言在实现数组合并时效果最好,代码利用率更高,而且不受数组类型影响。当数组元素个数很少时,采用Java语言循环遍历更好,当数组元素个数很大时,使用System.arraycopy效率是最好;对于Python语言,采用不同的方法所用的时间复杂度相同。通过对比三种语言在数据合并中的性能差别,有助于用户根据实际应用需求合理选择适合的合并方法。
关键词:数组合并;strcat函数;NumPy;循环遍历
中图分类号:G642.0 文献标识码:A
文章编号:1009-3044(2020)03-0078-05
1 背景
在计算机程序设计中,通常将相同类型数据的有序集合称为数组,数组在处理大量数据的存储和调用时可以有效简化程序,数组相关的一类重要应用就是合并数组。相关的程序设计语言中,对于合并数组的方法描述有一些是模糊的,如C語言中可以通过指针的相关调用来合并数组,但书中并未直接给出相关方法;有一些则是直接给出相关函数,如Python中的extend()方法可以直接将另一个列表数组追加到当前列表数组中。因此本文给出了C语言、Java、Python三种程序设计语言中的几种数组合并方法,本文所给代码均经过DEV-C++5.4.0、Eclipse、jdkl.8、PyCharm编译通过。
2 数组合并的方法
2.1 C语言
数组是C语言中重要的数据结构。在C语言中数组可分为一维数组、二维数组、字符数组三种类型的数组,所要合并的数组必须是同一类型的数组。在C语言中数组的合并方法主要有三种:创建新数组(将所要合并的两个数组逐一复制进去)、利用指针、调用strcat[3]函数,其中strcat函数只能在字符数组中使用。
2.1.1 创建新数组
#include
#include
void main0{
int a[3]-{1,2,3),b[4]={4,5,6,7),c[7];
int i=0,m=0,n=0;
while(i<7){
if(m<3){
c[i++]-a[m++];)
elsef
c[i++]=b[n++];))
for(i=0;i<7;i++)
printf(”%d”,c[i]);
l//输出结果:
1 2 3 4 5 6 7
在上述程序中,先创建两个一维数组a[3]、b[4]用于合并,再创建一个空的一维数组c[7],长度为要合并数组长度之和,再利用while(i<7)语句来限定复制到c[7]中的元素个数不超过7,用if(m<3)确保a[3]中的3个元素都被使用,用c[i++]_a[m++]将各个元素复制到c[7]中,复制b[4]类似。再将c[7]中的每个元素打印出来,方便对比程序功能是否实现。
2.1.2 利用指针[4]
#include
#include
void merge(char*c,char*a,char* b){
while(*a!=\0){
*C++=*a++:
)
while(%!=\0,){
*c++=*b++:
))
void main0{
char c[120];
char a[]=”Ilike”,b[]=”book”;
merge(c,a,b);
printf(”%s\n”,c);
1//输出结果:
I like book
在处理字符数组时有时会用到指针,在某些情况下可以减小程序复杂度。本程序中先创建一个合并函数merge,有三个变量*c,*a*b,定义运算规则为将*a、*b所指定的数组元素逐个复制到*c所指定的数组中,在定义时用while(*a!=\0)来判定*a所指定数组是否复制完毕,若未复制完,继续执行mc++=*a++语句将*a指定数组元素复制到*c指定的数组中,复制*b时类似。然后在主函数中定义相应的数组,目标数组长度应设置的足够大,然后调用函数merge实现数组合并,将目标数组逐个打印出来。
2.1.3 strcat函数
#include
#include
#include
void main0{
char strl[120]=”Ilike”,str2[60]=”book”;
strcat(strl,str2);
printf(”% s\n”,strl);
1//输出结果:
Ilike book
该函数的功能是将str2的内容和字符串结束标记\0一起连接到strl的尾部。连接后,原strl的\0会被自动覆盖,生成的新串存放在strl中。其中strl必须是字符数组,而str2可以是字符串常量、也可以是字符数组,值得注意的是strl必须有足够的长度以容纳连接后的新串内容。在程序中可以直接调用strcat函数完成两个满足条件的字符数组合并。在本程序中先创建strl[120]、str2[60]两个字符数组,再用strcat(strl,str2)语句将str2连接到strl的尾部,最后将strl打印出来,方便查看程序功能是否实现。
2.2 Java语言
2.2.1 Arrays类
lava中包装数组的一些基本用法的抽象类java. util.Ar-rays[1],这个类中包含操作数组的一些算法,该类中包含了一些用来直接操作数组的方法。它提供的所有方法都是静态的。Ja-va.utiI.ArrayList是大小可变的数组的实现,存储在内的数据称为元素,此类提供一些方法来操作内部存储的元素。ArrayList中可不断添加元素,其大小也自动增长,可以存储任意多的对象,但是只能存储对象,不能存储原生数据类型。addAlI是传人一个List,将此List中的所有元素加入当前List中,也就是当前List会增加的元素个数为传人的List的大小。ArrayList提供了一个将List转为数组的一个非常方便的方法toArray。
importjava.util.*;
publicclass lwdml{
publicstaticvoidmain(String args[D{
String stl[]={”1”,”2”,”3”);
String st2[]={”4”,”5”,”6”);
Listlist= newArrayList(Arrays.asList(stl》;
list.addAll(Arrays.asList(st2》;
Object[] st= list.toArray0;
System.out.println(Arrays.toString(s t》;
)
】//輸出结果:[1,2,3,4,5,6]
定义两个数组,使用Java中的Arrays工具类,调用ArrayList类创建一个ArrayList的对象,将数组元素添加到集合中。再调用ArrayList类中的toArray方法中的list.toArray0将list直接转为Object口数组.或者调用toArray方法中的list.toArray(T[]a)将list转化为你所需要类型的数组,注意使用的时候会转化为与list内容相同的类型。
2.2.2 循环遍历[2]
循环结构是在一定条件下,反复执行某一语句的控制流程。循环控制结构包括循环条件、初始部分和循环体三部分。
for循环是Java循环结构中最常用到的,for循环在第一次循环前要进行初始化。之后它会进行条件测试,而且在每一次循环结束时,会修改循环变量。
系统执行for语句时,首先对循环变量进行初始化,然后判断布尔表达式的值,若为假,跳出for循环;若为真,则执行循环体后在执行修改循环变量,一次循环结束。下一次循环从判断布尔表达式的值开始,结果为真,继续循环,结果为假则退出for循环。
但使用for循环将原数组的每个元素赋值给新数组的对应元素,效率低。
publicclass lwdm2{
publicstaticvoidmain(String args[1){
String[] stl={”1”,”2”,”3”);
String[] st2={”4”,”5”,”6”);
String[] st= new String[stl.length+st2.length];
for(int x=O;x
st[x]= stl[x];
for(int y=O;y
st[stl.length+y]=st2[y];
)
for(int z=O;z
System.out.print(st[z]+“”);
】
)
1//输出结果:123456
上面的程序段是三个for语句实现了数组合并的功能,利用for循环语句,分别对要合并的数组中的每一个元素进行遍历,再将原数组的每个元素赋值给新数组的对应元素,新的数组的长度是要合并数组长度之和。
2.2.3 System.arraycopy0方法
Java中没有二维数组的概念,平常实现的二维数组只是元素是一维数组3-维数组,而数组也是引用类型,继承自Object类。System中提供了一个native静态方法arraycopy0,可以使用这个方法来实现数组之间的复制。对于一维数组来说,这种复制属性值传递,修改副本不会影响原来的值。对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。
importjava.util.Arrays;
publicclass lwdm3{
publicstaticvoidmain(String args[1){
String[] stl={”l”,”2”,”3”);
String[] st2={”4”,”5”,”6”);
int stILength= stl.length;
int st2length= st2.length;
stl= Arrays.copyOf(stl, stlLength+st2length);
System.arraycopy(st2,0,stl, stILength, st2length);
System.out.println(Arrays.toString(stl》;
)
)//输出结果:[1,2,3,4,5,6]
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从src引用的源数组到dest引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于length参数。copyOf0的实现是用的是array-Copy0,arrayCopy0需要目标数组,对两个数组的内容进行可能不完全的合并操作。
2.3 python语言
数组是Python中最常用的数据类型,它可以用一个方括号内的逗号分隔值出现。在python中数组的数据项不需要具有相同的类型,创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可。对于一些基本的数组功能,我们可以通过列表、元组等数据类型实现。由于在数据量很大时,使用列表、元组来处理数据的速度就会慢的让人难以接受。为此Python语言提供了一个扩展程序库NumPy,NumPy提供了真正的数组功能,以及对数据进行快速处理的函数。
2.3.1基本类型
Python中的数组可以分为列表、元组、词典三种类型,其中列表在初始化后可通过特定的方法来动态添加数组元素;一旦元组定以后,其元素的值是不能改变的;词典即是Hash数组。由于在数据量很大时,使用这几种类型的操作运行速度就会慢的让人难以接受。所以一般它们只用于存储一些基础、小型数据。
以下给出列表的合并方法,元组的合并方法与其类似。
1) extend方法
extend0可以将另一个列表追加到当前列表中。追加时,列表中的每一个元素按照次序依次追加到列表中,相当于列表合并。
#coding=utf-8
import networkx as nx
listl=[l。2,3,4]
list2=[4,5,6,7]
listl.extend(list2)
print(listl)
listl=list(set(listl》
print(listl)
//输出结果:
[1,2,3,4,4,5,6,7]
[1,2,3,4,5,6,7]
上述程序中,对listl与list2两个列表数组调用extend0进行合并,根据结果观察,extend0将list2追到listl中,然后利用set()方法去除重复的数据。
2)直接相加法“+”
“+”也可用于列表的合并,通过“+”运算将两个列表的元素按先后顺序合并一起,它的效果与extend0类似。
#coding=utf-8
import networkx as nx
listl=[l,2,3,4,5]
list2=[3,4,5,6,7]
listl=listl+list2
print(listl)
listl=list(set(listl》
print(listl)
//输出结果:
[1,2,3,4,5,3,4,5,6,7]
[1,2,3,4,5,6,7]
上述程序中,对listl与list2两个列表数组使用“+”进行合并,根据结果观察,它将list2追到listl中.最后利用set0方法去除重复的数据。可见“+”与extend0方法作用相同。
2.3.2 NumPy(Numerical Python)'s]
Python语言提供了一个扩展程序库NumPy(Numerical Py-thon),NumPy是一个Python科学计算的基础模块。它不仅能够完成科学计算的任务,还能够被用作有效的多维数据容器,可以用于存储和处理大型数组和矩阵。它支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。因此我们可以根据Numpy中的函数来实现数组的拼接合并。
1) np.append0
NumPy提供了numpy.append(arrays,values,axis)函数。ap-pend函数对参数规定,只能用于一个数组和一个数值之间或者两个数组之间的合并拼接,不能对三个及其以上数组进行拼接。
#coding=utf-8
import numpy as np
a=[1,2,3,4,5]
b=[6,7,8,9,10]
c= np.append(a,10)
print(c)
c= np.append(a,b)
print(c)
//输出结果:
[1 2 3 4 510]
[1 2 3 4 5 6 7 8 910]
上述程序中,append函数将数据10增加到数组a;通过ap-pend将数组a,b进行合并,通过append函数实现了数与数组和数组与数组的合并。如果需要对合并后的数组进行排序,可以通过Set0方法实現。
c= np.append(b,a)
print(c)
c= list(set(c》
print(c)
//输出结果:
[6 7 8 910 1 2 3 4 5]
[1,2,3,4,5,6,7,8,9,10]
2) np.concatenate0
NumPy提供了numpy.concatenate《al,a2,…),axis)函数,它能够一次完成多个数组的拼接,传人的参数必须是一个多个数组的元组或者列表。另外还需指定拼接的方向,即axis aⅪs“;的取值,当axis=0时对行方向进行拼接;当axis=l时对列方向进行拼接;当axis=2(或-1)时对深度方向进行拼接。其中参数数组的维度必须大于axis的取值。
#coding=utf-8
lmport numpy as np
a=[[[1,2,3],[4,5,6]]]
b=[[[7,8,9],[10,1 1,12]]]
c = np.concatenate《a, b),axis=0)
print(c)
c= np.concatenate《a, b),axis=l)
print(c)
c= np.concatenate《a, b),axis=2)
print(c)
//输出结果:
[[[1 2 3][4 5 6]][[7 8 9][101112]]]
[[[1 2 3][4 5 6][7 8 9][101112]]]
[[[1 2 3 7 8 9][4 5 6101112]]]
对于axis的不同取值,得到的数组是不同的,axis=0时,在a的第一维度直接加上b中的元素;第二个np. concatenate《a,b),axis=l),则在a的第二维加上b的元素,所以这里axis=i时,输入参数(al,a2,a3…)除了第i维,其余维度的shape应该一致;同理,当axis=2时,是对三维上的数据进行合并。
3) np.stack0
stack(arrays,axis,out=None),它的两个主要参数,一个是ar-rays,也就是用来作为堆叠的数组,要求每个array的形状维度必须相等;第二个参数为axis也就是指定依照维度进行堆叠,返回值的维度比原arrays的维度高1。
#coding=utf-8
lmport numpy as np
a=[[1,2,3M4,5,6]]
b=[[1,2,3],[4,5,6]]//定义二维数组
c=np.stack([a,b],axis=O)//-维
print(c)
c=np.stack([a,b],axis=1)//二维
print(c)
c=np.stack([a,b],axis=2)//三维
print(c)
//输出结果:
[[[1 2 3][4 5 6]][[1 2 3][4 5 6]]]
[[[1 2 3][1 2 3]][[4 5 6][4 5 6]]]
[[[1 1][2 2][3 3]][[4 4][5 5][6 6]]]
a.b经stack0方法合并后生成的数组比它们要大一维,stack()中axis=0将原数组上下堆叠,增加了第一维度,a、b两个数组被依次存到c中;当axis=l时,原来的a[0][0]=1,因为这里axis=l,现在中间在加上一个维度,就变成了c[0][0][0]=1,注意中间的0,是因为np.stack《a,b),axis=l)中,a在b的前面。同理那么b[0][0]=1,因为在np.stack《a,b),axis=l)中,b在a的后面,所以c[0][1][0]=l;axis取值为2时,就是在三维上进行合并操作,即a[0][1]-2时,在后面增加一个维度,因为np.stack《a,b),axis=2),所以c[0][1][0]=2。
4) np.vstack0
Vstack即Vertical stack,垂直(按行)顺序堆叠数组,垂直拼接,沿着列的方向,对行进行拼接。等价于np.concatenate(arr,axis=0)。除了第一个轴之外,所有数组都必须具有相同的形状。一维数组的长度必须相同。通过vstack0堆叠给定的数组最后形成的数组将至少为二维的
#coding=utf-8
lmport numpy as np
a=[1,2,3]
b=h5,6]//定义一维数组
c= np.vstack([a,b])
print(c)
a=[[1,2,3]]
b=[[4,5,6]]//定义二维数组
c= np.vstack([a,b])
print(c)
//输出结果:
[[1 2 3][4 5 6]]
[[1 2 3][4 5 6]]
在上述程序中,在一维数组中,a、b二者在0号轴上连接起来,a被存到c[0]中b被存到c[l]中;对于两个二维数组a、b来说,a、b在0号轴上a在前2层b在后2层。观察一维和二维数组的情况,b在结果中被排在a的后面,形成a在上,b在下的垂直关系。
5) np.hstack0
Hstack即Horizontal stack,水平即按列順序堆叠数组,水平拼接,沿着行的方向,对列进行拼接。等价于np.concatenate(arr,axis=l)。除了第二个轴之外,数组必须具有相同的形状,除了可以是任意长度的一维数组。
#coding=utf-8
import numpy as np
a=[1,2]
b=[3,4]//定义一维数组
c= np.hstack([a.b])
print(c)
a=[[1,2],[3,4]]
b=[[5,6],[7,8]]//定义二维数组
c= np.hstack([a,b])
print(c)
//输出结果:
[12 3 4]
[[12 5 6][3 4 7 8]]
观察上述程序,与vstack0相比,在一维数组合并时现在没有增维情况,合并的结果还是一维的;在二维中,a[0]与b[0]合并存入c[0]中,a[l]与b[l]合并存入c[l]中,a、b在1号轴上被连接起来。
6) np.dstack
Dstack即deep stack,按顺序深度堆叠阵列(沿第三轴),沿着第三轴(深度方向)进行拼接。等价于np.concatenate (arr,axis=2)。除了第三个轴之外,数组的所有形状都必须相同。一维或二维数组必须具有相同的形状。
#coding=utf-8
import numpy as np
a=[1,2]
b=[3,4]//定义一维数组
c= np.dstack([a,b])
print(c)
a=[[1,2],[3,4]]
b=[[5,6M7,8]]//定义二维数组
c= np.dstack([a,b])
print(c)
//输出结果:
[[[1 3][2 4删
[[[1 5][2 6]][[3 7][4 8]]]
观察上述程序,无论a、b是一维数组,还是二维数组,它们合并后输出结果都变为三维数组。原因是不管a、b是一维数组,或是二维数组,系统都会首先将a、b变为三维数组,再按照2号轴进行合并操作。先把a中元素追加到c中,再把b中元素追加到c中,如a[0][0][0]=1存到c[0][0][0]中b[0][0][0]=5存到c[0][0][1]中。a、b在2號轴上被连接起来。在输出结果中,b的元素的2号轴的下标将变大,排到a的后面,但各元素其他轴的坐标不变。
2.4 三种语言在数组合并上的比较
在表1中可以发现,在C语言中使用指针实现数组合并更好,代码利用率更高,不受数组类型影响;在Java中数组元素个数很少时,循环遍历更好,当数组元素个数很大时,使用Sys-tem.arraycopy最好;在python中的数组合并均有相应的方法,它的数组合并时间复杂度都为0(1),但是它所占用的内存较为复杂。通过对比三种语言在数据合并中的差别,便于用户根据实际应用需求更加合理的选择开发环境以及适当的合并方法。
3 结束语
数组是计算机高级语言应用最广泛的数据结构,在程序设计中,它可以实现很多强大的功能,满足程序设计的大量需求。本文阐述了C语言、Java与Python中数组合并的一些基本方法,介绍了在数组合并中不同方法的原理及实现,并通过具体的程序实例比较了不同语言在不同操作方法下的性能优缺点。
参考文献:
[1]李兴华.Java开发实战经典[M].北京:清华大学出版社,2009.
[2]李刚.疯狂Java讲义[M].北京:电子工业出版社,2012.
[3]谭浩强.C语言程序设计[M].北京:清华大学出版社,2000.
[4]严蔚敏,吴伟民,数据结构(C语言版)[M].北京:清华大学出版社,2010.
[5]张若愚.Python科学计算[M].北京:清华大学出版社,2012.
[6]殷人昆.数据结构:用面向对象方法与C++描述[M].北京:清华大学出版社,1999.
[7] Wang P S.Java面向对象程序设计[M].杜一民,赵小燕,译.北京:清华大学出版社,2003.