沈逸飛 任春龍 胡云飛 王麗麗

摘要:在計算機高級編程語言中,數組是一種最常見且應用廣泛的數據結構。不同的程序設計語言在數據合并上采取不同的實現方式,其合并程序在時間和空間效率也存在很大的差別。該文主要研究了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.