前言:
近期在准备面试的时候,在准备算法的排序的过程中,查看了许多网上许多博客,发现很多博客总结的不是很全面,而且有部分博客中也存在着一些误差,于是我就花费了几天的时间,研究了一下排序算法,对排序算法做了一个比较全面的总结。包含了我们常使用的十大排序算法。
排序的定义
****对一组对象,按照某一规格进行有序排列
排序常见术语
- 稳定:排序要求必须正确,不能出现任何差错,比如两个数相同,则排序后两个数的顺序不能够改变
- 不稳定:相对于稳定而言,就是两个相同的数,排序后两个数的位置有可能发生改变。
- 内排序:所有的排序操作都在内存中完成(这个就要对操作系统内存概念有所了解)
- 外排序:由于数据太大,内存放不下,数据放在磁盘中,排序通过磁盘和内存的数据传输才能进行。
- **时间复杂度:**一个算法执行所需要的系统时间
- **空间复杂度:**一个算法执行所需要的内存空间大小
算法时间空间复杂度总结
一,冒泡排序
冒泡排序是排序算法中比较简单的排序算法,也是大多数人都会的算法,但是冒泡排序算法的时间性能较低,每一次比较相邻的两个元素,一次循环找到一个最大值放到最后。
1.1 算法描述
- 每一次比较相邻的两个元素,相邻的两个元素中较大的放到后面,遍历一次找到最大值放到最后
- 循环遍历剩余数
1.2 动图演示
1.3 代码实现
这是最基础版本的冒泡排序。
public class bubbleSort{
public static void main(String[] args) {
int[] arr={3,4,2,1,5,7,0,1,2};
maoSort(arr);
}
public static void maoSort(int[] arr){
int count=0;
//对数组arr采用冒泡排序进行排序
System.out.println("------------------------排序前-------------------------");
printArr(arr);
int temp=0;
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-i-1;j++){
if(arr[j]<=arr[j+1]){}
else {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;}}
System.out.println("------------------第"+i+"轮-------------------------------");
printArr(arr);
}
System.out.println("------------------------排序后-------------------------");
printArr(arr);
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行结果:
算法分析:
从这个我们可以看出我们一共有9个元素,需要进行八轮排序,而且我们发现第六轮后我们就已经排序完成,后面就属于浪费资源。
冒泡排序算法升级
升级其实很简单就是设置一个标志位flag,因为我们一旦排序完成后就不需要在交换数据,所以我们只要判断该次循环是否交换数据,如果有数据交换则没有排序完,如果没有数据交换就表示交换完成。这样会省很多时间。
public class MaoPao {
public static void main(String[] args) {
int[] arr={3,4,2,1,5,7,0,1,2};
maoSort(arr);
}
public static void maoSort(int[] arr){
int count=0;
//对数组arr采用冒泡排序进行排序
System.out.println("------------------------排序前-------------------------");
printArr(arr);
int temp=0;
for(int i=0;i<arr.length-1;i++){
boolean flag=true;
for(int j=0;j<arr.length-i-1;j++){
if(arr[j]<=arr[j+1]){}
else {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
flag=false;
}
}
if(flag==true) break;
System.out.println("------------------第"+(i+1)+"轮-------------------------------");
printArr(arr);
}
System.out.println("------------------------排序后-------------------------");
printArr(arr);
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行结果:这样会省去大量时间性能浪费。
算法性能分析:
最佳情况:T(n) = O(n) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2),所以说无论怎么升级,时间性能还是比较低。
二:选择排序
选择排序(Selection-sort)也是一种简单直观的排序算法,也是大家比较容易想得到的排序算法,他的工作原理是:首先在未排序的里面找到最小或最大的的元素,存放到数组的起始位置或末尾位置,然后再从剩余的元素里面找到最小的元素放到已排序的数组后面。
算法描述
- 有一组数,遍历其中的数找到最小的放大其实位置。
- 然后从去掉排序的剩余数里面找到最小的放到已经排序的后面
动图演示
代码
基础版本
public class Selection{
public static void main(String[] args) {
int[] arr={3,4,2,1,5,7,0,1,2};
xzSort(arr);
}
public static void xzSort(int[] arr){
//对数组arr采用冒泡排序进行排序
System.out.println("------------------------排序前-------------------------");
printArr(arr);
for(int i=0;i<arr.length-1;i++){
int min=arr[i];
int temp;
int index=i;
for(int j=i;j<arr.length;j++){
if(min>arr[j]){
min=arr[j];
index=j;
}
}
//遍历一遍后我们可以得到最小元素的下标,然后交换
temp=arr[i];
arr[i]=arr[index];
arr[index]=temp;
System.out.println("------------------第"+(i+1)+"轮-------------------------------");
printArr(arr);
}
System.out.println("------------------------排序后-------------------------");
printArr(arr);
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行结果
升级后,防止排序后继续排序浪费多余时间性能。和冒泡排序一样添加一个标志位。比如如果是排序数组,为[1,2,3,4,5,6,7],这样就浪费时间性能。
代码升级
public class Selection{
public static void main(String[] args) {
int[] arr={1,2,3,4,5,6,7};
xzSort(arr);
}
public static void xzSort(int[] arr){
//对数组arr采用冒泡排序进行排序
System.out.println("------------------------排序前-------------------------");
printArr(arr);
for(int i=0;i<arr.length-1;i++){
int min=arr[i];
int temp;
int index=i;
boolean flag=true;
for(int j=i;j<arr.length;j++){
if(min>arr[j]){
min=arr[j];
index=j;
flag=false;}}
//遍历一遍后我们可以得到最小元素的下标,然后交换
if(flag==true)break;
temp=arr[i];
arr[i]=arr[index];
arr[index]=temp;
System.out.println("------------------第"+(i+1)+"轮-------------------------------");
printArr(arr);
}
System.out.println("------------------------排序后-------------------------");
printArr(arr);
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行截图
算法分析
最佳情况:T(n) = O(n2) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2)
三:插入排序
****插入排序(Insertion-Sort)的算法是一种简单的排序算法。他的工作原理是通过构建有序序列,选择未排序的数列第一项,插入到已排序数列中的合适位置中。
算法描述
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素列表中从后向前扫描
- 如果该元素(已经排序)大于新元素,将该元素移到下一位
- 重复步骤
动图演示
代码实现
public class InsertSort {
public static void main(String[] args) {
int[] arr={3,4,2,1,5,7,0,1,2};
ISort(arr);
}
public static void ISort(int[] arr){
//对数组arr采用插入排序进行排序
//排序的思想是,选择未排序的第一个元素
//将这个元素插入到前面已经排序的合适位置中
System.out.println("------------------------排序前-------------------------");
printArr(arr);
int current;
for(int i=0;i<arr.length-1;i++){
current=arr[i+1];
int preIndex=i;
while(preIndex>=0&¤t<arr[preIndex]){
arr[preIndex+1]=arr[preIndex];
preIndex--;
}
arr[preIndex+1]=current;
System.out.println("------------------------第"+(i+1)+"轮-------------------------");
printArr(arr);
}
System.out.println("------------------------排序后-------------------------");
printArr(arr);
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行结果
算法分析
最佳情况:T(n) = O(n) 最坏情况:T(n) = O(n2) 平均情况:T(n) = O(n2)
四:希尔排序
****希尔排序(Shell)是希尔提出的一种排序算法。希尔排序也是一种插入排序,他是简单插入排序经过改进之后的一个更加高效的版本,也称为缩小量排序,同时该算法是冲突最大时间O(n^2)限制的第一批算法。他与插入排序不同的是,他会优先比较距离较远的元素。所以希尔排序又叫缩小量排序。
算法描述
****我们从图上可以看出,希尔排序的基本步骤,我们就是先对数组进行分组,然后分别进行组内排序,首先我们选择的分组间隔为数组长度的一半,然后依次减半直到间隔减为0为止。这里的每个组内排序采用的都是插入排序。对插入排序不了解的可以参考上面的插入排序算法。
详细设计思想
图解虽然很详细了,但是具体的设计还是需要结合代码来进行讲解,这样才能是大家更加清晰明了的熟悉希尔排序的原理。
组内排序算法讲解,这个是一次分组后的排序过程。
依次直到间距为1.
代码实现
public class ShellSort {
public static void main(String[] args) {
int[] arr={3,4,2,1,5,7,0,1,2};
sSort(arr);
}
public static void sSort(int[] arr){
System.out.println("------------------------排序前-------------------------");
printArr(arr);
//希尔排序算法
int len=arr.length;
int temp,grap=len/2;
//间隔最后到间隔为1截止
while (grap>0){
for(int i=grap;i<len;i++){
temp=arr[i];
int preIndex=i-grap;
while(preIndex>=0&&temp<arr[preIndex]){
arr[preIndex + grap] = arr[preIndex];
preIndex -= grap;
}
arr[preIndex+grap]=temp;
}
grap/=2;
}
System.out.println("------------------------排序后-------------------------");
printArr(arr);
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行结果
****
五:归并排序
归并排序和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
归并排序是建立在归并的操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
算法描述
- 帮长度为n的输入序列分成两个长度为n/2的子序列
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
动图显示
public class MergeSort {
public static void main(String[] args) {
int[] arr={3,4,2,1,5,7,0,1,2};
System.out.println("------------------------排序前-------------------------");
printArr(arr);
System.out.println("------------------------排序后-------------------------");
printArr(mSort(arr));
}
public static int[] mSort(int[] arr){
if (arr.length < 2) return arr;
int mid=arr.length/2;
int[] left = Arrays.copyOfRange(arr, 0, mid);
int[] right = Arrays.copyOfRange(arr, mid, arr.length);
return merge(mSort(left),mSort(right));
}
public static int[] merge(int[] left, int[] right) {
//需要开辟一个新的数组用于保存归并后的数组
int[] result=new int[left.length+right.length];
for(int index=0,i=0,j=0;index<result.length;index++){
if(i>=left.length){
result[index]=right[j++];
}
else if(j>=right.length){
result[index]=left[i++];
}else if(left[i]>right[j]){
result[index]=right[j++];
}else
result[index]=left[i++];
}
return result;
}
//打印数组元素
public static void printArr(int[] arr){
for(int a:arr){
System.out.print(a+" ");
}
System.out.println();
}
}
运行结果
总结
这是对十大经典排序的前五种排序进行了总结,分析下期会继续分享剩余的5种排序算法。以上代码我都已经进行了调试没有错误,希望可以对小伙伴有所帮助。