算法概念
由一系列有限指令组成,完成某个任务的策略。
算法(Algorithm) 是指解题方案的准确且完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定的规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
算法特性:
有穷性(Finiteness ):算法的有穷性是指算法必须能在执行有限个步骤之后终止;
确切性(Definiteness ):算法的每一步骤必须有确切的定义;
输入项(Input):一个算法有0个或多个输出,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
输出项(Output):一个算法有1个或多个输出,以反应对输入数据加工后的结果,没有输出的算法是毫无意义的;
可行性(Effectiveness ):算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性)。
算法的衡量
时间复杂度(运行次数):执行这个算法所需要的计算工作量
空间复杂度(变量个数):执行这个算法所需要的内存空间
时间复杂度
for(int i = 0;i < n;i++){
for(int j = 0;j < 2 * n + 1;j++){
...
}
}
执行次数为 2 n * n + n
-
去掉常量
-
保留最高阶 2 n2
-
去掉系数 n2
-
以O括起来 ---> O(n2)
for(int i = 0;i < 1000;i++){
...
}
时间复杂度: 如果是固定的值 记为 O(1)
for(int i = 0;i < 2 * n + 200;i++){
..
}
时间复杂度: O(n)
for(int i = 0;i < 2 * n + 200;i+=2){
..
}
时间复杂度:O(logn)
排序:时间从小到大
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n!)
查找算法
顺序查找
从头找到尾,发现相同就返回数据的位置
public int find(int[] array,int number){
for(int i = 0;i < array.length;i++){
if(array[i] == number){
return i;
}
}
return -1;
}
时间复杂度: O(n)
二分查找
适合排序后的数组
1) 找中间数
2) 和查找数进行比较,如果中间数小就淘汰掉中间数前的所有数
3) 如果中间数大就淘汰后面数
4) 再次执行1) 2) 3)
public static int binarySearch(int[] array,int key){
int begin = 0;
int end = array.length-1;
while (begin <= end){
int middle = (begin+end)/2;
if (key == array[middle]){
return middle;
}else if (array[middle]<key){
//如果中间数小于查找数,就淘汰前半部分
begin = middle+1;
}else {
//如果中间数大于查找数,就淘汰后半部分
end = middle-1;
}
}
return -1;
}
时间复杂度:O(logn)
排序算法
八大排序:
-
冒泡排序
-
插入排序
-
选择排序
-
快速排序
-
希尔排序
-
归并排序
-
堆排序
-
基数排序
冒泡排序
1)分为多轮
2)每轮将数据两两相比
3)将较大的交换到后面
多个数字来排队,两两相比小靠前,外层循环n-1,内层循环n-1-i
for(int i = 0;i < array.length - 1;i++){
for(int j = 0;j < array.length - 1 - i;j++){
if(array[j] > array[j + 1]){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
O(n2)
插入排序
类似扑克牌排序
1) 从第二个数开始作为插入数
2) 和前面的每个数进行比较
3) 如果前面的数比插入数大,就向后移动一位,直到前面数小于等于插入数
4) 将后面的数作为插入数,反复执行1)2)3)
//外层循环控制插入数
for(int i = 1;i < array.length;i++){
int temp = array[i];//保存插入数
int j;
//前面的数比插入数大,就向后移动一位
for(j = i;j > 0 && array[j - 1] > temp;j--){
array[j] = array[j - 1]; //向后移
}
//插入到最后停止位置
array[j] = temp;
}
O(n2)
选择排序
分为多轮
第1轮从第1个开始到结尾,找到最小值,和第1个交换
第2轮从第2个开始到结尾,找到最小值,和第2个交换
....
//外层控制轮数
for(int i = 0;i < array.length;i++){
int min = i;
//内层控制找最小值的位置
for(int j = i;j < array.length;j++){
if(array[j] < array[min]){
min = j;
}
}
//交换min和i的位置
if(min != i){
int temp = array[min];
array[min] = array[i];
array[i] = temp;
}
}
快速排序
选取一个基准数,通过一轮排序,将整个数组分为两部分,前部分比基准数小,后半部分比基准数大。
再讲前半部分和后半部分重复上面的操作。
分割 + 交换 + 递归
1)选择第一个数为基准数,定义左右两个下标 2)从右下标开始移动,如果右边的数比基准数大或相等,就向左移动 3)从左下标开始移动,如果左边的数比基准数小或相等,就向右移动 4)如果左右没有重合,交换左右下标 5)再执行2)3)4)操作 直到左右下标重合 6)将下标和基准数进行交换 7)将基准数的左右两部分再次进行上面操作
/**
* 选取基准数,将数据分割
*/
public static int pivot(int[] array,int begin,int end){
//选取第一个数为基准数
int pivot = begin;
//定义左右下标
int left = begin;
int right = end;
//如果左右没有重合
while(left != right){
//如果右边数大于等于基准数,右下标左移
while(left < right && array[right] >= array[pivot]){
right--;
}
//如果左边数小于等于基准数,左下标右移
while(left < right && array[left] <= array[pivot]){
left++;
}
//如果左右没有重合,交换左右下标的数据
if(left < right){
int temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
//左右重合后,将基准数和下标位置交换
int temp = array[pivot];
array[pivot] = array[left];
array[left] = temp;
//返回下标作为下一次基准数
return left;
}
public static void quickSort(int[] array,int begin,int end){
//开始位置超过结束位置,结束排序
if(begin > end){
return;
}
//获得基准数
int pivot = pivot(array, begin, end);
//对左半部分继续操作
quickSort(array,begin,pivot - 1);
//对右半部分递归
quickSort(array,pivot + 1,end);
}
最好: O(nlogn) 最差:O(n2)