无序数组
package test;
/**
* @Author:lanxuebin
* @Description: 无序数组容器类 ;假设数组不允许重复数据项
* @Date: 10:37 2018/8/9
*/
public class DisorderlyArray {
//内置数组
private long[] a;
//已有数据项个数
private int nelems;
/**
* 构造初始化
* @param max 数组最大容量
*/
public DisorderlyArray(int max) {
a = new long[max];
nelems = 0;
}
/**
* 插入数据项;(人为自觉的不要插入重复数据)
* 插入数据项是非常快的,只需一步完成,由于新的数据项总是插入到数组的第一个空位上,并且数据已有数据项个数已知,
* 所以算法知道空位的具体位置,新的数据项只是简单的插入到下一个可用的空间。
* @param value 数据项
*/
public void insert(long value) {
a[nelems] = value;
nelems++;
}
/**
* 查询某个数据项;
* 查找算法平均需要搜索一半的数据项来查找特定的数据项。找数组头部的数据项快,找数组尾部的数据项慢。
* 设数据项个数为N,则一个数据项的平均查找长度为N/2。在最坏的情况下,带查的数据项在数组的最后,
* 这需要N步才能查到。
*
* @param searchkey
* @return
*/
public boolean find(long searchkey) {
int j;
for(j = 0; j < nelems; j++) {
if(a[j] == searchkey) {
break;
}
}
if(j == nelems){
return false;
} else {
return true;
}
}
/**
* 删除某个数据项
* 删除算法暗含一个假设,即非空数据项必须连续存储,不能有洞。如果允许有洞,那么所有其他的算法都会变得复杂。
* 删除需要(假设不允许数据项重复)查找平均N/2数据项并平均移动剩下的N/2个数据项来填充删除而带来的洞。总共N步。
* @param value
* @return
*/
public boolean delete(long value) {
int j;
for(j = 0; j < nelems; j++) {
if(a[j] == value) {
break;
}
}
if(j == nelems){
return false;
} else {
for(int k = j; j < nelems; j++) {
a[k] = a[k+1];
}
nelems--;
return true;
}
}
/**
* 遍历所有数据项
*/
public void display() {
for(int j = 0; j < nelems; j++) {
System.out.print(a[j] + " ");
System.out.println("");
}
}
}
重复值问题:
允许重复值条件下的查找算法:
1)匹配一个,和不允许重复值一样平均需要N/2步
2)匹配全部,需要N步
允许重复值条件下的查找算法:只需一步
允许重复值条件下的删除算法:
1)匹配一个,需要平均N/2步比较和平均N/2步移动
2)匹配所有,需要N步比较和(可能)移动多于N/2个数据项,这个操作时间依赖于重复数据在数组中的分布情况。
有序数组
package test;
/**
* @Author:lanxuebin
* @Description: 有序数组
* @Date: 16:02 2018/8/9
*/
public class OrdArray {
//内置数组
private long[] a;
//已有数据项个数
private int nElems;
/**
* 构造初始化
* @param max 数组最大容量
*/
public OrdArray (int max) {
a = new long[max];
nElems = 0;
}
/**
* 插入数据项;
* @param value
*/
public void insert(long value) {
//查到当前值要插入的位置 平均需要N/2步
int j;
for(j = 0; j < nElems; j++) {
if(a[j] > value) {
break;
}
}
//后移一位大于当前值的数据 平均需要N/2步
for(int k = nElems; k > j; k--) {
a[k] = a[k-1];
}
//插入当前值
a[j] = value;
//已有数据项个数加一
nElems++;
}
/**
* 有序数组的二分查找 需要log2(N)步
* @param searchKey
* @return
*/
public int find(long searchKey) {
//当前范围的起始下标 初始值为0
int lowerBound = 0;
//当前范围的终止下标 初始值为数组的最后一位数组下标
int upperBound = nElems -1;
//二分查找当前中间值
int currIn;
while (true) {
currIn = (lowerBound + upperBound) / 2;
if(a[currIn] == searchKey) {
//如果中间值刚好等于查找值
return currIn;
} else if(lowerBound > upperBound) {
//如果数组中不存在改值
return nElems;
} else {
if(a[currIn] > searchKey) {
//在左半区 -1是前边已经判断了是否等于a[currIn],排除下标currIn
upperBound = currIn - 1;
} else {
//在右半区 +1是前边已经判断了是否等于a[currIn],排除下标currIn
lowerBound = currIn + 1;
}
}
}
}
/**
* 删除某个值 需要log2(N)+N/2步
* @param value
* @return
*/
public boolean delete(long value) {
//先查找到改值下标
int i = find(value);
//没有改值
if(i == nElems) {
return false;
} else {
//前移一位改值后的值
for(int k = i; i < nElems; i++) {
a[i] = a[i+1];
}
//已有数据项个数减一
nElems--;
return true;
}
}
/**
* 遍历所有数据项
*/
public void display() {
for(int j = 0; j < nElems; j++) {
System.out.print(a[j] + " ");
System.out.println("");
}
}
}
有序数组与无序数组比较
有序数组相较无序数组来说可以通过二分查找达到更快的查找速度,但是由于要维护有序导致插入数据的时候需要后移所以比无序数组慢,由于两者在删除时都需要前移数据来弥补空洞所以删除都慢。
大O表示对比:
算法 | 大O表示的运行时间 |
---|---|
线性查找 | O(N) |
二分查找 | O(logN) |
无序数组的插入 | O(1) |
有序数组的插入 | O(N) |
无序数组的删除 | O(N) |
有序数组的删除 | O(N) |
数组的缺陷
1)要么查找慢要么插入慢要么删除慢
2)一旦创建大小固定,不能随着数据项的插入自动扩展
ps:java中的vector类使用起来像数组并且可以扩展,但是这种功能是以效率为代价的。