简单介绍常用的排序方法、相应代码实现。
直接插入排序
操作方法:给定一数组,进行排序,将数组分为有序区和无序区两部分,初始时,有序区只有data[0]一个数据,无序区为data[1]~data[n-1],之后每次将无序区的第一个数据插入到有序区合适的位置,形成新的有序区,每次操作都使有序区的个数加1,无序区的个数减1,直到无序区不含有数据。
public int[] insertSort(int[] data){
for(int i=1;i<data.length;i++){
//将数据data[i]插入到有序区合适位置,从后往前依次与比它大的数据交换
for(int j=i-1,p = i;j>=0;j--,p--){
if(data[j]>data[p]){
//交换
int temp = data[p];
data[p] = data[j];
data[j] = temp;
}
}
}
return data;
}
//方法调用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("排序后数据:"+Arrays.toString(insertSort(data)));//排序后数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
复杂度&稳定性:
时间复杂度为n2(n的平方),空间复杂度为1,稳定
希尔排序
希尔排序:是插入排序的一种,是对插入排序的改进,先做“宏观调整”,再做“微观调整”。
希尔排序:取小于n的整数作为gap(步长),将数据中所有间距为gap的数据分组,对每组数据进行直接插入排序,之后减小步长,再进行相同的操作,知道gap=1,最终得到一组排列有序的数据。
希尔排序的步长选择:最后一个步长必须为1,每次步长的改变建议以步长取半的形式来实现。
希尔排序优于直接排序方法的原因:整体来讲希尔排序的比较移动次数相对直接插入排序的比较移动次数较少。希尔排序,在开始时,步长较大,分组较多,每组直接插入排序快,当后来步长较小时,每组数据之前大致进行了排序,故每组的数据接近有序,进行直接插入排序时也较快,整体来讲,希尔排序优于直接插入排序。
public int[] shellSort(int[] data){
for(int gap = data.length/2;gap>0;gap /= 2){//步长按照取半来改变步长
for(int i=0;i<gap;i++){//根据步长来分组
for(int ii = i;ii<data.length;ii += gap){//每组数据进行直接插入排序
for(int j=ii-gap,p=ii;j>=0;j-=gap,p-=gap){
if(data[j]>data[p]){
//交换
int temp = data[p];
data[p] = data[j];
data[j] = temp;
}
}
}
}
System.out.println(Arrays.toString(data));
}
return data;
}
//方法调用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("shell排序后的数据:"+Arrays.toString(shellSort(data)));//shell排序后的数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
复杂度&稳定性:
希尔排序的时间复杂度根据步长的选择不同而有所不同,最差时间复杂度就是步长取1时为n2;
空间复杂度为1
不稳定
直接选择排序
直接选择排序为:从未排序序列中选取最小的数据与该序列的第一个数据交换,之后选取序列中第二个数据之后(包含第二个数据)数据中最小的数据与第二个数据交换,之后以此类推。
public int[] selectSort(int[] data){
for(int i=0;i<data.length-1;i++){//直接插入的趟数
int min = i;//最小数的序号
for(int j=i+1;j<data.length-1;j++){
if(data[j]<data[min]) min=j;
}
//交换
int temp = data[min];
data[min] = data[i];
data[i] = temp;
}
return data;
}
//方法调用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("直接选择排序后的数据:"+Arrays.toString(selectSort(data)));//直接选择排序后的数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
复杂度&稳定性:
时间复杂度为n2,空间复杂度为1,稳定
堆排序
堆排序是对直接选择排序方法的一种改进。采用堆这种数据结构来存储数据。
在直接选择排序中,需要选出最小值(或最大值)与第一个数据(最后一个数据交换),依次类推,n个数据采用直接选择排序则需要n-1次交换,而每次交换之前需要选出最小(最大)值又需要较多次的比较,时间开销很大。堆排列是对直接选择排列方法的一种优化,堆排列是应用了二叉树的数据结构来方便帮助我们很快找到一组数据中的最大或最小值。
在了解堆排列之前先了解几个二叉树的概念:
完全二叉树:二叉树的1到k-1层必须达到最大节点数,最后一层节点集中在最左边;
最大堆二叉树:对于每个含有子节点的节点元素(数据)比两个子节点的数据都大;
最小堆二叉树:对于每个含有子节点的节点元素(数据)比两个子节点的数据都小;
完全二叉树转换为最大堆二叉树:从二叉树底层向上比较每个结点和父结点数据大小,若子节点比父结点数据大,则交换;
完全二叉树转换为最小堆二叉树:从二叉树底层向上比较每个结点和父结点数据大小,若子节点比父结点数据小,则交换;
下面我们来看如何利用二叉树结构来进行堆排列:
先将数据依次保存在完全二叉树中,再将完全二叉树转化为最大堆二叉树(最小堆二叉树),则二叉树的根结点对应的数据为所有数据中的最大值(最小值),然后与直接选择排序思路一样,将得到的最大值(最小值)与最后一个数据(第一个数据)交换,依次进行。
//堆排列
static public int[] heapSort(int[] data){
int temp;
for(int i=data.length-1;i>1;i--){
data = createHeap(data, 0, i);
//交换
temp = data[0];
data[0] = data[i];
data[i] = temp;
}
return data;
}
static public int[] createHeap(int[] data,int low,int high){
int temp;
for(int i=high;i>=low;i--){
if(data[(i-1)/2]<data[i]){
//交换
temp = data[i];
data[i] = data[(i-1)/2];
data[(i-1)/2] = temp;
}
}
return data;
}
//方法调用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("堆排序后的数据:"+Arrays.toString(heapSort(data)));//堆排序后的数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
复杂度和稳定性:
时间复杂度为nlogn(2为底数),空间复杂度为1
不稳定
冒泡排序
冒泡排序:对于一组数据,依次比较相邻的两个数据,若前者比后者大则交换,最后将一组数据中的最大的数据安排到最后一个数据,将最大的数据下沉,这称之为一次冒泡,之后再对最大数据前面的数据进行冒泡处理,将最大数据依次下沉,最终得到一组有序的数据。
public int[] bubbleSort(int[] data){
for(int i=data.length-1;i>0;i--){
for(int j=0;j<data.length-2;j++){
if(data[j+1]<data[j]){
//交换
int temp = data[j+1];
data[j+1] = data[j];
data[j] = temp;
}
}
System.out.println(Arrays.toString(data));
}
return data;
}
//方法引用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("冒泡排序后的数据:"+Arrays.toString(bubbleSort(data)));//冒泡排序后的数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
复杂度&稳定性:
时间复杂度为n2,空间复杂度为1,稳定
快速排序
快速排序:首先选定一个基准数据(一般选待排序区的第一个数据),以该数据为基准,将所有数据分为两部分,左侧所有数据都比基准数据小,右侧所有数据都比基准数据大(做法就是:一组数据,选择第一个数据为基准数据,设置双指针low,hight,low从基准数据后一个数据开始向右移动,hight指针从数据的最右向左移动,当low指针指向的数据比基准数据小时停下来,hight指向的数据比基准数据大时停下来,若low<hight时交换两数据,之后再继续移动两指针,直到low>=hight时停止,完成一次快排),这样一次操作称为一趟快排。之后对基准数据左右两部分的数据再分别进行快排,依次进行,直到得到一组有序数据。
static public int[] quickSort(int[] data,int low,int high){
if(low>=high)return data;
int pivot = data[low];
int i = low+1;
int j = high;
int temp;
while(i<j){
//分别移动两个指针
while(i<j&&pivot>=data[i]){
i++;
}
while(i<=j&&pivot<=data[j]){
j--;
}
if(i<j){
//交换
temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
//交换pivot和data[j]
temp = data[j];
data[j] = data[low];
data[low] = temp;
//递归,对基数两边的数据分别进行快排
quickSort(data, low, j-1);
quickSort(data, j+1, high);
return data;
}
//方法调用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("快速排序后的数据:"+Arrays.toString(quickSort(data)));//快速排序后的数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
复杂度&稳定性:
时间复杂度:nlogn(2位底数),空间复杂度:nlogn(2为底数)
不稳定
归并排序
归并排序:将多个有序子序列归并为一个有序序列。一组n个数据,可以看做n个(有序)序列,将相邻的两个序列两两合并后排序成一个有序序列,会将得到(n+1)/2个序列,每个序列含有2个或一个数据(所有数据为奇数个数据,则最后一个序列含有1个数据),这个过程称为一次归并,如此重复合并相邻序列,最后得到一个含有n个数据的有序序列。
在合并两个有序子序列为一个有序序列时,可以采用双指针,并新建数据来存储排序后的数据序列,两个指针分别指向两个子序列的第一次数据,比较两个指针指向的数据,将较小者存入新数组中,并移动相应的指针,如此依次进行,当一个数据序列指针指向数组尾部时,(最后一个数据已存入数组),该数组的全部数据已存入到新数组中,将另外一个数据中剩余未排列数据排在新数组数据后面即可,最终将得到一组有序的数据序列。
static public int[] mergeSort(int[] data){
int k = 1;//每组序列中含有的数据的个数
while(k<data.length){
data = mergeSort(data,k);
k *= 2;
}
return data;
}
static public int[] mergeSort(int[] data,int k){
int[] datas = new int[data.length];
//定义双指针
int i = 0;
int j = i+k;//第一组有序序列的起始位置
while(i<data.length){
System.out.println(i);
if(j+k<data.length){
int[] data1 = Arrays.copyOfRange(data, i, i+k);
int[] data2 = Arrays.copyOfRange(data, j, j+k);
int[] d = sort(data1,data2);
System.arraycopy(d, 0, datas, i, d.length);
}
if(i+k>=data.length){
//直接将数组data中i后面的数据复制到新数组对应的位置
System.arraycopy(data, i, datas, i, data.length-i);
}
if(i+k<data.length&&j+k>=data.length){
int[] data1 = Arrays.copyOfRange(data, i, i+k);
int[] data2 = Arrays.copyOfRange(data, j, data.length);
int[] d = sort(data1,data2);
System.arraycopy(d, 0, datas, i, d.length);
}
i += 2*k;
j += 2*k;
}
return datas;
}
static public int[] sort(int[] data1,int[] data2){//合并两个有序数组为一个有序数组
int p1 = 0;
int p2 = 0;//双指针
int[] data = new int[data1.length+data2.length];
while(p1<data1.length||p2<data2.length){
if(p1<data1.length&&p2<data2.length){
if(data1[p1]<data2[p2]){
data[p1+p2] = data1[p1];
p1++;
}else{
data[p1+p2] = data2[p2];
p2++;
}
}
if(p1<data1.length&&p2>=data2.length){
data[p1+p2] = data1[p1];
p1++;
}
if(p1>=data1.length&&p2<data2.length){
data[p1+p2] = data2[p2];
p2++;
}
}
return data;
}
//方法调用
int[] data = {7,3,4,2,4,6,2,1,3,12};
System.out.println("归并排序后的数据:"+Arrays.toString(mergeSort(data)));//归并排序后的数据:[1, 2, 2, 3, 3, 4, 4, 6, 7, 12]
稳定性:
稳定