文章目录
前言
冒泡排序,选择排序、插入排序,在最坏情况下的时间复杂度都是O(N^2),随着输入规模的增大,时间成本将急剧上升,所以冒泡排序,选择排序、插入排序不能处理大规模的问题
一. 希尔排序
1.1 排序原理
希尔排序是插入排序的一种,是插入排序算法的一种更高效的改进版本。
1.选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;
2.对分好组的每一组数据完成插入排序;
3.减小增长量,最小减为1,重复第二步操作。
增长量h的确定:
int h = 1;
while (h < 数组长度/2) {
h=2*h+1;
}
//循环结束后就可以确定h的最大值;
//h的减小规则为: h=h/2
1.2 希尔排序api设计和代码实现
类名 | Shell |
---|---|
构造方法 | Shell():创建Shell对象 |
成员方法:
public static void sort(Comparable[] a):对数组内的元素进行排序 ;
private static boolean greater(Comparable v,Comparable w):判断v是否大于w;
private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值
public class Shell {
public static void sort(Comparable[] a) {
/*第一步,根据数组的长度,确定增长量h*/
int h = 1;
while (h < a.length / 2) {
h = 2 * h + 1;
}
/*希尔排序*/
while (h >= 1) {
//排序
//找到待插入的元素
for (int i = h; i < a.length; i++) {
//把待插入的元素插入到有序数列中
for (int j = i; j >= h; j -= h) {
//待插入的元素为a[j],比较a[j]和a[j-h]
if (greater(a[j - h], a[j])) {
exch(a, j - h, j);
} else {
//待插入元素已经找到合适的位置,结束循环
break;
}
}
}
//缩小增长量
h /= 2;
}
}
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
1.3 测试
public static void main(String[] args) {
Integer[] a = new Integer[100000];
for(int i=0;i<100000;i++){
int element = new Random().nextInt(1000);
a[i]=element;
}
long start = System.currentTimeMillis();
Shell.sort(a);
long end = System.currentTimeMillis();
System.out.println(Arrays.toString(a));
System.out.println("排序用时:"+(end-start));
}
1.4 希尔排序的时间复杂度分析
在希尔排序中,增长量h并没有固定的规则,有很多论文研究了各种不同的递增序列,但无法证明某个序列是最好的,对于希尔排序的时间复杂度分析,需要进一步研究
二. 递归
2.1 概念
把一个大型复杂的问题,层层转换为一个与原问题相似的,规模较小的问题来求解。递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量
注:在递归中,不能无限制调用自己,必须要有边界条件
,能够让递归结束,因为每一次递归调用都会在栈内存开辟新的空间,重新执行方法,如果递归的层级太深,很容易造成栈内存溢出
。
2.2 代码实现
使用递归完成求N的阶乘
public class Factorial {
/*n的阶乘*/
public static long factorial(int n){
if(n==1){
return 1;
}
return n*factorial(n-1);
}
}
若递归层次太层,发生栈内存溢出
public static void main(String[] args) {
long res = Factorial.factorial(10000000);
System.out.println(res);
}
三. 归并排序
归并排序采用分治法,将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
3.1 排序原理
1.一组数据尽可能拆分成两个元素个数相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1
2.将相邻的两个子组进行合并成一个有序的大组;
3.不断的重复步骤2,直到最终只有一个组为止
3.2 归并排序api设计和代码实现
类名 | Merge |
---|---|
构造方法 | Merge():创建Merge对象 |
成员方法:
public static void sort(Comparable[] a):对数组内的元素进行排序 ;
private static void sort(Comparable[] a, int lo, int hi):对数组a中从索引lo到索引hi之间的元素进行排序
private static void merge(Comparable[] a, int lo, int mid, int hi):从索引lo到所以mid为一个子组,从索引mid+1到索引hi为另一个子组,把数组a中的这两个子组的数据合并成一个有序的大组(从索引lo到索引hi)
private static boolean less(Comparable v,Comparable w):判断v是否小于w
private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值
成员变量:
1.private static Comparable[] assist:完成归并操作需要的辅助数组
public class Merge {
private static Comparable[] assist;
public static void sort(Comparable[] a) {
//初始化辅助数组 assist
assist = new Comparable[a.length];
//定义一个lo变量和hi变量,用于记录数组中的最小索引和最大索引
int lo = 0;
int hi = a.length - 1;
//调用sort重载方法完成数组a,从索引lo到hi元素排序
sort(a, lo, hi);
}
private static void sort(Comparable[] a, int lo, int hi) {
//安全性校验
if (hi <= lo) {
return;
}
//对lo和hi之间的数据进行分组
int mid = lo + (hi - lo) / 2;
//分别对每组数据进行排序
sort(a, lo, mid);
sort(a, mid + 1, hi);
//再把2组中的数据进行归并
merge(a, lo, mid, hi);
}
private static void merge(Comparable[] a, int lo, int mid, int hi) {
//定义三个指针
int i = lo;
int p1 = lo;
int p2 = mid + 1;
//遍历,移动p1指针和p2指针,比较p1指针和p2指针对应索引处的值,找出较小的值,放入辅助数组的对应索引处
while (p1 <= mid && p2 <= hi) {
if (less(a[p1], a[p2])) {
assist[i++] = a[p1++];
} else {
assist[i++] = a[p2++];
}
}
//遍历,如果p1指针没有走完,那么顺序移动p1指针,把对应元素放入助数组的索引处
while (p1 <= mid) {
assist[i++] = a[p1++];
}
//遍历,如果p2指针没有走完,那么顺序移动p1指针,把对应元素放入助数组的索引处
while (p2 <= hi) {
assist[i++] = a[p2++];
}
//把辅助数组中的元素拷贝到原数组中
for (int index = lo; index <= hi; index++) {
a[index] = assist[index];
}
}
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
3.3 测试
public static void main(String[] args) {
Integer[] a = new Integer[10000];
for(int i=0;i<10000;i++){
int element = new Random().nextInt(1000);
a[i]=element;
}
long start = System.currentTimeMillis();
Merge.sort(a);
long end = System.currentTimeMillis();
System.out.println(Arrays.toString(a));
System.out.println("排序用时:"+(end-start));
}
3.4 归并排序的时间复杂度分析
对a[lo...hi]进行排序,先将它分为a[lo...mid]和a[mid+1...hi]两部分,分别通过递归调用将他们单独排序,
最后将有序的子数组归并为最终的排序结果。该递归的出口在于如果一个数组不能再被分为两个子数组,那么就会
执行merge进行归并,在归并的时候判断元素的大小进行排序。
log2(n)* 2^(log2(n))=log2(n)*n,
根据大O推导法则,忽略底数,最终归并排序的时间复杂度为O(nlogn);
归并排序的缺点:需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作。