数据结构是数据存储的抽象结构,而不是实际结构。例如以数组为抽象数据结构,在实际磁盘中存储的数据,并非是顺序存储的。数组有下标,如果知道一个数的下标,可以通过a[index]直接取出数据,因为它有一个指针指向磁盘的某一个位置。
一、操作
0 | 1 | 2 | 3 | 4 |
---|
1. 插入
数组的插入总是将新的数据项放入下一个有空的地方。a[index]。所以无论数组的数据项有多大,插入的时间复杂度都是O(1)。
2. 查找
- 线程查找 与N成正比
查找有多种查找方式,如果数组是一个无序数组,通常查找一个值,都是线性查找,即从头开始遍历数组,直到找到这个值,查找过程结束。它的平均时间为:n/2。数据越大,查询耗时越大。 - 二分查找 与lg(N)成正比
例如1-100的数字中,查找33这个数,最少用几次
步数 | 所判断的数 | 结果 | 可能值的范围 |
---|---|---|---|
0 | 1-100 | ||
1 | 50 | 太大 | 1-49 |
2 | 25 | 太小 | 26-49 |
3 | 37 | 太大 | 26-36 |
4 | 31 | 太小 | 32-37 |
5 | 34 | 太大 | 32-33 |
6 | 32 | 太小 | 33-33 |
7 | 33 | 正确 |
使用二分查找,最多只使用7次即可完成查询。当前,前提是数组是已经排好序的。与线性查找相比,速度快了很多。
因为每次都是折半,所以它的最差查找时间为:log2n
3. 删除
删除某项数据后,当前数据末尾的数据项都要向前移动一个位置来填补它。所以,删除的时间复杂度是O(N)
二、数组的优缺点
优点
数组的优点就是插入速度快,逻辑简单易于理解。
缺点
- 如果是无序数组,查找时间慢
- 数组一旦被创建后,大小尺寸都是固定的,不方便扩展。
三、二分查找
/**
* 二分查找
*
* @param ints 被查找数组
* @param searchKey 被查找值
* @return 返回数组下标,如果没有,则抛异常NotFondException
*/
public int find(int[] ints, int searchKey) throws Exception {
int start = 0;
int end = ints.length - 1;
int executionCount = 0;
while (start < end) {
executionCount++;
int index = (start + end) / 2;
if (ints[index] < searchKey) {
start = index + 1;
} else if (ints[index] > searchKey) {
end = index - 1;
} else {
System.out.println("共查找" + executionCount + "次");
return index;
}
}
throw new Exception("not found " + searchKey);
}
四、数组的排序
- 冒泡排序
- 选择排序
- 插入排序
- 归并排序
- 希尔排序
- 快速排序
1. 冒泡排序
思想
从左到右依次比较相邻的两个数,每次比较将大的数放在后边,最终结果是,最大的数将在末位,这表示第一回排序结束。
去除最后一个数,重复上面的步骤,这一回又得到最大的一个数在最后。
依次类推,循环n次后,将得到最终的结果。
还有一种思想,即始终都拿需要排序的子数组的第一个去与后面的进行比较,把最小的放在第一位,这样比较的结果是前面先排好序。与上面思维相反。一般写代码都是这样的。
图例
Java代码
public static void sort(int[] array) {
if (array.length > 1) {
for (int i = 0; i < array.length; i++) {
for (int j = i; j < array.length; j++) {
if (array[i] > array[j]) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
}
public static void sort2(int[] array) {
if (array.length > 1) {
for (int i = array.length - 1; i >= 0; i--) {
for (int j = 0; j < i; j++) {
if (array[i] < array[j]) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
}
效率
冒泡排序的效率是非常低的。时间复杂度一般O(1)表示最好,O(lgn)次之。O(n)还行,O(n2)最差。从上面结果可以看出,冒泡排序比较次数为:(n-1)+(n-2)+…+2+1=n(n−1)2,其时间复杂度为O(n2)。所以效率是最差的。
2. 选择排序
思想
选择排序的思想是在冒泡的基础上进行了一次改进。冒泡排序每比较一次差不多都需要交换位置,而选择排序则一趟比较,只需要交换一次。它的思维是将最小的进行标记,然后和待排序数组的第一个值进行交换。
图解
Java代码
public void sort(int[] array) {
for (int i = 0; i < array.length; i++) {
int index = i;
int temp = array[index];
for (int j = i; j < array.length; j++) {
if (array[index] > array[j]) {
index = j;
}
}
array[i] = array[index];
array[index] = temp;
}
}
效率
选择排序和冒泡相比,虽然时间复杂度并没有变化,只是它的复制交换次数变少了。如果是一个对象排序,复制一个对象耗时如果比较大的话,选择排序将是更好的选择。
3. 插入排序
思想
插入排序的思想是,一个数组,前面有k个值是已经排好序的,然后从k+1开始,依次从后向前比较,插入到已排序的数组的合适位置。
图解
实现
public static void sort(int[] array) {
for (int i = 1; i < array.length; i++) {
int index = i - 1;
while (index >= 0 && array[index] >= array[index + 1]) {
int temp = array[index];
array[index] = array[index + 1];
array[index + 1] = temp;
index--;
}
}
}
效率
插入排序的最差情况是,每次插入时,都将插入到第一位,比较次数依次为:1+2+……+(n-1)。和冒泡排序相等。这是插入排序的最差情况。最好情况是0,即已经是有序了。所以,插入排序的平均值是最差的一半。也就是n(n−1)4
综上可以看出,插入排序速度理论上是冒泡排序的一倍。以上三种方式插入排序是简单排序里面最快的一种,而且也易于理解。
4. 归并排序
思想
面试的时候,面试官问了我一个问题,两个有序的数组,合并后继续保持有序,如何实现。当时想都没想,回答合并再排序。面试官继续问,还有没有更好的方式。在这之前并没有归并的概念(也许大学是学过,不过我确实不记得归并的思想),于是我简单想了一下,一次合并的时候,就让它有序。
例如:int[] a = {1,3,5,7,9};int[] b = {0,2,4,6,8};合并到c数组时:
1与0比较,0小,0先放入c,接着b数组指针向后移动一位,继续比较,1比2小,1放入c中,a数组指针向后移动一位。依次类推,就能完成。
归并排序的思想是根据此为基础,将一个数组拆分成一半,再拆分……依次合并。最终得到一个有序数组。