目录
ExchangeSort类(可以略过,其中代码和上文各算法代码一致,不过都被整合到了ExchangeSort类中):
MergeSort类(可以略过,其中代码和上文各算法代码一致,不过都被整合到了ExchangeSort类中):
(一)交换排序
1.冒泡排序
(1)基本形式
1)核心思想:
冒泡排序,顾名思义,即每次循环通过交换的形式每次将非有序部分的最大值(或者最小值)放置在非有序部分的首端或者末端,就像冒泡一般,重复此过程最终整组元素有序。
2)代码实现(以从小到大排序为例):
//实现冒泡排序
public static void bubbleSort(int[] array){
for(int i=0;i<array.length-1;i++){
for(int j=0;j< array.length-1-i;j++){
if(array[j]>array[j+1]){
//若前一个元素比后一个元素大则交换
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
}
//完成一次循环,此时数组中最大值被“冒泡”至数组末端
}
}
}
3)代码分析:
时间复杂度:严格为O(n^2);
空间复杂度:O(1);
稳定性:稳定。
(2)优化形式
1)核心思想:
同基本形式,但是设定了标志位flg判断单次循环中是否发生交换,若未发生交换则说明整组元素已有序,直接中断即可。
2)代码实现(以从小到大排序为例):
//实现冒泡排序的优化形式
public static void bubbleSort2(int[] array){
for(int i=0;i<array.length-1;i++){
//设定标志位判断是否在一次循环中发生了交换
boolean flg=false;
for(int j=0;j< array.length-1-i;j++){
if(array[j]>array[j+1]){
//若前一个元素比后一个元素大则交换
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
flg=true;
}
//完成一次循环,此时数组中最大值被“冒泡”至数组末端
//判断标志位是否被置为true,若未被置为true则说明没有发生交换,整个数组有序,直接中断循环即可
if(!flg){
break;
}
}
}
}
3)代码分析:
时间复杂度:顺序情况(最好情况)下只需遍历一遍数组,时间复杂度降为O(n),最坏情况仍为O(n^2);
空间复杂度:O(1);
稳定性:稳定。
2.快速排序
(1)递归形式:Hoare型
1)核心思想:
快速排序是指,取待排序元素序列中某个元素作为基准值,依照该基准值将该元素序列分割成两个子序列,并且左子序列中的所有元素均小(大)于基准值,右子序列中所有元素均大(小)于基准值,中间为基准值,然后左右子序列重复该过程,向下递归,最终该组元素有序。
总体体现了分治的思想(即分而治之)。
Hoare型指Hoare法(发明人为Hoare)实现的分组方法。
2)代码实现(以从小到大排序为例):
//实现Hoare形式快速排序
public static void hoareQuickSort(int[] array){
//调用快速排序的底层方法
hoareQuickSortChild(array,0,array.length-1);
}
//Hoare形式快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void hoareQuickSortChild(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//通过基准值对该组元素进行分组,并获取基准值下标
int par=hoarePartition(array,start,end);
//递归左侧元素
hoareQuickSortChild(array,start,par-1);
//递归右侧元素
hoareQuickSortChild(array,par+1,end);
}
//递归时用到的Hoare形式分组方法
private static int hoarePartition(int[]array,int start,int end){
//记录基准值位置下标
int i=start;
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//两次循环结束说明分别在始端找到比基准值大的元素以及在末端找到比基准值小的元素,二者交换
int tmp1=array[start];
array[start]=array[end];
array[end]=tmp1;
}
//start和end相遇,将基准值和相遇处元素交换,使得中间为基准值,左侧为都比基准值小的元素,右边都为比基准值大的元素
int tmp2=array[start];
array[start]=array[i];
array[i]=tmp2;
//返回基准值下标
return start;
}
3)代码分析:
时间复杂度:最好情况下,分组时严格从中间二分,此时时间复杂度为O(n*log n),
最坏情况下,分组只向单侧分组,此时时间复杂度为O(n^2);
空间复杂度:最好情况下,分组时严格从中间二分,此时空间复杂度为O(log n),最坏情况下,分组只向单侧分组,此时空间复杂度为O(n);
稳定性:不稳定。
(2)递归形式:挖坑法型
1)核心思想:
同Hoare型,但是应用的分组方法为挖坑法。
2)代码实现(以从小到大排序为例):
//实现挖坑法形式快速排序
public static void holeQuickSort(int[] array){
//调用快速排序的底层方法
holeQuickSortChild(array,0,array.length-1);
}
//快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void holeQuickSortChild(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//通过基准值对该组元素进行分组,并获取基准值下标
int par=holePartition(array,start,end);
//递归左侧元素
holeQuickSortChild(array,start,par-1);
//递归右侧元素
holeQuickSortChild(array,par+1,end);
}
//递归时用到的挖坑法形式分组方法
private static int holePartition(int[]array,int start,int end){
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//直接从末端向始端的“坑”赋值填数
array[start]=array[end];
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//直接从始端向末端的“坑”赋值填数
array[end]=array[start];
}
//start和end相遇,将基准值填入最后一个“坑”
array[start]=tmp;
//返回基准值下标
return start;
}
3)代码分析:
同Hoare型。
(3)递归形式:前后指针法型
1)核心思想:
同Hoare型,但是应用的分组方法为前后指针法。
2)代码实现(以从小到大排序为例):
//实现前后指针形式快速排序
public static void pointerQuickSort(int[] array){
//调用快速排序的底层方法
pointerQuickSortChild(array,0,array.length-1);
}
//快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void pointerQuickSortChild(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//通过基准值对该组元素进行分组,并获取基准值下标
int par=pointerPartition(array,start,end);
//递归左侧元素
pointerQuickSortChild(array,start,par-1);
//递归右侧元素
pointerQuickSortChild(array,par+1,end);
}
//递归时用到的前后指针形式分组方法
private static int pointerPartition(int[]array,int start,int end){
//设定前后指针
int pre=start;
int suc=start+1;
//以下算法实现:当suc遇到比基准值(始端元素)小的元素,前后指针均后移;遇到比基准值大的元素时,后指针先一直后移找到后面第一个比基准值小的元素,前指针再后移一位指向第一个比基准值大的元素,两指针所指元素交换
while(suc<=end){
if(array[suc]<array[start] && array[++pre]!=array[suc]){
int tmp=array[pre];
array[pre]=array[suc];
array[suc]=tmp;
}
suc++;
}
//循环结束后前指针所在位置左侧都比基准值小,右侧都比基准值大,将基准值换到此位置
int tmp=array[pre];
array[pre]=array[start];
array[start]=tmp;
return pre;
}
3)代码分析:
同Hoare型。
(4)通过三数取中法优化(以递归形式:Hoare型为例)
1)核心思想:
同一般形式,但是用到了三数取中的方法来获取基准值。
2)代码实现(以从小到大排序为例):
//实现Hoare型快速排序的优化形式
public static void hoareQuickSort2(int[] array){
//调用快速排序的底层方法
hoareQuickSortChild2(array,0,array.length-1);
}
//Hoare形式快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void hoareQuickSortChild2(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//调用获取中位数下标的方法
int index=midThreeNum(array,start,end);
//将中位数放置在该组元素起始位置作为基准值,实现优化
int tmp=array[index];
array[index]=array[start];
array[start]=tmp;
//通过基准值对该组元素进行分组,并获取基准值下标
int par=hoarePartition2(array,start,end);
//递归左侧元素
hoareQuickSortChild2(array,start,par-1);
//递归右侧元素
hoareQuickSortChild2(array,par+1,end);
}
//获取中位数下标的底层方法:返回一组元素首元素,中间元素,末端元素的中位数
private static int midThreeNum(int[]array,int start,int end){
int mid=(start+end)/2;
if(array[start]<array[end]){
if(array[mid]<array[start]){
return start;
}else if(array[mid]>array[end]){
return end;
}else{
return mid;
}
}else{
if(array[mid]<array[end]){
return end;
}else if(array[mid]>array[start]){
return start;
}
else{
return mid;
}
}
}
//递归时用到的Hoare形式分组方法
private static int hoarePartition2(int[]array,int start,int end){
//记录基准值位置下标
int i=start;
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//两次循环结束说明分别在始端找到比基准值大的元素以及在末端找到比基准值小的元素,二者交换
int tmp1=array[start];
array[start]=array[end];
array[end]=tmp1;
}
//start和end相遇,将基准值和相遇处元素交换,使得中间为基准值,左侧为都比基准值小的元素,右边都为比基准值大的元素
int tmp2=array[start];
array[start]=array[i];
array[i]=tmp2;
//返回基准值下标
return start;
}
3)代码分析:
时间复杂度:优化后,分组时趋向从中间二分,时间复杂度趋向为O(n*log n),
空间复杂度:优化后,分组时趋向从中间二分,空间复杂度趋向为O(log n),
稳定性:不稳定。
(5)非递归形式(以Hoare型为例)
1)核心思想:
同递归形式,但是分组时是通过利用栈模拟递归算法向下分组的。
2)代码实现(以从小到大排序为例):
//实现Hoare型快速排序的非递归形式(借助栈实现)
public static void hoareQuickSort3(int[] array){
//创建一个栈
Stack<Integer>stack=new Stack<>();
int start=0;
int end=array.length-1;
int par=hoarePartition3(array,start,end);
//判断基准值左侧是否有两个以上元素
if(par>start+1){
//将左侧部分的首尾元素入栈
stack.push(start);
stack.push(end-1);
}
//判断基准值右侧是否有两个以上元素
if(par<end-1) {
stack.push(par + 1);
stack.push(end);
}
//栈不为空说明未全部分组完毕
while(!stack.isEmpty()){
//出栈获取下一个待分组的末端元素
end=stack.pop();
//出栈获取下一个待分组的首端元素
start=stack.pop();
par=hoarePartition3(array,start,end);
//判断基准值左侧是否有两个以上元素
if(par>start+1){
//将左侧部分的首尾元素入栈
stack.push(start);
stack.push(end-1);
}
//判断基准值右侧是否有两个以上元素
if(par<end-1) {
stack.push(par + 1);
stack.push(end);
}
}
}
//调用到的Hoare形式分组方法
private static int hoarePartition3(int[]array,int start,int end){
//记录基准值位置下标
int i=start;
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//两次循环结束说明分别在始端找到比基准值大的元素以及在末端找到比基准值小的元素,二者交换
int tmp1=array[start];
array[start]=array[end];
array[end]=tmp1;
}
//start和end相遇,将基准值和相遇处元素交换,使得中间为基准值,左侧为都比基准值小的元素,右边都为比基准值大的元素
int tmp2=array[start];
array[start]=array[i];
array[i]=tmp2;
//返回基准值下标
return start;
}
3.以上两种交换排序示例及各算法耗时参考
1)代码结构
ExchangeSort类:内部实现冒牌排序,快速排序(包括Hoare型、挖坑型、前后指针型递归形式,Hoare型递归形式的优化形式,Hoare型非递归形式)。
Test类:实现创建顺序数组,逆序数组,随机数组的方法(用来测试不同的交换排序算法)以及测量不同交换排序算法耗时的方法,并在main方法中进行示例演示。
2)程序源码
ExchangeSort类(可以略过,其中代码和上文各算法代码一致,不过都被整合到了ExchangeSort类中):
import java.util.Stack;
public class ExchangeSort {
//实现冒泡排序
public static void bubbleSort(int[] array){
for(int i=0;i<array.length-1;i++){
for(int j=0;j< array.length-1-i;j++){
if(array[j]>array[j+1]){
//若前一个元素比后一个元素大则交换
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
}
//完成一次循环,此时数组中最大值被“冒泡”至数组末端
}
}
}
//实现冒泡排序的优化形式
public static void bubbleSort2(int[] array){
for(int i=0;i<array.length-1;i++){
//设定标志位判断是否在一次循环中发生了交换
boolean flg=false;
for(int j=0;j< array.length-1-i;j++){
if(array[j]>array[j+1]){
//若前一个元素比后一个元素大则交换
int tmp=array[j];
array[j]=array[j+1];
array[j+1]=tmp;
flg=true;
}
//完成一次循环,此时数组中最大值被“冒泡”至数组末端
//判断标志位是否被置为true,若未被置为true则说明没有发生交换,整个数组有序,直接中断循环即可
if(!flg){
break;
}
}
}
}
//实现Hoare形式快速排序
public static void hoareQuickSort(int[] array){
//调用快速排序的底层方法
hoareQuickSortChild(array,0,array.length-1);
}
//Hoare形式快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void hoareQuickSortChild(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//通过基准值对该组元素进行分组,并获取基准值下标
int par=hoarePartition(array,start,end);
//递归左侧元素
hoareQuickSortChild(array,start,par-1);
//递归右侧元素
hoareQuickSortChild(array,par+1,end);
}
//递归时用到的Hoare形式分组方法
private static int hoarePartition(int[]array,int start,int end){
//记录基准值位置下标
int i=start;
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//两次循环结束说明分别在始端找到比基准值大的元素以及在末端找到比基准值小的元素,二者交换
int tmp1=array[start];
array[start]=array[end];
array[end]=tmp1;
}
//start和end相遇,将基准值和相遇处元素交换,使得中间为基准值,左侧为都比基准值小的元素,右边都为比基准值大的元素
int tmp2=array[start];
array[start]=array[i];
array[i]=tmp2;
//返回基准值下标
return start;
}
//实现挖坑法形式快速排序
public static void holeQuickSort(int[] array){
//调用快速排序的底层方法
holeQuickSortChild(array,0,array.length-1);
}
//快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void holeQuickSortChild(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//通过基准值对该组元素进行分组,并获取基准值下标
int par=holePartition(array,start,end);
//递归左侧元素
holeQuickSortChild(array,start,par-1);
//递归右侧元素
holeQuickSortChild(array,par+1,end);
}
//递归时用到的挖坑法形式分组方法
private static int holePartition(int[]array,int start,int end){
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//直接从末端向始端的“坑”赋值填数
array[start]=array[end];
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//直接从始端向末端的“坑”赋值填数
array[end]=array[start];
}
//start和end相遇,将基准值填入最后一个“坑”
array[start]=tmp;
//返回基准值下标
return start;
}
//实现前后指针形式快速排序
public static void pointerQuickSort(int[] array){
//调用快速排序的底层方法
pointerQuickSortChild(array,0,array.length-1);
}
//快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void pointerQuickSortChild(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//通过基准值对该组元素进行分组,并获取基准值下标
int par=pointerPartition(array,start,end);
//递归左侧元素
pointerQuickSortChild(array,start,par-1);
//递归右侧元素
pointerQuickSortChild(array,par+1,end);
}
//递归时用到的前后指针形式分组方法
private static int pointerPartition(int[]array,int start,int end){
//设定前后指针
int pre=start;
int suc=start+1;
//以下算法实现:当suc遇到比基准值(始端元素)小的元素,前后指针均后移;遇到比基准值大的元素时,后指针先一直后移找到后面第一个比基准值小的元素,前指针再后移一位指向第一个比基准值大的元素,两指针所指元素交换
while(suc<=end){
if(array[suc]<array[start] && array[++pre]!=array[suc]){
int tmp=array[pre];
array[pre]=array[suc];
array[suc]=tmp;
}
suc++;
}
//循环结束后前指针所在位置左侧都比基准值小,右侧都比基准值大,将基准值换到此位置
int tmp=array[pre];
array[pre]=array[start];
array[start]=tmp;
return pre;
}
//实现Hoare型快速排序的优化形式
public static void hoareQuickSort2(int[] array){
//调用快速排序的底层方法
hoareQuickSortChild2(array,0,array.length-1);
}
//Hoare形式快速排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在快速排序方法中调用此递归方法即可
private static void hoareQuickSortChild2(int[]array,int start,int end){
//递归的结束情况
if(start>=end){
return;
}
//调用获取中位数下标的方法
int index=midThreeNum(array,start,end);
//将中位数放置在该组元素起始位置作为基准值,实现优化
int tmp=array[index];
array[index]=array[start];
array[start]=tmp;
//通过基准值对该组元素进行分组,并获取基准值下标
int par=hoarePartition2(array,start,end);
//递归左侧元素
hoareQuickSortChild2(array,start,par-1);
//递归右侧元素
hoareQuickSortChild2(array,par+1,end);
}
//获取中位数下标的底层方法
private static int midThreeNum(int[]array,int start,int end){
int mid=(start+end)/2;
if(array[start]<array[end]){
if(array[mid]<array[start]){
return start;
}else if(array[mid]>array[end]){
return end;
}else{
return mid;
}
}else{
if(array[mid]<array[end]){
return end;
}else if(array[mid]>array[start]){
return start;
}
else{
return mid;
}
}
}
//递归时用到的Hoare形式分组方法
private static int hoarePartition2(int[]array,int start,int end){
//记录基准值位置下标
int i=start;
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//两次循环结束说明分别在始端找到比基准值大的元素以及在末端找到比基准值小的元素,二者交换
int tmp1=array[start];
array[start]=array[end];
array[end]=tmp1;
}
//start和end相遇,将基准值和相遇处元素交换,使得中间为基准值,左侧为都比基准值小的元素,右边都为比基准值大的元素
int tmp2=array[start];
array[start]=array[i];
array[i]=tmp2;
//返回基准值下标
return start;
}
//实现Hoare型快速排序的非递归形式(借助栈实现)
public static void hoareQuickSort3(int[] array){
//创建一个栈
Stack<Integer>stack=new Stack<>();
int start=0;
int end=array.length-1;
int par=hoarePartition3(array,start,end);
//判断基准值左侧是否有两个以上元素
if(par>start+1){
//将左侧部分的首尾元素入栈
stack.push(start);
stack.push(end-1);
}
//判断基准值右侧是否有两个以上元素
if(par<end-1) {
stack.push(par + 1);
stack.push(end);
}
//栈不为空说明未全部分组完毕
while(!stack.isEmpty()){
//出栈获取下一个待分组的末端元素
end=stack.pop();
//出栈获取下一个待分组的首端元素
start=stack.pop();
par=hoarePartition3(array,start,end);
//判断基准值左侧是否有两个以上元素
if(par>start+1){
//将左侧部分的首尾元素入栈
stack.push(start);
stack.push(end-1);
}
//判断基准值右侧是否有两个以上元素
if(par<end-1) {
stack.push(par + 1);
stack.push(end);
}
}
}
//调用到的Hoare形式分组方法
private static int hoarePartition3(int[]array,int start,int end){
//记录基准值位置下标
int i=start;
//将该组元素的起始元素作为基准值
int tmp=array[start];
while(start<end){
//从末端开始遍历数组,直到找到比基准值小的元素的下标
//start<end目的是防止下标越界
//取大于等于或者小于等于的目的是防止进入死循环
//先走末端因为若先走始端会出现相遇的地方为比基准值大的情况
while(start<end && array[end]>=tmp){
end--;
}
//从始端开始遍历数组,直到找到比基准值大的元素的下标
while(start<end && array[start]<=tmp){
start++;
}
//两次循环结束说明分别在始端找到比基准值大的元素以及在末端找到比基准值小的元素,二者交换
int tmp1=array[start];
array[start]=array[end];
array[end]=tmp1;
}
//start和end相遇,将基准值和相遇处元素交换,使得中间为基准值,左侧为都比基准值小的元素,右边都为比基准值大的元素
int tmp2=array[start];
array[start]=array[i];
array[i]=tmp2;
//返回基准值下标
return start;
}
}
Test类:
import java.util.Arrays;
import java.util.Random;
public class Test {
//生成一个顺序数组的方法
public static void order(int[] array){
for(int i=0;i< array.length;i++){
array[i]=i;
}
}
//生成一个逆序数组的方法
public static void reverseOrder(int[] array){
for(int i=0;i<array.length;i++){
array[i]= array.length-i;
}
}
//生成一个随机数数组的方法
public static void randomOrder(int[] array){
Random random=new Random();
for(int i=0;i<array.length;i++){
array[i]= random.nextInt(10_0000);
}
}
//测试冒泡排序时间的方法
public static void testBubbleSort(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.bubbleSort(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("冒泡排序耗时:"+(endtime-starttime));
}
//测试冒泡排序优化形式时间的方法
public static void testBubbleSort2(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.bubbleSort2(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("冒泡排序优化形式耗时:"+(endtime-starttime));
}
//测试Hoare形式快速排序时间的方法
public static void testHoareQucikSort(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.hoareQuickSort(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("Hoare形式快速排序耗时:"+(endtime-starttime));
}
//测试挖坑法形式快速排序时间的方法
public static void testHoleQucikSort(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.holeQuickSort(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("Hole形式快速排序耗时:"+(endtime-starttime));
}
//测试前后指针法形式快速排序时间的方法
public static void testPointerQucikSort(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.pointerQuickSort(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("Pointer形式快速排序耗时:"+(endtime-starttime));
}
//测试Hoare型快速排序的优化形式时间的方法
public static void testHoareQucikSort2(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.hoareQuickSort2(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("Hoare型快速排序的优化形式耗时:"+(endtime-starttime));
}
//测试Hoare型快速排序的非递归形式时间的方法
public static void testHoareQucikSort3(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
ExchangeSort.hoareQuickSort3(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("Hoare型快速排序的非递归形式耗时:"+(endtime-starttime));
}
public static void main(String[] args) {
int[]array=new int[1_0000];
//测试顺序数组情况
System.out.println("***********************");
System.out.println("顺序数组情况:");
order(array);
testBubbleSort(array);
testBubbleSort2(array);
testHoareQucikSort(array);
testHoleQucikSort(array);
testPointerQucikSort(array);
testHoareQucikSort2(array);
testHoareQucikSort3(array);
System.out.println("***********************");
//测试逆序数组情况
System.out.println("逆序数组情况:");
reverseOrder(array);
testBubbleSort(array);
testBubbleSort2(array);
testHoareQucikSort(array);
testHoleQucikSort(array);
testPointerQucikSort(array);
testHoareQucikSort2(array);
testHoareQucikSort3(array);
System.out.println("***********************");
//测试随机数组情况
System.out.println("随机数组情况:");
randomOrder(array);
testBubbleSort(array);
testBubbleSort2(array);
testHoareQucikSort(array);
testHoleQucikSort(array);
testPointerQucikSort(array);
testHoareQucikSort2(array);
testHoareQucikSort3(array);
System.out.println("***********************");
}
}
3)测试结果
上图可以直观感受各算法在不同情况下的表现。
(二)归并排序
1.归并排序
(1)递归形式
1)核心思想:
归并排序是指,将一个元素序列多次递归分解为多组有序序列,再将所有已有序的子序列合并(一般为两两合并,即二路归并),直到合并为一个元素序列,此时该元素序列已经有序。
总体体现了分治的思想(即分而治之)。
归并排序在外排序中经常用到。
2)代码实现(以从小到大排序为例):
//实现归并排序
public static void mergeSort1(int[]array){
//调用归并排序的递归方法
mergeSortChild1(array,0, array.length-1);
}
//归并排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在归并排序方法中调用此递归方法即可
private static void mergeSortChild1(int[]array,int start,int end){
//判断结束情况
if(start>=end){
return;
}
//向下递归分解为有序数组
int mid=(start+end)/2;
//向左递归分解
mergeSortChild1(array,start,mid);
//向右递归分解
mergeSortChild1(array,mid+1,end);
//合并分解完的数组
merge1(array,start,mid,end);
}
//调用到的合并数组方法
private static void merge1(int[]array,int start,int mid,int end){
//创建一个新的数组作为模板
int[]tmp=new int[end-start+1];
//设定新数组的下标
int k=0;
//设定左右两个待合并的组的起始,终止下标
int s1=start;
int e1=mid;
int s2=mid+1;
int e2=end;
//由于两个待合并的数组皆有序(从小到大顺序排放),所以直接比较两数组的起始元素,取出其中较小这放入模板数组中,对应下标后移
while(s1<=e1&&s2<=e2){
if(array[s1]<=array[s2]){
tmp[k++]=array[s1++];
}else{
tmp[k++]=array[s2++];
}
}
//循环结束说明某个数组已经遍历完毕,则另一个数组剩下元素都比模板数组中存储的元素大,直接将剩余元素存入其中即可
while(s1<=e1){
tmp[k++]=array[s1++];
}
while(s2<=e2){
tmp[k++]=array[s2++];
}
//此时tmp数组中已经存储了二者合并完毕的内容,将其中内容拷贝到原数组中即可,注意对应下标
for(int i=0;i<k;i++){
array[i+start]=tmp[i];
}
}
3)代码分析:
时间复杂度:O(n*log n),
空间复杂度:O(n)(建立tmp数组最大开辟大小为n的空间),
稳定性:稳定。
(2)非递归形式
1)核心思想:
同递归形式,但是分解时是通过特定循环算法模拟递归算法向下分组的。
2)代码实现(以从小到大排序为例):
//实现归并排序的非递归形式
public static void mergeSort2(int[]array){
//认为数组已经按照单个元素为一组的方式分解完毕,开始合并
//设定gap增量来获取合并数组的首、中、末,并且不断使gap扩倍,以此模拟递归算法中实现的合并方式
int gap=1;
while(gap<array.length){
for(int i=0;i< array.length;i+=2*gap){
int start=i;
int mid=start+gap-1;
//判断是否越界,越界直接置于末端
if(mid>array.length-1){
mid= array.length-1;
}
//判断是否越界,越界直接置于末端
int end=mid+gap;
if(end>array.length-1){
end= array.length-1;
}
//合并数组
merge2(array,start,mid,end);
}
//gap扩倍
gap*=2;
}
}
//调用到的合并数组方法
private static void merge2(int[]array,int start,int mid,int end){
//创建一个新的数组作为模板
int[]tmp=new int[end-start+1];
//设定新数组的下标
int k=0;
//设定左右两个待合并的组的起始,终止下标
int s1=start;
int e1=mid;
int s2=mid+1;
int e2=end;
//由于两个待合并的数组皆有序(从小到大顺序排放),所以直接比较两数组的起始元素,取出其中较小这放入模板数组中,对应下标后移
while(s1<=e1&&s2<=e2){
if(array[s1]<=array[s2]){
tmp[k++]=array[s1++];
}else{
tmp[k++]=array[s2++];
}
}
//循环结束说明某个数组已经遍历完毕,则另一个数组剩下元素都比模板数组中存储的元素大,直接将剩余元素存入其中即可
while(s1<=e1){
tmp[k++]=array[s1++];
}
while(s2<=e2){
tmp[k++]=array[s2++];
}
//此时tmp数组中已经存储了二者合并完毕的内容,将其中内容拷贝到原数组中即可,注意对应下标
for(int i=0;i<k;i++){
array[i+start]=tmp[i];
}
}
2.以上算法示例及各算法耗时参考
1)代码结构
MergeSort类:内部实现归并排序的递归形式与非递归形式。
Test类:实现创建顺序数组,逆序数组,随机数组的方法(用来测试不同的交换排序算法)以及测量不同归并排序算法耗时的方法,并在main方法中进行示例演示。
2)程序源码
MergeSort类(可以略过,其中代码和上文各算法代码一致,不过都被整合到了ExchangeSort类中):
public class MergeSort {
//实现归并排序
public static void mergeSort1(int[]array){
//调用归并排序的递归方法
mergeSortChild1(array,0, array.length-1);
}
//归并排序的底层递归方法(使用递归时要求递归有以下三个参数,为保证接口统一性,在归并排序方法中调用此递归方法即可
private static void mergeSortChild1(int[]array,int start,int end){
//判断结束情况
if(start>=end){
return;
}
//向下递归分解为有序数组
int mid=(start+end)/2;
//向左递归分解
mergeSortChild1(array,start,mid);
//向右递归分解
mergeSortChild1(array,mid+1,end);
//合并分解完的数组
merge1(array,start,mid,end);
}
//调用到的合并数组方法
private static void merge1(int[]array,int start,int mid,int end){
//创建一个新的数组作为模板
int[]tmp=new int[end-start+1];
//设定新数组的下标
int k=0;
//设定左右两个待合并的组的起始,终止下标
int s1=start;
int e1=mid;
int s2=mid+1;
int e2=end;
//由于两个待合并的数组皆有序(从小到大顺序排放),所以直接比较两数组的起始元素,取出其中较小这放入模板数组中,对应下标后移
while(s1<=e1&&s2<=e2){
if(array[s1]<=array[s2]){
tmp[k++]=array[s1++];
}else{
tmp[k++]=array[s2++];
}
}
//循环结束说明某个数组已经遍历完毕,则另一个数组剩下元素都比模板数组中存储的元素大,直接将剩余元素存入其中即可
while(s1<=e1){
tmp[k++]=array[s1++];
}
while(s2<=e2){
tmp[k++]=array[s2++];
}
//此时tmp数组中已经存储了二者合并完毕的内容,将其中内容拷贝到原数组中即可,注意对应下标
for(int i=0;i<k;i++){
array[i+start]=tmp[i];
}
}
//实现归并排序的非递归形式
public static void mergeSort2(int[]array){
//认为数组已经按照单个元素为一组的方式分解完毕,开始合并
//设定gap增量来获取合并数组的首、中、末,并且不断使gap扩倍,以此模拟递归算法中实现的合并方式
int gap=1;
while(gap<array.length){
for(int i=0;i< array.length;i+=2*gap){
int start=i;
int mid=start+gap-1;
//判断是否越界,越界直接置于末端
if(mid>array.length-1){
mid= array.length-1;
}
//判断是否越界,越界直接置于末端
int end=mid+gap;
if(end>array.length-1){
end= array.length-1;
}
//合并数组
merge2(array,start,mid,end);
}
//gap扩倍
gap*=2;
}
}
//调用到的合并数组方法
private static void merge2(int[]array,int start,int mid,int end){
//创建一个新的数组作为模板
int[]tmp=new int[end-start+1];
//设定新数组的下标
int k=0;
//设定左右两个待合并的组的起始,终止下标
int s1=start;
int e1=mid;
int s2=mid+1;
int e2=end;
//由于两个待合并的数组皆有序(从小到大顺序排放),所以直接比较两数组的起始元素,取出其中较小这放入模板数组中,对应下标后移
while(s1<=e1&&s2<=e2){
if(array[s1]<=array[s2]){
tmp[k++]=array[s1++];
}else{
tmp[k++]=array[s2++];
}
}
//循环结束说明某个数组已经遍历完毕,则另一个数组剩下元素都比模板数组中存储的元素大,直接将剩余元素存入其中即可
while(s1<=e1){
tmp[k++]=array[s1++];
}
while(s2<=e2){
tmp[k++]=array[s2++];
}
//此时tmp数组中已经存储了二者合并完毕的内容,将其中内容拷贝到原数组中即可,注意对应下标
for(int i=0;i<k;i++){
array[i+start]=tmp[i];
}
}
}
Test类:
import java.util.Arrays;
import java.util.Random;
public class Test {
//生成一个顺序数组的方法
public static void order(int[] array){
for(int i=0;i< array.length;i++){
array[i]=i;
}
}
//生成一个逆序数组的方法
public static void reverseOrder(int[] array){
for(int i=0;i<array.length;i++){
array[i]= array.length-i;
}
}
//生成一个随机数数组的方法
public static void randomOrder(int[] array){
Random random=new Random();
for(int i=0;i<array.length;i++){
array[i]= random.nextInt(10_0000);
}
}
//测试归并排序时间的方法
public static void testMergeSort(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
MergeSort.mergeSort1(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("归并排序耗时:"+(endtime-starttime));
}
//测试归并排序非递归形式时间的方法
public static void testMergeSort2(int[]array){
//拷贝一个新的数组
array= Arrays.copyOf(array,array.length);
//获取起始时间戳
long starttime=System.currentTimeMillis();
MergeSort.mergeSort2(array);
//获取终止时间戳
long endtime=System.currentTimeMillis();
//输出耗时
System.out.println("归并排序非递归形式耗时:"+(endtime-starttime));
}
public static void main(String[] args) {
int[]array=new int[10_0000];
//测试顺序数组情况
System.out.println("***********************");
System.out.println("顺序数组情况:");
order(array);
testMergeSort(array);
testMergeSort2(array);
System.out.println("***********************");
//测试逆序数组情况
System.out.println("逆序数组情况:");
reverseOrder(array);
testMergeSort(array);
testMergeSort2(array);
System.out.println("***********************");
//测试随机数组情况
System.out.println("随机数组情况:");
randomOrder(array);
testMergeSort(array);
testMergeSort2(array);
System.out.println("***********************");
}
}
3)测试结果
上图可以直观感受各算法在不同情况下的表现。
以上便是通过Java实现交换排序(包括冒泡排序;快速排序的Hoare法、挖坑法、前后指针法及递归、非递归形式)和归并排序的递归、非递归形式的全部内容,同时关于选择排序和插入排序算法的思路以及具体实现,读者可以垂阅某之博客:田硕1895:通过Java实现插入排序(直接插入,希尔)与选择排序(直接选择,堆排)-CSDN博客。
感谢各位读者支持,如有不当,敬请斧正!