算法的定义
算法可以在有限的空间和时间内用定义明确的形式语言来表示,以计算函数。算法的一个典型例子是欧几里德算法,用于确定两个整数的最大公约数。在逻辑上,一个算法完成所需的时间是无法测量的,因为它与我们习惯的物理维度无关,这种不确定性导致无法找到既适合在某种意义上又适合抽象术语使用的算法定义。简言之则是解决数学题的最好方法。
一、时间复杂度
在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为O(n)。
二、空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。
三、冒泡排序(Bubble Sort)
/**
* 冒泡排序算法 具有稳定性
*
* 冒泡次数 冒泡后的结果
* -----------------------------------
* 初始结果 {9,8,10,7,6,0,11}
* 第1次冒泡 {8,9,8,6,0,10,11} 比较了6次 取出最大值11 剩下6个数
* 第2次冒泡 {8,8,6,0,9,10,11} 取出最大值10 剩下5个数
* 第3次冒泡 {8,8,6,0,9,10,11} 取出最大值9 剩下4个数
* 第4次冒泡 {8,6,0,8,9,10,11} 取出最大值8 剩下3个数
* 第5次冒泡 {6,0,8,8,9,10,11} 取出最大值8 剩下2个数
* 第6次冒泡 {0,6,8,8,9,10,11} 取出最大值6 剩下1个数
*
* 冒泡排序的原理:从左边开始相邻的两个往右边比较,相当于在比较的过程中将大的数一直右移
* 冒泡排序的特点:冒泡次数为(总个数-1),总比较次数为(2加到length),从数组下标为0,一直到数组下标为(length-1)
*/
public class Bubble{
public static void sort(Comparable[] arr){
for(int i = arr.length - 1; i > 0 ; i--){
for(int j = 0;j < i; j++){
//如果相邻两个数中左边的值大于右边的值,则交换
if(greater(arr[j],arr[j + 1])){
exch(arr, j, j + 1);
}
}
}
}
//比较两值的大小
public static boolean greater(Comparable v,Comparable w){
return v.compareTo(w) > 0;
}
//交换两值位置的方法
public static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
四、选择排序(Selection Sort)
/**
* 选择排序算法 不具有稳定性
*
* 选择次数 选择后的结果
* -------------------------------------------
* 初始结果 {3,11,64,21,65,37,9}
* 第1趟排序 {3,11,64,21,65,37,9}
* 第2趟排序 {3,9,11,64,21,65,37}
* 第3趟排序 {3,9,11,21,64,65,37}
* 第4趟排序 {3,9,11,21,37,64,65}
* 第5趟排序 {3,9,11,21,37,64,65}
* 选择排序的原理:假定第一初索引元素为最小值,与其他索引处的值依次进行比较,
* 如若当前索引较大,则假定与其相比较的值最小,最后找到后放置最左边。
*/
public class Select{
public static void sort(Comparable[] arr){
for(int i = 0 ; i < arr.length - 2 ; i ++){
int min = i;//假设i处的索引为最小的值
for(int j = i + 1; j < arr.length ; j++){
//如若某个索引的值小于初索引的值,则某个索引定义为min
if(greater(arr[min],arr[j])){
min = j;
}
}
exch(arr,i,min);
}
}
public static boolean greater(Comparable v,Comparable w){
return v.compareTo(w) > 0;
}
public static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
五、插入排序(Insertion Sort)
/**
* 插入排序算法 具有稳定性
*
* 插入次数 插入后的结果
* -----------------------------------------
* 初始结果 {3,11,64,21,65,37,9}
* 第1趟插入 {3,11,64,21,65,37,9} 从索引为1的11跟3比
* 第2趟插入 {3,11,64,21,65,37,9} 从索引为2的64跟11比
* 第3趟插入 {3,11,21,64,65,37,9} 从索引为3的21跟64比,交换位置再跟11比
* 第4趟插入 {3,11,21,64,65,37,9} 从索引为4的65跟64比
* 第5趟插入 {3,11,21,37,64,65,9} ....
* 第6趟插入 {3,9,11,21,37,64,65} ....
* 插入排序的原理:从数组索引为1的元素依次向左比较,如若左边的元素大于右边的元素就换位,将大的元素往右移
*/
public class Insert{
public static void sort(Comparable[] arr){
for(int i = 1 ; i < arr.length;i++){
for(int j = i ; j > 0;j--){
if(greater(arr[j - 1],arr[j])){
exch(arr,j - 1,j);
}else {
break;
}
}
}
}
public static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
public static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
六、希尔排序(Shell Sort)
/**
* 希尔排序算法(插入排序的改良版) 不具有稳定性
*
* 插入次数 插入后的结果
* -----------------------------------------
* 初始结果 {9,1,2,5,7,4,8,6,3,5}
* 第1趟排序 {4,1,2,3,5,9,8,6,5,7} 索引为0的9与索引为(0+5)的4一组 ....如若左比右大,就交换位置
* 第2趟排序 {2,1,4,3,5,6,5,7,8,9}
* 第3趟排序 {1,2,3,4,5,5,6,7,8,9}
* 希尔排序的原理:选定一个增长量,令其作为数据分组的依据,对分完组的数据完成插入排序,
* 再减小增长量(最小为1),重复分组对数据进行插入排序。
*/
public class Shell{
public static void sort(Comparable[] arr){
//1、根据数组a的长度,确定增长量h的初始值
int h = arr.length/2;
while(h >= 1){
//2、找到待插入的元素
for(int i = h; i < arr.length; i++){
//3、把待插入的元素插入到有序数列中
for(int j = i; j >= h; j = j - h){
//待插入的元素是a[j],与a[j-h]比较
if(greater(arr[j-h],arr[j])){
exch(arr,j-h,j);
}else{
break;
}
}
}
h = h/2;
}
}
public static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
public static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
七、归并排序(Merge Sort)
/**
* 归并排序 O(n log n) 不具有稳定性
*
* 分组次数 分组后的结果
* ----------------------------------------------------------------------
* 初始结果 {9,1,2,5,7,4,8,6,3,5}
* 第1趟分组 {9,1,2,5,7}、{4,8,6,3,5}
* 第2趟分组 {9,1,2}、{5,7}、 {4,8,6}、{3,5}
* 第3趟分组 {9,1}、{2}、{5}、{7}、 {4,8}、{6}、{3}、{5}
* 第4趟分组 {9}、{1}、{2}、{5}、{7}、 {4}、{8}、{6}、{3}、{5}
*
* 第1次合并 {1,9}、{2,5}、{7}、 {4,8}、{3,6}、{5}
* 第2次合并 {1,2,5,9}、{7}、 {3,4,6,8}、{5}
* 第3次合并 {1,2,5,7,9}、{3,4,5,6,8}
* 第4次合并 {1,2,3,4,5,5,6,7,8,9}
* 归并排序的原理:将一组原数据尽可能拆分成两个元素相同的子数据组,进而继续拆分到每个组都为1为之
* 将相邻两个子组合并成一个有序大组,不断重复此操作。
*/
public class Merge {
private static Comparable[] assist;//归并所需要的辅助数组
/*
对数组a的元素进行排序
*/
public static void sort(Comparable[] a){
//1、初始化辅助数组assist
assist = new Comparable[a.length];
//2、定义一个low变量和height变量,分别记录数组中最小的索引和最大的索引
int low = 0;
int height = a.length-1;
//3、调用sort重载方法完成数组a中,从索引low到索引height的元素排序
sort(a,low,height);
}
/*
对数组a中从low到height的元素进行排序
*/
public static void sort(Comparable[] a, int low, int height){
//1、做安全性校验
if(height <= low){
return;
}
//2、对low到height之间的数据进行分组
int mid = low + (height - low)/2;
//3、分别对每一组数据进行排序
sort(a,low,mid);
sort(a,mid + 1,height);
//4、再把分完组后的数据进行归并
merge(a,low,mid,height);
}
/*
对数组中,从low到mid为一组,从mid+1到height为一组,对这两组数据进行合并
*/
private static void merge(Comparable[] a,int low ,int mid,int height){
//1、定义三个指针
int i = low;
int p1 = low;
int p2 = mid + 1;
//2、遍历,移动两个指针,比较对应索引处的值,找出最小的放在辅助数组中
while(p1 <= mid && p2 <= height){
//比较对应所索引处的值
if(less(a[p1],a[p2])){
assist[i++] = a[p1++];
}else {
assist[i++] = a[p2++];
}
}
//3、遍历,如果第一个指针没有走完,那么顺序移动第一个指针,把对应的元素放到辅助数组的对应索引处
while(p1 <= mid){
assist[i++] = a[p1++];
}
//4、遍历,如果第二个指针没有走完,那么顺序移动第二个指针,把对应的元素放到辅助数组的对应索引处
while(p2 <= height){
assist[i++] = a[p2++];
}
//5、把辅助数组中的元素拷贝到原数组中
for(int index = low; index <= height; index++){
a[index] = assist[index];
}
}
private static boolean less(Comparable v,Comparable w){
//传入的前者(v.compareTo(w))的v若大于w,则返回1,综合返回为false
return v.compareTo(w) < 0;
}
}
八、快速排序(Quick Sort)
/**
* 快速排序 具有稳定性
*
* 快速排序的原理:1、首先设定一个分界值,用两个指针分别指向数组的头部和尾部
* 2、先从尾部向头部搜索比分界值小的元素,搜索到即停止,并记录指针的位置
* 3、再从尾部向头部搜索比分界值大的元素,搜索到即停止,并记录指针的位置
* 4、交换当前左边指针位置和右边指针位置
* 5、重复2,3,4步骤,直到左边指针的值大于右边指针的值停止
*/
public class Quick {
//对数组内的元素进行排序
public static void sort(Comparable[] a){
int low = 0;
int height = a.length - 1;
sort(a,low,height);
}
//对数组a中从索引low到索引height之间的元素进行排序
public static void sort(Comparable[] a,int low,int height){
//做安全性校验
if(height <= low){
return;
}
//对数组中索引low到height索引的元素进行分组(左子组和右子组)
int partition = partition(a,low,height);//返回的是分组的分界值所在的索引,分界值位置变换后的索引
//让左子组排序
sort(a,low,partition - 1);
//让右子组排序
sort(a,partition + 1,height);;
}
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w) < 0;
}
private static int partition(Comparable[] a,int low,int height){
//确定分界值
Comparable key = a[low];
//定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
int left = low;
int right = height + 1;
//切分
while(true){
//先从右往左扫描,移动right指针,找到一个比分界值小的元素,停止
while(less(key,a[--right])){
if(right == low){
break;
}
}
//再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
while(less(a[++left],key)){
if(left == height){
break;
}
}
//判断left >= right,是的话则证明元素扫描完毕,结束循环,如果不是,则交换元素即可
if(left >= right){
break;
}else {
exch(a,left,right);
}
}
//交换分界值
exch(a,low,right);
return right;
}
}