排序
本博客主要展示插入排序、选择排序、冒泡排序、希尔排序、归并排序、快速排序的Java代码实现,以及他们稳定性的测试。
代码
package nwnu.day11.sort;
import java.util.HashSet;
import java.util.Set;
/**
* 本节主要编写各种排序方法:
* 1. 插入排序: 稳定排序 O(n^2)
* 将待排序数值与已排序的数值相对比,小于向前移动,大于直接插入并且本次循环结束
* 优化方法1:将有序部分利用二分查找算法,找到待插入数据的插入位置。
* 优化方法2:
*
* 2. 选择排序: 不稳定排序 O(n^2)
* 每次选取最小值,放在待排序部分的最前面
*
* 3. 冒泡排序:稳定排序 O(n^2)
* 从头开始,每次交换两个元素位置,直至待排序部分的最大元素移动到最后方。
* 排序时,定义标记负flag, 在单次循环没有发生数据交换时,说明数据有序,直接结束排序操作。
*
* 4. 希尔排序: 不稳定排序 O(n^(1.3-2)) > O(nlog(n))
* 又称缩小增量排序(gap) gap = gap / 2
* 每次将gap的元素分为一组进行排序
*
* 5. 归并排序 稳定排序算法 时间复杂度:O(nlog(n)) 空间复杂度:O(n)
* 分解 -> 解决 -> 合并
*
*
* 6. 快速排序 不稳定排序算法 时间复杂度O(nlog(n))
* 快速排序从两边开始,与中间数据进行比较,
* 当first>mid && last<mid时,交换first索引和last索引位置的元素
*
*什么是稳定排序,什么是不稳定排序
* 在待排序序列中的相同大小元素,在排序后相对位置未发生改变的是稳定排序,否则是不稳定排序。
* 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的 *相对次序保持不变*
* 即在原序列中,A1=A2,且A1在A2之前,而在排序后的序列中,A1仍在A2之前,则称这种排序算法是稳定的;
* 否则称为不稳定的。
*/
public class SortMain {
public static int MAXNUMBER = 100; //测试用例中最大数的数值为100
public static int MINNUMBER = 0; //测试用例中最大数的数值为0
public static void main(String[] args) {
//插入排序测试
int[] arr = new int[]{3,2,1,4,5,4,7,9,8};
System.out.println("++++++++++++++++ 插入排序测试 +++++++++++++++++");
int[] index = generateIndex(arr);
//show(index);
insertSort(arr,index);
//选择排序测试
int[] arr2 = new int[]{9,8,7,6,5,3,2,1,1,9,8};
System.out.println("++++++++++++++++ 选择排序测试 +++++++++++++++++");
index = generateIndex(arr2);
//show(index);
selectSort(arr2,index);
//冒泡排序
int[] arr3 = new int[]{3,2,1,1,2,3};
System.out.println("++++++++++++++++ 冒泡排序测试 +++++++++++++++++");
index = generateIndex(arr3);
bubbleSort(arr3,index);
//希尔排序
int[] arr4 = new int[]{9,8,7,6,5,4,3,4,7,8,9,9,8,8};
System.out.println("++++++++++++++++ 希尔排序测试 +++++++++++++++++");
index = generateIndex(arr4);
shellSort(arr4,index);
//测试merge方法 (合并两个已经有序的数组)
int[] arr5 = new int[]{1,3,5,7,2,4,6,8};
merge(arr5, 0,3, 7);
show(arr5);
System.out.println();
//归并排序
int[] arr6 = new int[]{1,3,5,7,2,4,6,8};
System.out.println("++++++++++++++++ 归并排序测试 +++++++++++++++++");
mergeSort(arr6,0,7);
show(arr6);
System.out.println();
//快速排序 - 1
int[] arr7 = new int[]{9,8,7,6,5,4,3,2,1};
int[] arr8 = new int[]{1,3,8,1,5,1,6,7,9};
System.out.println("++++++++++++++++ 快速排序测试 +++++++++++++++++");
index = generateIndex(arr7);
quickSort(arr7,0,8,index);
show(arr7);
System.out.println();
show(index);
System.out.println();
//快速排序 - 2
System.out.println("++++++++++++++++ 快速排序测试 +++++++++++++++++");
index = generateIndex(arr8);
quickSort(arr8,0,8,index);
show(arr8);
System.out.println();
show(index);
System.out.println();
}
/**
* 插入排序
* @param arr 待排序数组
*/
public static void insertSort(int[] arr, int[] index){
for(int i=0; i<arr.length-1; i++){
for(int j=i+1; j>0; j--){
if(arr[j-1] > arr[j]){ //相同元素未交换,是稳定排序
swap(arr,j-1,j);
swap(index,j-1,j);
}else{
break;
}
}
System.out.print("第"+i+"次排序结果 = ");
show(arr);
System.out.println();
}
System.out.print(" 结果相对位置 = ");
show(index);
System.out.println();
}
/**
* 选择排序
* @param arr 待排序数组
* @param index 数组重复元素顺序值
*/
public static void selectSort(int[] arr, int[] index){
for(int i=0; i<arr.length-1; i++){
for(int j=i; j<arr.length; j++){
if(arr[i] > arr[j]){
swap(arr,i,j);
swap(index,i,j);
}
}
System.out.print("第"+i+"次排序结果 = ");
show(arr);
System.out.println();
}
System.out.print(" 结果相对位置 = ");
show(index);
System.out.println();
}
/**
* 冒牌排序
* @param arr 待排序数组
* @param index 数组重复元素顺序值
*/
public static void bubbleSort(int[] arr, int[] index){
//从后向前,每个大循环去除一个最大值
for(int i=0; i<arr.length-1; i++){
int flag = 0;
for(int j=0; j<arr.length-i-1; j++){
if(arr[j] > arr[j+1]){
swap(arr,j+1,j);
swap(index,j+1,j);
flag = 1;
}
}
System.out.print("第"+i+"次排序结果 = ");
show(arr);
System.out.println();
if(flag == 0){ //如果一次遍历没有发生数据交换,那么说明数据已经有序,此时不需要再进行后续操作
break;
}
}
System.out.print(" 结果相对位置 = ");
show(index);
System.out.println();
}
/**
* 希尔排序
* @param arr 待排序数组
* @param index 数组重复元素顺序值
*/
public static void shellSort(int[] arr, int[] index){
int gap = arr.length / 2; //由于gap特殊规定,这里就取长度的一半作为其排序间隔
while(true) {
for (int i = 0; i < gap; i++) { //循环所有分组
for(int j = i; (j+gap)<arr.length; j = j+gap){ //对于每一个分组内,进行插入排序
for(int k = j+gap; k>i; k = k-gap) { //k>i => k-gap >= i
if (arr[k-gap] > arr[k]) {
swap(arr, k-gap, k);
swap(index, k-gap, k);
}
}
}
}
System.out.print("gap = "+gap+"时, 排序结果 = ");
show(arr);
System.out.println();
if(gap == 1){ //当间隔变为1时排序结束
System.out.print(" 结果相对位置 = ");
show(index);
System.out.println();
break;
}else{
gap = gap / 2;
}
}
}
/**
* 归并排序-递归
* @param arr 待排序数组
*/
public static void mergeSort(int[] arr, int l, int r){
if(l >= r){
return;
}
int mid = (l+r)/2;
mergeSort(arr,l,mid);
mergeSort(arr,mid+1,r);
if(arr[mid] > arr[mid+1]) { //左边有序部分最大的大于右边最小的,进行合并, 提高效率
merge(arr, l, mid, r);
}
}
/**
* 合并两个有序数组
* @param a1 数组1
* @param a2 数组2
* @return 返回一个长度为两数组之和的新数组
*/
public static int[] merge(int[] a1, int[] a2){
int len1 = a1.length;
int len2 = a2.length;
int[] newArr = new int[len1+len2];
int index1 = 0; //a数组的索引
int index2 = 0; //b数组的索引
for(int i=0; i<len1+len2; i++){
if(a1[index1] < a2[index2]){ //如果a1元素小,插入到新数组中
newArr[i] = a1[index1];
index1++;
}else{
newArr[i] = a2[index2];
index2++;
}
if(index1 >= len1 && index2 < len2){
for(int j=index2; j<len2; j++){
newArr[++i] = a2[j];
}
break;
}
if(index2 >= len2 && index1 < len1){
for(int j=index1; j<len1; j++){
newArr[++i] = a1[j];
}
break;
}
}
return newArr;
}
/**
* 合并两个有序的部分为一个有序整体
* @param arr 待排序数组
* @param l 左侧待排序的开始索引
* @param mid 中间索引
* @param r 右侧待排序的结束索引 (不是数组长度)
*/
public static void merge(int[] arr, int l, int mid, int r){
int i = l;
int j = mid+1;
int[] newArr = new int[arr.length];
for(int k=l; k<=r; k++){ //这里使用 <=
if(arr[i] < arr[j]){ //左边小,先进
newArr[k] = arr[i];
i++;
}else{
newArr[k] = arr[j];
j++;
}
if(i>mid && j <= r){ //前半部分都排序完成,将后半部分全部存入newArr
for(int p=j; j<=r; j++){
newArr[++k] = arr[p]; //由于循环未结束,newArr[k] = arr[j];赋值后,k值未加一,此处需要先加加
}
break;
}
if(i<=mid && j>r){
for(int p=i; i<=mid; i++){
newArr[++k] = arr[p]; //同上
}
break;
}
}
for(int k=l; k<=r; k++){
arr[k] = newArr[k];
}
}
/**
* 快速排序
* @param arr 待排序数组
* @param l 左侧索引
* @param r 右侧索引
*/
public static void quickSort(int[] arr, int l, int r, int[] index){
if(l>=r){
return;
}
int mid = (l+r)/2;
int midNum = arr[mid];
int i = 0;
int j = r;
while(true){
while(i<mid && arr[i] < midNum)
i++;
while(j>mid && arr[j] > midNum)
j--;
if(i >= j){
break;
}
swap(arr,i,j);
swap(index,i,j);
i++;
j--;
}
System.out.print("快速排序结果 = ");
show(arr);
System.out.println();
quickSort(arr,l,mid-1,index);
quickSort(arr,mid+1,r,index);
}
/**
* 交换i j位置的元素
* @param arr 数组
* @param i 索引
* @param j 索引
*/
public static void swap(int[] arr, int i, int j){
int o = arr[i];
arr[i] = arr[j];
arr[j] = o;
}
/**
* 输出int数组中的元素
* @param arr 被输出数组
*/
public static void show(int[] arr){
for (int i : arr) {
System.out.print(i+" ");
}
}
/**
* 根据arr中的元素,生成相同元素的相对位置,单个元素用0表示,多个相同元素根据位置前后依次编号为 0 1 2 3 4 ...
* @param arr 待标记数组
*/
public static int[] generateIndex(int[] arr) throws NumberFormatException {
int size = arr.length;
int[] num = new int[MAXNUMBER+1]; //用于记录相同数字出现的次数
int[] index = new int[size];
Set<Integer> set = new HashSet<Integer>();
for(int i=0; i<size; i++){
if(set.contains(arr[i])){
if(arr[i] <0 || arr[i] >100){
throw new NumberFormatException();
}
index[i] = ++num[arr[i]]; //每次查询到包含时,增加1
}else{
set.add(arr[i]);
}
}
return index;
}
}