目录
- 数组综述
- Java中的数组
- 创建数组
- 访问数组
- 初始化数组
- 有序数组
- 线性查找
- 二分查找
- 有序数组的优缺点
- 大O表示法(order of)
- 为什么不用数据解决一切
- 小结
数组综述
数组是最广泛的数据存储结构,其中还有一种特殊数组(有序数组),下面讲解下插入、查询、删除相关的注意事项
- 插入: 新数据总是插在数组第一个空位上,由于已有数据项个数已知,所以空位的具体位置很容易得知,然而查询和删除却没有那么快。当不允许插入重复值的模式下,还需要将算法计算是否存在数据项。
- 查找: 若是无重复项模式,必须平均搜索一半的数据项来查找特定数据项。找数组头部的数据项快,找数组尾部的数据项慢。若重复模式,必须从头查找到尾。
- 删除: 只有找到特定数据项后才能进行删除操作(要么该数据项被替换,要么变为空)。删除算法中假设不允许有洞(一个或多个空单元后面存在非空数据项),如果删除算法允许有洞,则其他算法变得很复杂,因为要判断非空降低效率。因此非空数据项必须连续,不能有洞。在无重复项模式下查找平均N/2个数据项+移动剩下的N/2个数据项来补洞。总共是N步。
- 重复值问题:
- 重复项模式下查找算法: 重复项使查找算法复杂,匹配上一个后,还得考虑是否继续寻找可能的匹配,直到最后一个数据项(所有蓝眼睛的人和第一个蓝眼睛的人的区别)。通常操作需要N步。
- 重复项模式下插入算法: 和不重复项模式一样,只需要一步。
- 重复项模式下删除算法:若是删除第一个特定的数据项,操作步骤平均是N/2次查找+N/2移动,若是删除所有特定的数据项,需要检查N个数据项+移动多余N/2个数据项,操作的平均时间取决于重复项的个数和分布。
表格区别重复与无重复模式的区别
操作 | 不允许重复 | 允许重复 |
---|---|---|
查找 | N/2次比较 | N次比较 |
插入 | 无比较、一次移动 | 一次移动 |
删除 | N/2次比较、N/2次移动 | N次比较、多于N/2次移动 |
Java中的数组
创建数组
int[] intArray = new int[]{};
int[] intArray = new int[2];
访问数组
数组数据项通过下标访问,下标从0开始,止于长度减1。若下标不在这个范围访问数组,则抛出Array Index Out of Bounds的运行时错误。
初始化数组
int[] intArray = {1,2,3,4,5};
int[] intArray = new int[]{1,2,3,4,5};
代码示例
public class HightArray {
/** 声明一个数组 */ private long[] a;
/** 声明变量记录数据项个数 */ private int nElemts;
/** 构造方法用于初始化数组和数据项总数 */
public HightArray(int max){ a = new long[max]; nElemts = 0; }
public void insert(long value){ a[nElemts] = value; nElemts ++ ; }
public boolean find(long searchKey){
int j;
for (j=0;j<nElemts;j++){ if(a[j]==searchKey){ break; } }
return j == nElemts;
}
public boolean delete(long value){
int j;
for (j=0; j<nElemts; j++){ if(a[j]==value){ break; } }
if(j == nElemts) { return false; }
else {
for (int k=j; k<nElemts; k++){ a[k] = a[k+1]; }
nElemts--;
return true;
}
}
public void display(){ System.out.println(Arrays.stream(a).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
}
}
测试类>>>
public class HightArrayTest {
@Test public void test(){
int maxSize = 100;
HightArray hightArray = new HightArray(maxSize){{
insert(77); insert(99); insert(44); insert(55); insert(66);
insert(22); insert(88); insert(11); insert(00); insert(33);
}};
hightArray.display();
int searchKey = 35;
System.out.println(hightArray.find(searchKey)?"Found ":"Can't find " + searchKey);
hightArray.delete(00); hightArray.delete(55); hightArray.delete(99);
hightArray.display();
}
}
有序数组
数组中的数据项按照关键字升序排列。当向此数组中插入数据项时,需要为插入操作找到正确位置,在稍小位置和稍大位置之间,然后将稍大位置至末尾的数据项往后移动一位腾出位置。
这中顺序排列的好处就是可以通过二分查找显著提高查询速度,但是降低了插入的速度,毕竟要腾出坑位。
线性查找
线性查找就是依次向后,寻找匹配。而在有序数组中当匹配到一个合适的数据项就退出查找。
二分查找
二分查找就是一半一半的找,这种查询比线性查询快很多,尤其对大数组来说。
有序数组代码示例
public class OrderArray {
private long[] a;
private int nElems;
public OrderArray(int maxSize){ a = new long[maxSize]; nElems = 0; }
public int size(){ return nElems; }
public void insert(long value){
int j ;
for (j = 0; j < nElems; j++) { if(a[j] > value) { break; } }
for(int k=nElems; k>j; k--){ a[k] = a[k-1]; }
a[j] = value;
nElems++;
}
public int find(long searchKey){
int lowerBound = 0, upperBound = nElems -1, curIndex;
while (true){
if(lowerBound > upperBound){ return nElems; }
curIndex = (lowerBound + upperBound)/2;
if(a[curIndex] == searchKey) { return curIndex; }
else if (a[curIndex] < searchKey){ lowerBound = curIndex + 1; }
else { upperBound = curIndex - 1; }
}
}
public boolean delete(long value){
int j = find(value);
if(j == nElems) { return false; }
for (int k = j; k < nElems; k++){ a[k] = a[k+1]; }
nElems--;
return true;
}
public void display(){
System.out.println(Arrays.stream(a).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
}
}
测试类>>>
public class OrderArrayTest {
@Test public void test(){
int maxSize = 100;
OrderArray orderArray = new OrderArray(maxSize){{
insert(77); insert(99); insert(44); insert(55); insert(22);
insert(88); insert(11); insert(00); insert(66); insert(33);
}};
int searchKey = 55;
System.out.println(orderArray.find(searchKey)!=orderArray.size()?"Found":"Can't find" + searchKey);
orderArray.display();
orderArray.delete(00); orderArray.delete(55); orderArray.delete(99);
orderArray.display();
}
}
有序数组的优缺点
- 优点: 二分查找比无序数据快。
- 缺点: 插入需要查找,查找后需要将靠后的数据项整体往后移动。删除操作都需要补洞。
大O表示法(order of)
算法 | 运行时间 |
---|---|
线性查找(从头到尾) | O(N) |
二分查找(一半一半) | O(LogN) |
无序数组插入(仅一次) | O(1) |
有序数组插入(查询+移动) | O(N) |
无序数组删除(查询+补洞) | O(N) |
有序数组删除(查询+补洞) | O(N) |
为什么不用数据解决一切
一个无序数组插入(O(1)时间),查询却花费(O(N)时间)。一个有序数组查询花费(O(LogN)时间),但是插入缺花费(O(N)时间)。而删除都需要花费(O(N)时间)。所以需要一种数据结构插入、查询、删除都很快,理论上(O(1)或者O(LogN)时间),而且数组一旦创建,其大小被固定,扩容不便,若空间大了又会浪费。
小结
- 无序数组可以快速插入,但查询和删除比较慢。
- 有序数组可以使用二分法查找。
- 线性查找需要的时间和数据项个数成正比。
- 二分法查找需要的时间和数据项个数的对数成正比。
- O(1)算法最好、O(LogN)次之、O(N)一般、O(N*N)最差。