第三章 数组
3.5 算法的5大特征
说明:满足确定性的算法也称:确定性算法。现在人们也关注更广泛的概念,例如考虑各种非确定性的算法,如并行算法,概率算法等。另外,人们也关注并不要求终止的计算描述,这种描述有时被称为过程(procedure)。
3.6 数组元素的排序算法
3.6.1 排序算法的概念
排序:假设含有n个记录的序列为{R1,R2,...,Rn},其相关的关键字序列{K1,K2,...,Kn}。将这些记录重新排序为{Ri1,Ri2,...,Rin},使得相应的关键字值满足条件Ki1<=Ki2<=...<=Kin,这样的一种操作称为排序。通常来说,排序的目的是快速查找。
3.6.2 衡量排序算法的优劣:
时间复杂度:分析关键字的比较次数和记录的移动次数
空间复杂度:分析排序算法中需要多少辅助内存
稳定性:若两个记录A和B的关键字值相等,但排序后A,B的先后次序保持不变,则称这种排序算法是最稳定的
3.6.3 排序算法分类:内部排序和外部排序
内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成的。
3.6.4 十大内部排序算法
选择排序:直接选择排序,堆排序
交换排序:冒泡排序,快速排序
插入排序:直接插入排序,折半插入排序,Shell排序
归并排序,桶式排序,基数排序
3.6.5 冒泡排序
介绍:冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
排序思想:
①.比较相邻的元素。如果第一个比第二个大(升序),就交换它们两个。
②.对每一对相邻元素做同样的操作,从开始的第一对到结束的最后一对,全部交换操作完成之后最后面的元素会是最大的元素。
③.针对所有的元素重复以上的步骤,除了最后一个。
④.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较为止。
public class ArrayTest {
public static void main(String[] args) {
//初始化一个10位数组
int arr[] = {22, 33, 11, 23, 76, 348, 89, 2, 54, 211};
//冒泡排序
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
3.6.6 快速排序
介绍:快速排序通常明显比同为O(nlogn)的其他算法更快,因此常被采用,而且快速排序采用了分治法的思想,所以在很多笔试面试中能常看到快排的影子。可见掌握快排的重要性。
快速排序(Quick Sort)由图灵奖获得者Tony Hoare发明,被列为20世纪十大算法之一,是迄今为止所有内排序算法中速度最快的一种。冒泡排序的升级版,交换排序的一种。快速排序的时间复杂度为O(nlog(n))。
package ca.demo03;
public class ArrayTest {
public static void quickSort(int[] arr, int low, int high) {
int i, j, temp, t;
if (low > high) {
return;
}
i = low;
j = high;
//temp就是基准位
temp = arr[low];
while (i < j) {
//先看右边,依次往左递减
while (temp <= arr[j] && i < j) {
j--;
}
//再看左边,依次往右递增
while (temp >= arr[i] && i < j) {
i++;
}
//如果满足条件则交换
if (i < j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j - 1);
//递归调用右半数组
quickSort(arr, j + 1, high);
}
public static void main(String[] args) {
int[] arr = {10, 7, 2, 4, 7, 62, 3, 4, 2, 1, 8, 9, 19};
quickSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
3.6.7 排序算法性能对比
①.从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归并排序。
②.从算法简单性看:由于直接选择排序,直接插入排序和冒泡排序的算法比较简单,将其认为简单算法。对于Shell排序,堆排序,快速排序和归并排序算法,其算法比较复杂,认为是复杂排序。
③.从稳定性看:直接插入排序,冒泡排序和归并排序是稳定的,而直接选择排序,快速排序,Shell排序和堆排序是不稳定排序。
④.从待排序的记录数n的大小看,n较小时,宜采用简单排序,而较大时宜采用改进排序。
3.6.8 排序算法的选择
①.若n较小(如n≤50),可采用直接插入或直接选择排序。当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应该直接选择排序为宜。
②.若文件初始状态基本有序(指正序),则应选用直接插入,冒泡或随机的快速排序为宜。
③.若n较大时,则应采用时间复杂度为O(nlgn)的排序方法:快速排序,堆排序或归并排序。
3.7 Arrays工具类的使用
java.util.Arrays类即为操作数组的工具类,包含了用来操作数组(比如排序和搜索)的各种方法。
import java.util.Arrays;
public class ArrayTest {
public static void main(String[] args) {
//判断两个数组是否相等
int[] arr1 = {1,2,3,4};
int[] arr2 = {1,2,3,4};
if (Arrays.equals(arr1, arr2)) {
System.out.println("数组arr1与数组arr2相等");
}else {
System.out.println("数组arr1与数组arr2不相等");
}
//输出数组信息
String string = Arrays.toString(arr1);
System.out.println(string);
//将指定值填充到数组之中
Arrays.fill(arr1,5);
System.out.println(Arrays.toString(arr1));
//对数组进行排序
int[] arr3 = {2,3,6,1,0,7};
Arrays.sort(arr3);
System.out.println(Arrays.toString(arr3));
//对排序后的数组进行二分检索指定的值
int i = Arrays.binarySearch(arr3, 6);
System.out.println(i);
}
}
3.8 数组使用中常见异常
数组脚标越界异常:数组的索引中不存在需要访问的索引位置。
空指针异常:访问空数组,访问不存在的数组。
练习
1.使用冒泡排序,实现如下的数组从小到大排序
int[ ] arr = new int[ ]{34,5,22,-98,6,-76,0,-3}