希尔排序
希尔排序是插入排序的优化,添加了增长量的概念,将相距某个“增量”的记录组成一个子序列,在子序列中进行插入排序,然后减小增量,重复上述操作,继续进行排序,直至增量为一。
对于增量,我们按照每次排序为数组的长度除以三减一(即 h=a.length/3-1 )每次的除以2,知道h为1.
分析
1.选定一个增量为h,以h为依据进行分组
2.每组进行插入排序
3.减小h的值,重复操作
代码实现
public class ShellTest1 {
public static void main(String[] args) {
int[] a={1,33,465,75,47,457856,8,56,8,5768,56,87,56,7,56,7};
shell(a);
System.out.println(Arrays.toString(a));
}
public static void shell(int[] a){
//对数组中的元素进行分组
int h = a.length/3-1;
while (h>=1){
//按照增量进行分组
for (int i = h ;i<a.length;i++){
//每次减小增量进行比较
for (int j = i;j>=h;j-=h){
if (a[j]<a[j-h]){
int temp = a[j];
a[j]=a[j-h];
a[j-h]=temp;
}else{
break;
}
}
}
//每次分组比较后减小h,知道减小为1
h/=2;
}
}
}
结果
[1, 7, 7, 8, 8, 33, 47, 56, 56, 56, 56, 75, 87, 465, 5768, 457856]
稳定性和时间复杂度的分析
希尔排序是对插入排序的优化,在每次比较交换的时候只有结果为小于才会出现交换,所以希尔排序是稳定的。
希尔排序的时间复杂度是:O(nlogn)~O(n2),平均时间复杂度大致是O(n√n),可以用最坏的情况验证,将100000~1一次加入数组中,查看希尔排序与插入排序所需的时间。
归并排序
归并排序就是用归并的思想来实现,将一个队列分成一个个小的队列,完成一系列操作后在合并。
分析
1.尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是
1为止。
2.将相邻的两个子组进行合并成一个有序的大组;
3.不断的重复步骤2,直到最终只有一个组为止。
代码实现
public class Merge {
public static void main(String[] args) {
int[] a = {1,231,43,5,2,52,43,4,24,23};
int[] temp = new int[a.length];
merge(a);
System.out.println(Arrays.toString(a));
}
public static void merge(int[] a){
int left = 0 ;
int rigth =a.length-1;
int[] temp = new int[a.length];
mergeDevide(a,left,rigth,temp);
}
//分解
public static void mergeDevide(int[] a,int left , int right ,int[] temp){
if (left<right){
int mid = (right+left)/2;
//向左递归分解
mergeDevide( a, left , mid , temp);
//向右递归分解
mergeDevide(a,mid+1,right,temp);
merge1(a,left,mid,right,temp);
}
}
//合并
/**
*
* @param a 需要排序的数组
* @param left 左边临时数组的下标
* @param mid 区分两个临时数组
* @param right 右边数组的最后下标
* @param temp 用来储存数据的临时数组
*/
public static void merge1(int[] a , int left , int mid,int right,int[] temp){
int i = left;
int j = mid+1;
int t = 0;
//1.将两边的有序数据填充到temp中,直到有一方填充完毕
while (i<=mid && j<=right){
//左边的元素小于右边的元素,则将左边元素填充到temp中
if (a[i]<a[j]){
temp[t]=a[i];
i+=1;
t+=1;
}else{//反之将左边的数据填充到temp中
temp[t]=a[j];
j+=1;
t+=1;
}
}
//2.将另一方剩下的数据填充到temp中
while (i<=mid){
//左边有剩余则把数组填充到temp中
temp[t]=a[i];
i+=1;
t+=1;
}
while (j<=right){
//右边有剩余则把数组填充到temp中
temp[t]=a[j];
j+=1;
t+=1;
}
//3.将临时数组temp拷贝到a中
t=0;
int tempLeft =left;
while (tempLeft<=right){
a[tempLeft] = temp[t];
t+=1;
tempLeft+=1;
}
}
}
结果
[1, 2, 4, 5, 23, 24, 43, 43, 52, 231]
稳定性和时间复杂度的分析
归并排序在归并的过程中,只有arr[i]<arr[i+1]的时候才会交换位置,如果两个元素相等则不会交换位置,所以它并不会破坏稳定性,归并排序是稳定的
归并排序时间复杂度:O(n log n)
快速排序
快速排序是通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对两部分记录继续进行排序,以达到整个序列有序的目的。
分析
1.首先选择一个基准数,用来和其他的数据进行比较,一班都是用数据中的第一个数来作为基准数。
2.从这组数据的最后一个位置往前与基准数比较,如果比基准数大,则继续往前比较,否则与基准数交换位置。
3.用基准数的后一个数组与基准数比较,如果比基准数大,则交换位置,否则用下一个数据继续比较。
4.最终所有的数据都与基准数比较完。
代码实现
public class QuickTest{
public static void main(String[] args) {
int[] a={100,3,111,54,545,65,8,324,45,8789,12};
quickSort(a);
System.out.println(Arrays.toString(a));
}
/*
* 首先需要一个数组存放所有的数据
* 定一个开始位置和一个结束为止
* 选择一个数作为准基数
*/
public static void quickSort(int[] a){
int min = 0;
int max = a.length-1;
sort(a,min,max);
}
public static void sort(int a[],int min,int max) {
int key=a[min];//准基数
int i=min; //开始位置
int j =max;//结束位置
while(j>i) { //循环条件是否数值交叉
//从后开始往前查找
while(j>i&&a[j]>=key) {
//如果找到的值大于基数值,那么继续往下找,end--
j--;
}
//如果找到的值小于基数值,那么进行值交换
if(a[j]<key) {
int temp=a[j];
a[j]=a[i];
a[i]=temp;
}
//从前往后找
while(j>i&&a[i]<=key) {
//如果找到的值小于基数值,那么继续往下找,start++
i++;
}
//如果找到的值大于基数值,那么进行值交换
if(a[i]>key) {
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
//这部分的数据都是小于准基数,通过递归在进行一趟快排
if(i>min) {
sort(a, min, i-1); //开始位置为第一位,结束位置为关键索引-1
}
if(j<max) {
sort(a, j+1, max); //开始位置为关键索引+1,结束位置最后一位
}
}
}
结果
[3, 8, 12, 45, 54, 65, 100, 111, 324, 545, 8789]
稳定性和时间复杂度的分析
快速排序需要一个基准值,在基准值的右侧找一个比基准值小的元素,在基准值的左侧找一个比基准值大的元素,然后交换这两个元素,此时会破坏稳定性,所以快速排序是一种不稳定的算法
最优情况下快速排序的时间复杂度为O(nlogn);
最坏情况下,快速排序的时间复杂度为O(n^2);