我在首次技术面试中就被问到了排序算法中效率的问题,我当时
只会冒泡排序与选择排序两种排序算法,很无奈,我没有通过技术面,
因而我对常用的排序算法进行了学习,快排为重点,面试经常遇到。
当然,我在这里只介绍了六种,剩下的我会后续跟进。有问题的地方
希望大家多多指点。
1、直接插入排序
我们经常会到这样一类排序问题:把新的数据插入到已经排好的数据列中。
将第一个数和第二个数排序,然后构成一个有序序列将第三个数插入进去,
构成一个新的有序序列。对第四个数、第五个数……直到最后一个数,重复第二步。
直接插入排序的基本思想:
在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经
是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。
如此反复循环,直到全部排好顺序。
缺点:多次的移动,假如一个很小的数在最右端的位置,那么要将该数据排序到正确的位置上,
则所有的中间数据都要向右移动一位。
代码实现:
首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。
设定插入数和得到已经排好序列的最后一个数下标值。insertNum和j=i-1。
从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。
将当前数放置到空着的位置,即j+1。
```
import java.util.Arrays;
public class ChaRu {
public static void insertSort(int [] a){
int len=a.length;//单独把数组长度拿出来,提高效率
int insertNum;//要插入的数
for(int i=1;i<len;i++){//因为第一次不用,所以从1开始
insertNum=a[i];
int j=i-1;//序列元素个数
while(j>=0&&a[j]>insertNum){//从后往前循环,将大于insertNum的数向后移动
a[j+1]=a[j];//元素向后移动
j--;
}
a[j+1]=insertNum;//找到位置,插入当前元素
}
System.out.println(Arrays.toString(a));
}
public static void main(String[] args) {
ChaRu.insertSort(new int[]{32,21,45,20,19,48,12});
}
}
```
2、希尔排序
针对直接插入排序的效率低下问题,有人对次进行了改进与升级,这就是现在的希尔排序。希尔排序,
也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
算法基本思想:
对于直接插入排序问题,数据量巨大时
将数的个数设为n,取奇数k=n/2,将下标差值为k的数分为一组,构成有序序列。
再取k=k/2 ,将下标差值为k的书分为一组,构成有序序列。
重复第二步,直到k=1执行简单插入排序。
优点:希尔排序通过加大插入排序中元素之间间隔,并对这些间隔的元素进行插入排序,
从而使得数据可以大幅度的移动,当完成该间隔的排序后,希尔排序会减少数据的间隔
再次进行排序。依次进行下去。
代码实现:
首先确定分的组数。
然后对组中元素进行插入排序。
然后将length/2,重复1,2步,直到length=0为止。
```
import java.util.Arrays;
public class XiEr {
public static void sheelSort(int[] arr){
int d=arr.length;
while (d!=0){
d=d/2;
//分组的数目
for (int x=0;x<d;x++){
//组中的元素,从第二个开始
for (int i=x+d;i<arr.length;i+=d){
//j为有序序列的最后一个元素的下标
int j=i-d;
//要插入的数
int temp=arr[i];
//从后往前遍历
for (;j>=0&&temp<arr[j];j-=d){
//向后移动d个单位
arr[j+d]=arr[j];
}
//找到位置,插入值
arr[j+d]=temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void main(String[] args) {
XiEr.sheelSort(new int[]{32,21,45,20,19,48,12});
}
}
```
3、快速排序(要求时间最快时)
算法基本思想:
选择第一个数为key,小于key的数放在左边,大于key的数放在右边。
递归的将key左边和右边的数都按照第一步进行,直到不能递归。
代码实现:
```
import java.util.Arrays;
public class KuaiPai {
public static void main(String[] args){
int[] a = new int[]{32,21,45,20,19,48,12};
int start = 0;
int end = a.length-1;
sort(a,start,end);
System.out.println(Arrays.toString(a));
}
public static void sort(int[] a,int low,int high){
int start = low;
int end = high;
//左边第一个为基准值
int key = a[low];
while(end>start){
//从后往前比较
while(end>start&&a[end]>=key) //如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较
end--;
if(a[end]<=key){
int temp = a[end];
a[end] = a[start];
a[start] = temp;
}
//从前往后比较
while(end>start&&a[start]<=key)//如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
start++;
if(a[start]>=key){
int temp = a[start];
a[start] = a[end];
a[end] = temp;
}
//此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用
}
//递归
if(start>low) sort(a,low,start-1);//左边序列。第一个索引位置到关键值索引-1
if(end<high) sort(a,end+1,high);//右边序列。从关键值索引+1到最后一个
}
}
```
4、归并排序
速度仅次于快速排序,内存少的时候使用,可以进行并行计算的时候使用。
归并排序 (merge sort) 是一类与插入排序、交换排序、选择排序不同的另一种排序方法。
归并的含义是将两个或两个以上的有序表合并成一个新的有序表。归并排序有多路归并排序、
两路归并排序 , 可用于内排序,也可以用于外排序。这里仅对内排序的两路归并方法进行讨论。
算法基本思想:
分而治之(pide - conquer);每个递归过程涉及三个步骤
第一, 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素.
第二, 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作
第三, 合并: 合并两个排好序的子序列,生成排序结果.
代码实现:
```
import java.util.Arrays;
public class GuiBing {
public static int[] sort(int[] a,int low,int high){
int mid = (low+high)/2;
if(low<high){
sort(a,low,mid);
sort(a,mid+1,high);
//左右归并
merge(a,low,mid,high);
}
return a;
}
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high-low+1];
int i= low;
int j = mid+1;
int k=0;
// 把较小的数先移到新数组中
while(i<=mid && j<=high){
if(a[i]<a[j]){
temp[k++] = a[i++];
}else{
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while(i<=mid){
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while(j<=high){
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for(int x=0;x<temp.length;x++){
a[x+low] = temp[x];
}
}
public static void main(String[] args) {
int[] a = new int[]{32,21,45,20,19,48,12};
int left = 0;
int right= a.length-1;
GuiBing.sort(a,left,right);
System.out.println(Arrays.toString(a));
}
}
```
5、冒泡排序
算法基本思想:
将序列中所有元素两两比较,将最大的放在最后面。
将剩余序列中所有元素两两比较,将最大的放在最后面。
重复第二步,直到只剩下一个数。
代码实现:
```
import java.util.Arrays;
//时间复杂度一般情况下为O(n2)
//原理:两两比较
public class MaoPao {
public static void main(String[] args) {
int[] arr=new int[]{32,21,45,20,19,48,12};
//比较的轮次数为数组长度-1次,每循环一轮,内层的比较次数就会-1
for (int j=0;j<arr.length-1;j++) {
//第一轮循环下来,最大元素出现在最大索引处,-j为了防止下标越界
for (int i = 0; i < arr.length - 1-j; i++) {
int t;
if (arr[i] > arr[i + 1]) {
t = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = t;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
```
6、简单选择排序
常用于取序列中最大最小的几个数时。
(如果每次比较都交换,那么就是交换排序;如果每次比较完一个循环再交换,就是简单选择排序.)
算法基本思想:
遍历整个序列,将最小的数放在最前面。
遍历剩下的序列,将最小的数放在最前面。
重复第二步,直到只剩下一个数。
代码实现:
```
import java.util.Arrays;
//时间复杂度:O(n2)
public class XuanZe {
//原理:一个元素与其他所有元素比较
public static void main(String[] args) {
int []arr=new int[]{32,21,45,20,19,48,12};
for (int i=0;i<arr.length-1;i++){
for (int j=i+1;j<arr.length;j++){
if (arr[i]>arr[j]){
int t;
t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
```