第3章 数组操作与数据结构算法
数组是一个由若干同类型变量组成的集合,引用这些变量时可用同一名字。数组中的每一个变量都叫做数组的一个元素。在开发中,数组有广泛的用途。本章将对数组及一些数组的使用技巧作详细讲解。
3.1 一维数组与多维数组
数组实际上就是一组变量。数组可以是一维的,也可以是多维的。所谓的多维数组可以理解成元素中包含数组的数组,例如二维数组就是两个元素个数相同的一维数组构成的。本节将对一维数组与多维数组的定义作一下简要介绍。
3.1.1 一维数组简介
一维数组在本质上是由同类数据构成的表,表3-1 说明了一个一维数组$array 键与值的对应情形。
表3-1 一维数组
3.1.2 多维数组简介
PHP允许使用多维数组,最简单的多维数组是二维数组。实际上,二维数组是以一维数组为元素构成的数组,表3-2说明了一个4×4二维数组$array键与值的对应情形。从表中可以看出,每一行都可以看做一个一维数组。4个一维数组按照键0、1、2、3的顺序又构成了一个新的数组,而这个新的数组就是一个二维数组。
表3-2 二维数组
二维数组有些类似一个普通的二维表。与二维数组和一维数组的关系类似,N维数组可以看做是多个N-1维数组组成的一个新的数组。
3.2 常用的数组操作
数组的一般操作主要包括数组的创建、调用、更新以及数组元素的遍历等,本节将逐一介绍这些操作。
3.2.1 数组的创建与调用
在PHP中使用array函数来创建一个数组,它接受一定数量用逗号分隔的key => value参数对。其中,key可以是integer或者string,value可以是任何值。以下代码是一个创建一维数组的例子。
<?php $array = array("key1" => "Simon", 2 => "Elaine"); //数组的创建 echo $array["key1"]; //输出Simon echo $array[2]; //输出Elaine ?>
以下代码是一个创建二维数组的例子。
<?php $array = array("key1" => array(0 => 1, 1 => 10, 2 => 100), "key2" => array(0 => 5, 1 => 25, 2 => 125)); echo $array["key1"][0]; //输出1 echo $array["key1"][1]; //输出10 echo $array["key1"][2]; //输出100 echo $array["key2"][0]; //输出5 echo $array["key2"][1]; //输出25 echo $array["key2"][2]; //输出125 ?>
上面的例子通过列举数组中每一个元素的方法输出数组。但是,当数组中元素很多的时候,使用这种方法就显得过于麻烦了。通常在程序中使用循环语句 foreach 来输出数组的每一个元素,关于foreach 的具体用法,本节会在后面详细介绍。对于以调试为目的的输出,还有一个简单的方法,就是使用print_r函数直接输出数组内容。将上面的例子修改后如下所示。
<?php $array = array("key1" => array(0 => 1, 1 => 10, 2 => 100), //定义数组 "key2" => array(0 => 5, 1 => 25, 2 => 125)); print_r($array); //输出数组 ?>
代码运行结果如下。
Array ( [key1] => Array ( [0] => 1 [1] => 10 [2] => 100 ) [key2] => Array ( [0] => 5 [1] => 25 [2] => 125 ) )
由以上代码可以看出,这种简单的方法可以输出数组的完整内容和结构。在代码调试的过程中,这种用法非常常见。但是,这种方法的输出不够美观,所以,在实际应用中往往不使用。
如上所示,二维数组实际上就是值为一维数组的数组。以此类推,可以创建其他类型的多维数组。此处不再举例说明。
在实际应用中,往往不明确地指定键名,PHP会从0开始依次自动分配键名,代码如下。
<?php $array = array("a", "b","c"); //定义数组 print_r($array); //输出数组 ?>
代码运行结果如下。
Array ( [0] => a [1] => b [2] => c )
由以上结果可以看出,数组的键名被自动分配了。
3.2.2 数组的更新
前面的例子中包括了对数组中元素的调用,数组创建以后,对数组中元素的调用是通过方括号([])的方式进行的。同样,对数组元素进行更新也是通过方括号([])的方式进行的。通常,通过在方括号内指定键名,并明确地设定一个新值赋值给数组,以此来改变一个现有的数组,代码如下。
<?php $array = array("a", "b","c"); //定义数组 $array[0] = "Simon"; //修改数组元素 print_r($array); //输出数组 ?>
代码运行结果如下。
Array ( [0] => Simon [1] => b [2] => c )
上面的程序通过对$array[0]的重新赋值实现了对数组元素的更新。
3.2.3 数组元素的遍历
在上面对数组元素进行逐一释放的例子中,使用了foreach函数。foreach函数是PHP提供的一种遍历数组的简便方法。foreach仅能用于数组,将其用于其他数据类型或者一个未初始化的变量时会产生错误。具体有两种语法格式,如下所示。第二种比较次要,但却是第一种的有效扩展。
foreach (array as $value) statements foreach (array as $key => $value) statements
第一种格式遍历给定的array数组。每次循环中,当前单元的值被赋给$value,并且数组内部的指针向前移一步。第二种格式做同样的事,只是当前单元的键值也会在每次循环中被赋给变量$key。以下代码使用了这两种方法对一个一维数组进行遍历。
<?php //定义一个数组 $arr = array(0=>"zero", 1=>"one", 2=>"two"); //使用第一种方法对数组进行遍历 foreach ($arr as $value) { echo "Value: $value; "; } echo "<BR>"; //使用第二种方法对数组进行遍历 foreach ($arr as $key => $value) { echo "Key: $key; Value: $value; "; } ?>
代码运行结果如下。
Value: zero; Value: one; Value: two; Key: 0; Value: zero; Key: 1; Value: one; Key: 2; Value: two;
对多维数组的遍历,只需要嵌套使用 foreach 结构即可。以下代码对一个二维数组进行遍历。
<?php //定义数组 $array = array("ar1" => array(5=>100, 3=>120, 4=>30), "ar2" => array(4=>"three", 9=>"four", 1=>"five")); //对数组进行遍历 foreach ($array as $v1) { foreach ($v1 as $v2) { print "$v2\n"; } } ?>
代码运行结果如下。
100 120 30 three four five
3.3 数组索引与键值的操作技巧
所谓索引,就是数组键名的集合。在数组创建的时候,数组的索引也会被随之建立。如上面讲述的那样,如果没有指定键名,PHP会自动分配键名,这就是索引起了作用。
除了在创建数组时可以省略键名,在更新数组的时候也可以省略键名,也就是只给数组名加上一对空的方括号。使用省略键名方式增加新的键,新的键名会使用最大整数键名加1,代码如下所示。
<?php $array = array("a", "b","c"); //定义数组 $array[] = "Simon"; //增加一个新的数组元素 print_r($array); //输出数组 ?>
代码运行结果如下。
Array ( [0] => a [1] => b [2] => c [3] => Simon )
在更新数组时,如果指定的键名不存在,则会新建一个键,如以下代码所示。
<?php $array = array("a", "b","c"); //定义数组 $array[9] = "Simon"; //增加一个新的数组元素 print_r($array); //输出数组 ?>
运行结果如下所示。
Array ( [0] => a [1] => b [2] => c [9] => Simon )
如果要删除一个键名,可以使用unset函数进行释放。unset函数应用于数组有两种使用方法,一种是删除整个数组,另一种是保持数组结构而逐一删除键。如果使用 unset 函数对数组中的每个元素进行逐一释放而保持数组结构,使用省略键名方式增加新的键时,新的键名依然会使用最大整数键名加1。如果希望对数组进行重新索引,则需要使用array_values 函数。以下代码是一个更新数组的例子。
<?php //创建一个简单的数组 $array = array(0=>1, 1=>2, 2=>3, 3=>4, 6=>5); print_r($array); //现在把数组中键为2的值更新为100 $array[2] = 100; print_r($array); //现在添加一个键 $array["X"] = 50; print_r($array); //现在删除所有键,但保持数组本身的结构 foreach($array as $i => $value) { unset($array[$i]); } print_r($array); //再添加一个键 $array[] = 25; print_r($array); //使用array_values函数进行重新索引 $array = array_values($array); $array[] = 13; print_r($array); ?>
代码运行结果如下所示。
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [6] => 5 ) Array ( [0] => 1 [1] => 2 [2] => 100 [3] => 4 [6] => 5 ) Array ( [0] => 1 [1] => 2 [2] => 100 [3] => 4 [6] => 5 [X] => 50 ) Array ( ) Array ( [7] => 25 ) Array ( [0] => 25 [1] => 13 )
3.4 数组的排序
在PHP数组函数库中,提供了多种对数组元素进行排序的函数。本节主要介绍其中最常用的3种,即sort函数、rsort函数和array_multisort函数。
3.4.1 递增排序——sort
sort 函数对数组中的值进行排序。当函数结束时数组单元将被从最低到最高重新安排,该函数的语法格式如下所示。
void sort(array array [, int sort_flags])
其中array是要排序的数组,sort_flags表示排序行为,包括以下几种。
➢ SORT_REGULAR:正常比较单元。
➢ SORT_NUMERIC:单元被作为数字来比较。
➢ SORT_STRING:单元被作为字符串来比较。
以下代码应用sort函数实现了对一个数组的排序。
<?php $arr = array(5=>"zero", 3=>"one", 4=>"two"); //定义一个数组 sort($arr); //使用sort对数组进行排序 foreach ($arr as $key => $value) //对数组进行遍历查看排序后的结果 { echo "Key: $key; Value: $value; "; } ?>
代码运行结果如下。
Key: 0; Value: one; Key: 1; Value: two; Key: 2; Value: zero;
需要注意的是,数组在被排序后键被重新分配了。排序函数是对数组的值进行排序,在这个例子中,就是对zero、one和two 3个字符串进行排序,按照字母的顺序排序结果为one、two和zero。
3.4.2 递减排序——rsort
与sort函数相反,rsort函数对数组进行逆向排序(从最高到最低),该函数的语法如下所示。
void rsort(array array [, int sort_flags])
其中的参数含义与sort函数完全相同。以下代码使用rsort函数修改了上面的例子。
<?php //定义一个数组 $arr = array(5=>"zero", 3=>"one", 4=>"two"); //使用rsort对数组进行排序 rsort($arr); //对数组进行遍历查看排序后的结果 foreach ($arr as $key => $value) { echo "Key: $key; Value: $value; "; } ?>
运行结果如下所示。
Key: 0; Value: zero; Key: 1; Value: two; Key: 2; Value: one;
从这里可以看出,使用rsort函数的排序结果与sort函数完全相反。但是,与sort函数相同,键值被重新分配了。
3.4.3 数组排序——array_multisort
array_multisort函数用于对多个数组或多维数组进行排序。使用array_multisort函数可以一次对多个数组进行排序或者根据某一维对多维数组进行排序。与sort函数和rsort函数不同的是 array_multisort 函数排序时保留原有的键名关联,即数组的键不会被重新分配,该函数的语法格式如下所示。
bool array_multisort(array array [,arg [,sort_flags ... [, array ...]]])
其中,array是要被排序的数组,arg表示排序顺序标志,包括以下几种。
➢ SORT_ASC:按照从最低到最高顺序排序(默认);
➢ SORT_DESC:按照从最高到最低顺序排序。
sort_flags表示排序行为,包括以下几种。
➢ SORT_REGULAR:正常比较单元(默认)。
➢ SORT_NUMERIC:单元被作为数字来比较。
➢ SORT_STRING:单元被作为字符串来比较。
以下代码是一个对多个数组进行排序的例子。
<?php //定义2个数组 $ar1 = array(5=>"zero", 3=>"one", 4=>"two"); $ar2 = array(4=>"three", 9=>"four", 1=>"five"); //对数组进行排序 array_multisort($ar1, $ar2); //对数组进行遍历查看排序后的结果 foreach ($ar1 as $key => $value) { echo "Key: $key; Value: $value; "; } echo "<BR>"; foreach ($ar2 as $key => $value) { echo "Key: $key; Value: $value; "; } ?>
代码运行结果如下。
Key: 0; Value: one; Key: 1; Value: two; Key: 2; Value: zero; Key: 0; Value: four; Key: 1; Value: five; Key: 2; Value: three;
以下代码是一个对多维数组进行排序的例子。
<?php //定义一个二维数组 $array = array("ar1" => array(5=>100, 3=>120, 4=>30), "ar2" => array(4=>"three", 9=>"four", 1=>"five")); //对数组进行排序 array_multisort($array["ar1"], SORT_NUMERIC, SORT_DESC, $array["ar2"], SORT_STRING, SORT_ASC); //对数组进行遍历查看排序后的结果 foreach ($array as $v1) { foreach ($v1 as $v2) { echo "$v2\n"; } } ?>
代码运行结果如下。
120 100 30 four three five
3.5 几种数组的应用实例
数组的一个重要应用就是对数据结构算法的实现。本节将以几个常见的例子来说明如何使用PHP来实现这些应用。
3.5.1 顺序查找
顺序查找是在数组中查找某个元素的最简便方法,即通过对数组元素的逐一比较来得到结果。以下代码中的函数search即完成了这项操作。如果找到要找的值,则结果为该值所在的键。如果没有找到,则返回-1。
<?php function search($array, $k) //search函数,$array为数组,$k为要查找的值 { $n = count($array); //count函数用于计算数组中的元素个数 $array[$n] = $k; //新建一个元素,并将k存放进去 for($i=0; $i<$n; $i++) //逐一比较 { if($array[$i]==$k) { break; } } if ($i<$n) //如果在新元素的前面找到了要找的值,则返回该值 { return $i; } else //否则,返回-1 { return -1; } } $array = array(5,6,3); //测试search函数 echo search($array, 6); //调用search函数并输出查找结果 ?>
上面程序的运行结果为1,也就是在数组$array中找到了值为6的元素,键名为1。
3.5.2 二分法查找
二分法查找是在数组中查找某个元素的一种效率较高的方法。但是二分法查找需假定数组已经是排好序的,然后通过对数组元素的比较来得到结果。每次不成功的比较都会排除掉一半的数组元素。以下代码中的函数search即实现了这个算法。如果找到要找的值,则结果为该值所在的键。如果没有找到,则返回-1。
<?php //search函数 其中$array为数组,$k为要找的值,$low为查找范围的最小键值,$high为查找范围的最大键值 function search($array, $k, $low=0, $high=0) { if(count($array)!=0 and $high == 0) //判断是否为第一次调用 { $high = count($array); } if($low <= $high) //如果还存在剩余的数组元素 { $mid = intval(($low+$high)/2); //取$low和$high的中间值 if ($array[$mid] == $k) //如果找到则返回 { return $mid; } elseif ($k < $array[$mid]) //如果没有找到,则继续查找 { return search($array, $k, $low, $mid-1); } else { return search($array, $k, $mid+1, $high); } } return -1; } $array = array(4,5,7,8,9,10); //测试search函数 echo search($array, 8); //调用search函数并输出查找结果 ?>
这里需要注意在函数search中对search函数进行的递归调用。
3.5.3 使用array_search函数进行查找
除了可以使用前面介绍的方法对数组元素进行查找外,还可以使用 PHP 自带的array_search函数进行查找,其语法如下所示。
array_search(value, array)
其中value是要查找的值,array是要查找的数组。该函数如果在数组中找到了值,则返回该值所在的键,如果没有找到,则返回FALSE。以下代码是使用array_search进行查找的例子。
<?php $array = array(4,5,7,8,9,10); $found = array_search(8, $array); //调用array_search函数并输出查找结果 if($found) //如果找到输出键 echo "已找到,键为".$found; else //如果没有找到输出错误信息 echo "没有找到"; ?>
代码输出结果如下。
已找到,键为3
由以上代码可以看出,array_search函数的作用与前面介绍的查找功能相同。
3.5.4 线性表的入栈与出栈
前面介绍了如何对数组中的元素进行插入和删除。在数据结构中,常常有入栈与出栈的例子。入栈即将更多的元素压入数组并放置到数组的最后。出栈即将数组的最后一个元素删除掉。
1.入栈
PHP中,入栈通过函数array_push来实现,其语法格式如下所示。
int array_push(array, var [, var ...])
var即要压入数组的元素,array为数组。函数将返回数组新的元素总数。以下代码是一个入栈的例子。
<?php $stack = array("Simon", "Elaine"); //定义数组 array_push($stack, "Helen", "Peter"); //入栈 print_r($stack); ?>
代码运行结果如下。
Array ( [0] => Simon [1] => Elaine [2] => Helen [3] => Peter )
可以看到,两个新元素被追加到了数组$array 的最后。除了可以将新元素从数组的末尾追加以外,还可以从数组的第一个元素前追加。这时需要使用函数 array_unshift 来完成。array_unshift 函数的使用方法与 array_push 类似,以下代码改写了上面的例子,使用array_unshift来代替array_push。
<?php $stack = array("Simon", "Elaine"); //定义数组 array_unshift ($stack, "Helen", "Peter"); //入栈 print_r($stack); ?>
代码运行结果如下。
Array ( [0] => Helen [1] => Peter [2] => Simon [3] => Elaine )
由以上代码可以看到,两个新元素被追加到了数组$array的最前面。
2.出栈
PHP中,出栈通过函数array_pop来实现,其语法格式如下所示。
array_push(array)
array为数组。函数将返回数组的最后一个元素,即被删除的元素。以下代码是一个出栈的例子。
<?php $stack = array("Simon", "Elaine", "Helen", "Peter"); echo array_pop($stack)."\n"; //出栈 print_r($stack); ?>
运行结果如下所示。
Peter Array ( [0] => Simon [1] => Elaine [2] => Helen )
由以上代码可以看到,最后的一个元素已经从数组$array 中被删除了,而被删除的元素的值也被成功地输出到了屏幕上。
与array_push和array_unshift的关系类似,对于array_pop函数也有一个对应的用于从数组前端出栈的函数。该函数是array_shift,用法与array_pop类似。以下代码改写了上面的例子。
<?php $stack = array("Simon", "Elaine", "Helen", "Peter"); echo array_shift($stack)."\n"; //出栈 print_r($stack); ?>
运行结果如下所示。
Simon Array ( [0] => Elaine [1] => Helen [2] => Peter )
由以上代码可以看到,第一个元素已经从数组$array 中被删除了,而被删除的元素的值也被成功地输出到了屏幕上。
3.5.5 数组的合并
array_merge函数用于数组的合并操作,其语法格式如下所示。
array_merge(array1, array2, …)
该函数用于将参数中的数组合并到一个新数组中。以下代码是一个合并数组的例子。
<?php $array1 = array("A","B","C","D"); $array2 = array("1","2","3","4"); $array3 = array("!","@","#","$"); $arrayX = array_merge($array1, $array2, $array3); //将3个数组合并起来 print_r($arrayX); ?>
该函数的输出结果如下所示。
Array ( [0] => A [1] => B [2] => C [3] => D [4] => 1 [5] => 2 [6] => 3 [7] => 4 [8] => ! [9] => @ [10] => # [11] => $ )
如果前面的例子在数组定义的时候指定了键名,则在合并时如果键名有重复会出现问题。例如,以下代码就没有得到预期的结果。
<?php $array1 = array("AA"=>"A","BB"=>"B","CC"=>"C","DD"=>"D"); $array2 = array("AA"=>"1","BB"=>"2","CC"=>"3","DD"=>"4"); $array3 = array("AA"=>"!","BB"=>"@","CC"=>"#","DD"=>"$"); $arrayX = array_merge($array1, $array2, $array3); //合并数组 print_r($arrayX); ?>
代码运行结果如下。
Array ( [AA] => ! [BB] => @ [CC] => # [DD] => $ )
由以上结果可以看出,只有array3中的元素被保存了下来。为此,PHP还提供了一种合并数组的方式,即 array_merge_recursive 函数。该函数可以将键名相同的元素放置到一个数组中。例如,以下代码使用array_merge_recursive函数改写了上面的例子。
<?php $array1 = array("AA"=>"A","BB"=>"B","CC"=>"C","DD"=>"D"); $array2 = array("AA"=>"1","BB"=>"2","CC"=>"3","DD"=>"4"); $array3 = array("AA"=>"!","BB"=>"@","CC"=>"#","DD"=>"$"); $arrayX = array_merge_recursive($array1, $array2, $array3); //合并数组 print_r($arrayX); ?>
运行结果如下所示。
Array ( [AA] => Array ( [0] => A [1] => 1 [2] => ! ) [BB] => Array ( [0] => B [1] => 2 [2] => @ ) [CC] => Array ( [0] => C [1] => 3 [2] => # ) [DD] => Array ( [0] => D [1] => 4 [2] => $ ) )
3.5.6 数组的拆分
前面介绍了数组的合并,PHP除了提供数组的合并方法以外,还提供了数组的拆分方法。在介绍拆分数组的方法之前首先介绍一下取数组子集的方法。array_slice 函数用于取数组的子集,语法格式如下所示。
array_slice(array, int start [, int length])
这里,array是原数组,start是子集的开始位置,length是子集的长度。如果不指定length,则表示一直取到数组的末尾。以下代码是一个使用array_slice获取数组子集的例子。
<?php $array = array(1,2,3,4,5,6,7,8,9); $arrayX = array_slice($array, 2, 6); //获取数组的第2个到第7个元素 print_r($array); print_r($arrayX); ?>
代码运行结果如下。
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 [6] => 7 [7] => 8 [8] => 9 ) Array ( [0] => 3 [1] => 4 [2] => 5 [3] => 6 [4] => 7 [5] => 8 )
由以上结果可以看到,数组的子集被取了出来,而且并不影响原数组。PHP提供了一种真正达到拆分数组的函数——array_splice函数。该函数与array_slice用法完全相同,不同的是其结果会将取出的元素从原数组中删除。以下代码改写了上面的例子。
<?php $array = array(1,2,3,4,5,6,7,8,9); $arrayX = array_splice($array, 2, 6); //获取数组的第2个到第7个元素 print_r($array); print_r($arrayX); ?>
代码运行结果如下。
Array ( [0] => 1 [1] => 2 [2] => 9 ) Array ( [0] => 3 [1] => 4 [2] => 5 [3] => 6 [4] => 7 [5] => 8 )
由以上结果可以看出,被取出的元素已经被从原数组中删除了。
3.5.7 随机排序
PHP还提供了一个很有用的函数,类似于扑克游戏中的洗牌。函数shuffle可以随机地将数组打乱,得到一个元素与原数组内容相同、顺序不同的新数组,其语法格式如下所示。
void shuffle(array)
以下代码演示了这个函数的使用方法。
<?php $array = array('A','2','3','4','5','6','7','8','9','10','J','Q','K'); shuffle($array); //随机排序数组 print_r($array); //输出数组 ?>
代码运行结果如下。
Array ( [0] => 2 [1] => 4 [2] => 8 [3] => A [4] => 9 [5] => 3 [6] => Q [7] => J [8] => 10 [9] => 6 [10] => K [11] => 5 [12] => 7 )
由以上结果可以看出,数组$array 已经被打乱了。需要注意的是这个运行结果不是绝对的,因为shuffle函数是随机打乱的机制,所以每次运行其结果可能都不相同。
3.6 小结
本章介绍了PHP中数组的基本应用。在实际应用中,数组通常用于存放一组元素数据。例如,在订阅系统中用户提交的订阅清单等。本章中常用的数组操作是重点,这些操作会在实际应用中经常被用到。读者也可以结合数组索引进行一些常用的数组操作。
数组与数据结构算法的结合是本章的一个难点。但是,往往不通过类似PHP这种脚本语言来实现一些复杂的算法运算,在实际的Web应用中,复杂的算法也不多见。因此,对于一些复杂的算法操作,不建议读者使用PHP来实现。