[b]马上要自考了,其中有一门数据结构,好久没复习,忘得差不多了,今天从头开始,顺便记一下笔记。我看的是[url="http://www.douban.com/subject/1144007/"]Java数据结构和算法[/url]这本书,所以这里的数据结构和算法全都用Java语言描述。每篇笔记都会附上随书的Applet演示程序,有算法看不明白的,可以[url="http://yuan.iteye.com/topics/download/cf39c9aa-40d3-3d9c-b0c6-bd7316add714"]下载Applet[/url]运行起来(直接打开html文件即可),可以很容易地看清楚算法的每一步。[/b]
[size=large][align=center][b]二分查找法和线性查找法[/b][/align][/size]
[b]二分查找[/b]法是一种比普通线性查找快得多的查找算法,但只适用于有序集合当中。拿[b]升序[/b]排序后的[b]整型[/b]数组来说,二分法具体的实现原理是:先把[b]待查找数[/b]a与[b]数组中间的那个数[/b]x对比,如果相等,直接返回x的索引;如果a大于x,则排除掉[b]数组的前面一半[/b](包括x),接着拿a与[b]剩下一半数组[/b]中间的那个数x对比,如果相等,直接返回x的索引;如果a小于x,则排除掉数组[b]后面一半的后面一半[/b]……如此循环直到找到目标。
普通的[b]线性查找[/b]法是从数组的第一个数开始对比,接着是第二个,第三个……直到找到目标。
[size=large][align=center][b]大O表示法[/b][/align][/size]
[b]大O表示[/b]法是一种粗略试题算法效率的方法。了解大O表示法之前先看一组公式:
[b]无序数组[/b]的插入是与数组中数据项个数无关的算法,由于插入时不必考虑排序,新数据项总是被放在下一个有空的地方。我们可以说向一个无序数组中插入一个数据项的时间T是一个[b]常量K[/b](K值与cpu运行速度、编译程序生成程序代码的效率等有关),得出:
[b]T = K[/b]
在数据项的线性查找中,[b]最好的情况下[/b]比较次数只有1次(数组第1个数据项就是所要查找目标的情况);[b]最坏的情况下[/b]比较次数有N(数组长度)次(数组最后一个数据项是查找目标)。平均次数为N/2次,搜索时间T与N/2成正比,也就是与N成正比:
[b]T = K*N[/b]
二分查找法……先反过来思考一个问题:只给5次比较机会,能搜索到目标的最大范围数组长度是多少?1次能比较2个,2次能比较4个,3次能比较8个,4次16个,5次32个。设[b]数组长度[/b]为N,[b]比较次数[/b]为X,N是2的X次方,也就是说X是以2为底N的对数即log2(N)。由此得出二分查找法在最坏情况下花费的时间T为比较次数log2(N)乘以单次比较所花费的时间K,即:
[b]T = K*log2(N)[/b]
也就是T与log2(N)成正比。由于任何对数都和其他对数成比例,我们也可以说T与log(N)(以10为底N的对数)成正比,即:
[b]T = K*log(N)[/b]
大O表示法同上面的公式比较类似,但它省去了常数K。因为比较算法时不需要在乎硬件设备等。大O表示法使用大写字母O,可以使用大O表示法来描述线性查找使用了O(N)级时间,二分查找使用了O(log N)级时间,向一个无序数组插入数据使用了O(1)(或常数)级时间。
[size=large][b][align=center]无序数组和有序数组[/align][/b][/size]
下面是两个简单数组类,其中无序数组的add方法直接向成员array中插值,时间复杂度用大O表示法表示为O(1);有序数组的add方法平均要经过N/2次比较,不考虑插入值之前向后移动数组所花时间(当然这很花时间),时间复杂度为O(N);无序数组的delete方法首先要调用私有的find方法,这里find方法使用线性查找法,时间复杂度为O(N);有序数组的delete方法所调用的binarySearch是二分查找法,时间复杂度为O(log N)。
结论是:
1、无序数组插值效率比有序数组要高得多(有序数组插值时除了比较数据还要移动数组);
2、有序数组删除数据的效率比无序数组高(两种数组在删除数据时都要移动数组,只在查找数据的算法上有区别)。
无序数组类:
有序数组类:
[size=large][align=center][b]二分查找法和线性查找法[/b][/align][/size]
[b]二分查找[/b]法是一种比普通线性查找快得多的查找算法,但只适用于有序集合当中。拿[b]升序[/b]排序后的[b]整型[/b]数组来说,二分法具体的实现原理是:先把[b]待查找数[/b]a与[b]数组中间的那个数[/b]x对比,如果相等,直接返回x的索引;如果a大于x,则排除掉[b]数组的前面一半[/b](包括x),接着拿a与[b]剩下一半数组[/b]中间的那个数x对比,如果相等,直接返回x的索引;如果a小于x,则排除掉数组[b]后面一半的后面一半[/b]……如此循环直到找到目标。
普通的[b]线性查找[/b]法是从数组的第一个数开始对比,接着是第二个,第三个……直到找到目标。
[size=large][align=center][b]大O表示法[/b][/align][/size]
[b]大O表示[/b]法是一种粗略试题算法效率的方法。了解大O表示法之前先看一组公式:
[b]无序数组[/b]的插入是与数组中数据项个数无关的算法,由于插入时不必考虑排序,新数据项总是被放在下一个有空的地方。我们可以说向一个无序数组中插入一个数据项的时间T是一个[b]常量K[/b](K值与cpu运行速度、编译程序生成程序代码的效率等有关),得出:
[b]T = K[/b]
在数据项的线性查找中,[b]最好的情况下[/b]比较次数只有1次(数组第1个数据项就是所要查找目标的情况);[b]最坏的情况下[/b]比较次数有N(数组长度)次(数组最后一个数据项是查找目标)。平均次数为N/2次,搜索时间T与N/2成正比,也就是与N成正比:
[b]T = K*N[/b]
二分查找法……先反过来思考一个问题:只给5次比较机会,能搜索到目标的最大范围数组长度是多少?1次能比较2个,2次能比较4个,3次能比较8个,4次16个,5次32个。设[b]数组长度[/b]为N,[b]比较次数[/b]为X,N是2的X次方,也就是说X是以2为底N的对数即log2(N)。由此得出二分查找法在最坏情况下花费的时间T为比较次数log2(N)乘以单次比较所花费的时间K,即:
[b]T = K*log2(N)[/b]
也就是T与log2(N)成正比。由于任何对数都和其他对数成比例,我们也可以说T与log(N)(以10为底N的对数)成正比,即:
[b]T = K*log(N)[/b]
大O表示法同上面的公式比较类似,但它省去了常数K。因为比较算法时不需要在乎硬件设备等。大O表示法使用大写字母O,可以使用大O表示法来描述线性查找使用了O(N)级时间,二分查找使用了O(log N)级时间,向一个无序数组插入数据使用了O(1)(或常数)级时间。
[size=large][b][align=center]无序数组和有序数组[/align][/b][/size]
下面是两个简单数组类,其中无序数组的add方法直接向成员array中插值,时间复杂度用大O表示法表示为O(1);有序数组的add方法平均要经过N/2次比较,不考虑插入值之前向后移动数组所花时间(当然这很花时间),时间复杂度为O(N);无序数组的delete方法首先要调用私有的find方法,这里find方法使用线性查找法,时间复杂度为O(N);有序数组的delete方法所调用的binarySearch是二分查找法,时间复杂度为O(log N)。
结论是:
1、无序数组插值效率比有序数组要高得多(有序数组插值时除了比较数据还要移动数组);
2、有序数组删除数据的效率比无序数组高(两种数组在删除数据时都要移动数组,只在查找数据的算法上有区别)。
无序数组类:
package dsaa.array;
/**
* @(#)SortedArray.java 2008-12-24 下午06:36:22
*
* @author Qiu Maoyuan
* Unsorted Array
*/
public class UnsortedArray {
private int[] array;
private int size = 0;
public UnsortedArray(int initialCapacity){
this.array = new int[initialCapacity];
}
public UnsortedArray(){
this(10);
}
public void add(int number){
ensureCapacity();
array[size++] = number;
}
public int get(int index){
if(index>=size)
throw new IndexOutOfBoundsException();
return array[index];
}
public boolean delete(int value){
int index = find(value);
if(index==size)
return false;
moveFrontwards(index);
size--;
return true;
}
private void ensureCapacity() {
if(size==array.length){
int[] newArray = new int[size * 3 / 2 + 1];
System.arraycopy(array, 0, newArray, 0, size);
array = newArray;
}
}
private int find(int value){
int i = 0;
while(i<size && array[i]!=value) i++;
return i;
}
private void moveFrontwards(int startingIndex){
for(int i=startingIndex; i<size-1; i++)
array[i] = array[i+1];
}
}
有序数组类:
package dsaa.array;
/**
* @(#)SortedArray.java 2008-12-24 下午06:36:22
*
* @author Qiu Maoyuan
* Sorted Array
*/
public class SortedArray {
private int[] array;
private int size = 0;
public SortedArray(int initialCapacity){
this.array = new int[initialCapacity];
}
public SortedArray(){
this(10);
}
public void add(int value){
if(size==0){
array[0] = value;
size++;
return;
}
ensureCapacity();
for(int i=0; i<size+1; i++){
if(value<array[i]){
moveBackwards(i);
array[i] = value;
size++;
return;
}
}
array[size] = value;
size++;
}
public int get(int index){
if(index>=size)
throw new IndexOutOfBoundsException();
return array[index];
}
public boolean delete(int value){
int index = binarySearch(value);
if(index==size)
return false;
moveFrontwards(index);
size--;
return true;
}
public int getSize(){
return this.size;
}
private void ensureCapacity() {
if(size==array.length){
int[] newArray = new int[size * 3 / 2 + 1];
System.arraycopy(array, 0, newArray, 0, size);
array = newArray;
}
}
private void moveBackwards(int startingIndex) {
for(int j=size; j>startingIndex; j--){
array[j] = array[j-1];
}
}
private void moveFrontwards(int startingIndex){
for(int i=startingIndex; i<size-1; i++)
array[i] = array[i+1];
}
private int binarySearch(int target){
if(size==0) return size;
int currentIndex;
int lowerBound = 0;
int upperBound = size - 1;
while(true){
currentIndex = (lowerBound + upperBound) / 2;
int currentValue = array[currentIndex];
if(currentValue==target)
break;
else if(currentValue<target){
lowerBound = currentIndex + 1;
}else{
upperBound = currentIndex - 1;
}
if(lowerBound>=upperBound) return size;
}
return currentIndex;
}
}