Java排序
目录
一、简单排序
前言
排序是非常常见的一种需求,提供一些数据元素,把这些数据元素按照一定的规则进行排序。以下会依次介绍排序算法。内容来自黑马程序员。
一、简单排序
1.冒泡排序
![](https://i-blog.csdnimg.cn/blog_migrate/192569ec79580fbe77110de67fd00c2e.png)
代码实现 :
package 排序;
import javax.sound.midi.Soundbank;
import java.awt.image.ImageFilter;
import java.util.Arrays;
/**
* @Date: 2022/1/6 - 01 - 06 - 14:04
* @Description:
*/
public class Bubble {
public static void sort(Comparable[] a){
for (int i = a.length-1; i >0 ; i--) {
for (int j = 0; j <i ; j++) {
if (greater(a[j],a[j+1])){
exch(a,j,j+1);
}
}
}
}
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
private static void exch(Comparable[] a,int i,int j){
Comparable t=a[i];
a[i]=a[j];
a[j]=t;
}
public static void main(String[] args) {
Integer[] a={4,5,2,3,7,8,0};
Bubble.sort(a);
System.out.println(Arrays.toString(a));
}
}
2.选择排序
![](https://i-blog.csdnimg.cn/blog_migrate/6b394818f0458c49c590cb091a192671.png)
代码实现:
package 排序;
import java.util.Arrays;
/**
* @Date: 2022/1/6 - 01 - 06 - 14:51
* @Description:
*/
public class Selection {
public static void sort(Comparable[] a){
for (int i = 0; i <a.length-1; i++) {//因为剩余一个元素就不需要选择了,所以i <a.length-1
//依次循环最小索引的值
int min=i;
for (int j = i+1; j <a.length ; j++) {
if (greater(a[min],a[j])){//比较其与最小索引所在的值大小,如果最小索引处大,就交换下标值
min=j;
}
}
exch(a,min,i);//交换下标之后将元素位置互换
}
}
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
private static void exch(Comparable[] a,int i,int j){
Comparable t=a[i];
a[i]=a[j];
a[j]=t;
}
public static void main(String[] args) {
Integer[] a={4,5,2,3,7,8,0};
Bubble.sort(a);
System.out.println(Arrays.toString(a));
}
}
选择排序的时间复杂度分析: 选择排序使用了双层for循环,其中外层循环完成了数据交换,内层循环完成了数据比较,所以我们分别统计数据交换次数和数据比较次数: 数据比较次数: (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2; 数据交换次数:N-1 时间复杂度:N^2/2-N/2+(N-1)=N^2/2+N/2-1; 根据大O推导法则,保留最高阶项,去除常数因子,时间复杂度为O(N^2);
3.插入排序
如图所示:
代码实现:
package 排序;
/**
* @Date: 2022/1/6 - 01 - 06 - 15:30
* @Description:
*/
public class Insertion {
public static void sort(Comparable[] a){
for(int i=1;i<a.length;i++){//从第二个元素开始遍历
for(int j=i;j>0;j--){
//比较索引j处的值和索引j-1处的值,如果索引j-1处的值比索引j处的值大,则交换数据,如果不大,那么就找到合适的位置了,退出循环即可;
if (greater(a[j-1],a[j])){
exch(a,j-1,j);
}else{
break;
}
}
}
}
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
二、高级排序
1.希尔排序
![](https://i-blog.csdnimg.cn/blog_migrate/0ddacf3e558103ac0a89a6f0f4c277e0.png)
int h = 1while ( h < length/2 ){h = 2 h + 1 ;}// 循环结束后我们就可以确定 h 的最大值;h 的减小规则为:h = h /2
代码实现:
package 排序;
/**
* @Date: 2022/1/6 - 01 - 06 - 15:44
* @Description:
*/
public class Shell {
public static void sort(Comparable[] a){
//1.根据数组a的长度,确定增长量h的初始值;
int h = 1;
while(h<a.length/2){
h=2*h+1;
}
//2.希尔排序
while(h>=1){
//排序
//2.1.找到待插入的元素
for (int i=h;i<a.length;i++){
//2.2把待插入的元素插入到有序数列中
for (int j=i;j>=h;j-=h){
//待插入的元素是a[j],比较a[j]和a[j-h]
if (greater(a[j-h],a[j])){
//交换元素
exch(a,j-h,j);
}else{
//待插入元素已经找到了合适的位置,结束循环;
break;
}
}
}
//减小h的值
h= h/2;
}
}
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
public class SortCompare {
//调用不同的测试方法,完成测试
public static void main(String[] args) throws Exception{
//1.创建一个ArrayList集合,保存读取出来的整数
ArrayList<Integer> list = new ArrayList<>();
//2.创建缓存读取流BufferedReader,读取数据,并存储到ArrayList中;
BufferedReader reader = new BufferedReader(new InputStreamReader(SortCompare.class.getClassLoader().getResourceAsStream("reverse_arr.txt")));
String line=null;
while((line=reader.readLine())!=null){
//line是字符串,把line转换成Integer,存储到集合中
int i = Integer.parseInt(line);
list.add(i);
}
reader.close();
//3.把ArrayList集合转换成数组
Integer[] a = new Integer[list.size()];
list.toArray(a);
//4.调用测试代码完成测试
//testInsertion(a);//37499毫秒
testShell(a);//30毫秒
// testMerge(a);//70毫秒
}
//测试希尔排序
public static void testShell(Integer[] a){
//1.获取执行之前的时间
long start = System.currentTimeMillis();
//2.执行算法代码
Shell.sort(a);
//3.获取执行之后的时间
long end = System.currentTimeMillis();
//4.算出程序执行的时间并输出
System.out.println("希尔排序执行的时间为:"+(end-start)+"毫秒");
}
//测试插入排序
public static void testInsertion(Integer[] a){
//1.获取执行之前的时间
long start = System.currentTimeMillis();
//2.执行算法代码
Insertion.sort(a);
//3.获取执行之后的时间
long end = System.currentTimeMillis();
//4.算出程序执行的时间并输出
System.out.println("插入排序执行的时间为:"+(end-start)+"毫秒");
}
//测试归并排序
public static void testMerge(Integer[] a){
//1.获取执行之前的时间
long start = System.currentTimeMillis();
//2.执行算法代码
Merge.sort(a);
//3.获取执行之后的时间
long end = System.currentTimeMillis();
//4.算出程序执行的时间并输出
System.out.println("归并排序执行的时间为:"+(end-start)+"毫秒");
}
}
2.归并排序
![](https://i-blog.csdnimg.cn/blog_migrate/06e9a02647ba818655891bac3c38b74b.png)
填充原理:将原数组分为左子组和右子组,p1指针为左子组指针,p2为右子组指针,并设置i为辅助数组指针。比较当前p1,p2指针所指的元素大小,将小的放到指针i处;然后向后移动p1,p2,i指针继续比较。
代码实现:
package 排序;
/**
* @Date: 2022/1/7 - 01 - 07 - 10:14
* @Description:
*/
public class Merge {
//归并所需要的辅助数组
private static Comparable[] assist;
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w)<0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
public static void sort(Comparable[] a) {
//1.初始化辅助数组assist;
assist = new Comparable[a.length];
//2.定义一个lo变量,和hi变量,分别记录数组中最小的索引和最大的索引;
int lo=0;
int hi=a.length-1;
//3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a,lo,hi);
}
private static void sort(Comparable[] a, int lo, int hi) {
//做安全性校验;
if (hi<=lo){
return;
}
//对lo到hi之间的数据进行分为两个组
int mid = lo+(hi-lo)/2;// 5,9 mid=7
//分别对每一组数据进行排序
sort(a,lo,mid);
sort(a,mid+1,hi);
//再把两个组中的数据进行归并
merge(a,lo,mid,hi);
}
private static void merge(Comparable[] a, int lo, int mid, int hi) {
//定义三个指针
int i=lo;
int p1=lo;
int p2=mid+1;
//遍历,移动p1指针和p2指针,比较对应索引处的值,找出小的那个,放到辅助数组的对应索引处
while(p1<=mid && p2<=hi){
//比较对应索引处的值
if (less(a[p1],a[p2])){
assist[i++] = a[p1++];
}else{
assist[i++]=a[p2++];
}
}
//遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
while(p1<=mid){
assist[i++]=a[p1++];
}
//遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
while(p2<=hi){
assist[i++]=a[p2++];
}
//把辅助数组中的元素拷贝到原数组中
for(int index=lo;index<=hi;index++){
a[index]=assist[index];
}
}
}
归并排序时间复杂度分析:
3.快速排序
![](https://i-blog.csdnimg.cn/blog_migrate/cec815817ac4a41d865cb4cfdfd531a9.png)
代码实现:
package 排序;
/**
* @Date: 2022/1/7 - 01 - 07 - 10:36
* @Description:
*/
public class Quick {
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
public static void sort(Comparable[] a) {
int lo = 0;
int hi = a.length-1;
sort(a,lo,hi);
}
private static void sort(Comparable[] a, int lo, int hi) {
//安全性校验
if (hi<=lo){
return;
}
//需要对数组中lo索引到hi索引处的元素进行分组(左子组和右子组);
int partition = partition(a, lo, hi);//返回的是分组的分界值所在的索引,分界值位置变换后的索引
//让左子组有序
sort(a,lo,partition-1);
//让右子组有序
sort(a,partition+1,hi);
}
//对数组a中,从索引 lo到索引 hi之间的元素进行分组,并返回分组界限对应的索引
public static int partition(Comparable[] a, int lo, int hi) {
//确定分界值,就是第一个元素
Comparable key = a[lo];
//定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
int left=lo;
int right=hi+1;
//切分
while(true){
//先从右往左扫描,移动right指针,找到一个比分界值小的元素,停止
while(less(key,a[--right])){
if (right==lo){
break;
}
}
//再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
while(less(a[++left],key)){
if (left==hi){
break;
}
}
//判断 left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素即可
if (left>=right){
break;
}else{
exch(a,left,right);
}
}
//交换分界值,此时right=left所指的位置
exch(a,lo,right);
//最终返回分界值即可
return right;
}
}
三、排序的稳定性
![](https://i-blog.csdnimg.cn/blog_migrate/3df32cfbd0aac2b44a755a4e90107244.png)
- 冒泡排序: 只有当arr[i]>arr[i+1]的时候,才会交换元素的位置,而相等的时候并不交换位置,所以冒泡排序是一种稳定排序 算法。
- 选择排序: 选择排序是给每个位置选择当前元素最小的,例如有数据{5(1),8 ,5(2), 2, 9 },第一遍选择到的最小元素为2,所以5(1)会和2进行交换位置,此时5(1)到了5(2)后面,破坏了稳定性,所以选择排序是一种不稳定的排序算法。
- 插入排序:比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么把要插入的元素放在相等 元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
- 希尔排序:希尔排序是按照不同步长对元素进行插入排序 ,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。
- 归并排序:归并排序在归并的过程中,只有arr[i]<arr[i+1]的时候才会交换位置,如果两个元素相等则不会交换位置,所以它并不会破坏稳定性,归并排序是稳定的。
- 快速排序:快速排序需要一个基准值,在基准值的右侧找一个比基准值小的元素,在基准值的左侧找一个比基准值大的元素,然后交换这两个元素,此时会破坏稳定性,所以快速排序是一种不稳定的算法。