数据结构与算法(Java)之排序

冒泡排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 冒泡排序规则:
 * 1.排序的次数是数组长度-1
 * 2.每次都将本趟最大或最小的那个数放到倒数位置
 * 3.(优化)当发现一趟排序没有进行交换的时候说明当前的数组已经是有序的,不要再往下进行比较了
 */
public class BubbleSort {
    public static void main(String[] args) {
//        int[] arr = {3, 9, -1, 10, 20};

        //测是大量数据时冒泡排序的耗时
        int[] arr = new int[80000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }
        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        bubbleSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("冒泡排序耗时:" + (end - start) + "ms");

//
//        int temp = 0;//交换时使用的临时变量
//        boolean flag = false;//设置一个哨兵用于优化冒泡排序
        //为了方便理解写出冒泡排序的原始解法
//        //第一趟将第一大的数排到倒数第一的位置
//        for (int j = 0; j < arr.length - 1; j++){
//            if (arr[j] > arr[j + 1]){
//                temp = arr[j + 1];
//                arr[j + 1] = arr[j];
//                arr[j] = temp;
//            }
//        }
//        System.out.println("第一趟排序完之后的数组:" + Arrays.toString(arr));
//
//        //第二趟将第二大的数排到倒数第二的位置
//        for (int j = 0; j < arr.length - 2; j++){
//            if (arr[j] > arr[j + 1]){
//                temp = arr[j + 1];
//                arr[j + 1] = arr[j];
//                arr[j] = temp;
//            }
//        }
//        System.out.println("第二趟排序完之后的数组:" + Arrays.toString(arr));
//
//        //第三趟将第三大的数排到倒数第三的位置
//        for (int j = 0; j < arr.length - 3; j++){
//            if (arr[j] > arr[j + 1]){
//                temp = arr[j + 1];
//                arr[j + 1] = arr[j];
//                arr[j] = temp;
//            }
//        }
//        System.out.println("第三趟排序完之后的数组:" + Arrays.toString(arr));
//
//        //第四趟将第四大的数排到倒数第四的位置
//        for (int j = 0; j < arr.length - 4; j++){
//            if (arr[j] > arr[j + 1]){
//                temp = arr[j + 1];
//                arr[j + 1] = arr[j];
//                arr[j] = temp;
//            }
//        }
//        System.out.println("第四趟排序完之后的数组:" + Arrays.toString(arr));

        //使用循环嵌套就能解决上面的多次循环
//        for (int i = 0; i < arr.length - 1; i++) {
//            for (int j = 0; j < arr.length - i - 1; j++) {
//                //第一趟将第一大的数排到倒数第一的位置
//                if (arr[j] > arr[j + 1]){
//                    flag = true;//标志本趟有经过交换操作
//                    temp = arr[j + 1];
//                    arr[j + 1] = arr[j];
//                    arr[j] = temp;
//                }
//            }
//            if(!flag){//flag依然是false说明本趟没有交换操作,可以退出循环
//                break;
//            }else{//本趟有交换操作为了下次标识要将flag还原
//                flag = false;
//            }
//            System.out.printf("第%d趟排序完之后的数组:%s\n", (i+1), Arrays.toString(arr));
//        }
    }

    //将上面的冒牌排序封装为一个方法
    public static void bubbleSort(int[] arr){
        int temp = 0;//交换时使用的临时变量
        boolean flag = false;//设置一个哨兵用于优化冒泡排序
        //使用循环嵌套就能解决上面的多次循环
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                //第一趟将第一大的数排到倒数第一的位置
                if (arr[j] > arr[j + 1]){
                    flag = true;//标志本趟有经过交换操作
                    temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
            if(!flag){//flag依然是false说明本趟没有交换操作,可以退出循环
                break;
            }else{//本趟有交换操作为了下次标识要将flag还原
                flag = false;
            }
//            System.out.printf("第%d趟排序完之后的数组:%s\n", (i+1), Arrays.toString(arr));
        }
    }
}

选择排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 选择排序思想:
 * 1.一共进行n-1轮排序,n为数组arr的长度
 * 2.第i轮将第i小/大的数放到arr[i-1]的位置
 * 3.(优化)如果第i小/大的数就是本身就不用交换
 */
public class SelectSort {
    public static void main(String[] args) {
//        int[] arr = {110, 30, 119, 1};
//        System.out.println("排序前:" + Arrays.toString(arr));
//        selectSort(arr);

        //测试选择排序的耗时
        int[] arr = new int[80000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }

        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        selectSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("选择排序耗时测试用时:" + (end - start) + "ms");//选择排序耗时测试用时:3541ms

    }

    public static void selectSort(int[] arr){

        //从下面的详细分析可以找到规律
        int minIndex = 0;//假设最小值的下标
        int min = arr[0];//假设最小值
        boolean flag = false;//标识本轮要不要交换
        for (int i = 0; i < arr.length - 1; i++) {
            //遍历数组从minIndex+1下标开始,找到最小值和最小值的下标
            for (int j = i + 1; j < arr.length; j++){
                if(min > arr[j]) {
                    flag = true;
                    minIndex = j;
                    min = arr[j];
                }
            }
            if(flag) {//找到新的最小值才要交换
                //将当前的数值和最小值交换
                arr[minIndex] = arr[i];
                arr[i] = min;
                flag = false;//重置flag
            }
            //显示本轮经过交换的情况
//            System.out.println("第"+ (i + 1) +"轮交换后的结果:" + Arrays.toString(arr));
        }
        /*
        //以案例:使用选择排序将{110, 30, 119, 1}这个数组从小到大排序
        //为了理解选择排序,将步骤拆分为以下 3 轮排序
        //第一轮:{110, 30, 119, 1}->{1, 30, 119, 110}
        int minIndex = 0;//假设最小值的下标
        int min = arr[0];//假设最小值
        boolean flag = false;//标识本轮要不要交换
        //遍历数组从minIndex+1下标开始,找到最小值和最小值的下标
        for (int j = 0 + 1; j < arr.length; j++){
            if(min > arr[j]) {
                flag = true;
                minIndex = j;
                min = arr[j];
            }
        }
        if(flag) {//找到新的最小值才要交换
            //将当前的数值和最小值交换
            arr[minIndex] = arr[0];
            arr[0] = min;
            flag = false;//重置flag
        }
        //显示本轮经过交换的情况
        System.out.println("第1轮交换后的结果:" + Arrays.toString(arr));

        //第二轮:{1, 30, 119, 110}->{1, 30, 119, 110}
        minIndex = 1;//假设最小值的下标
        min = arr[1];//假设最小值
        flag = false;//标识本轮要不要交换
        //遍历数组从minIndex+1下标开始,找到最小值和最小值的下标
        for (int j = 1 + 1; j < arr.length; j++){
            if(min > arr[j]) {
                flag = true;
                minIndex = j;
                min = arr[j];
            }
        }
        if(flag) {//找到新的最小值才要交换
            //将当前的数值和最小值交换
            arr[minIndex] = arr[1];
            arr[1] = min;
            flag = false;//重置flag
        }
        //显示本轮经过交换的情况
        System.out.println("第2轮交换后的结果:" + Arrays.toString(arr));

        //第三轮:{1, 30, 119, 110}->{1, 30, 110, 119}
        minIndex = 2;//假设最小值的下标
        min = arr[2];//假设最小值
        flag = false;//标识本轮要不要交换
        //遍历数组从minIndex+1下标开始,找到最小值和最小值的下标
        for (int j = 2 + 1; j < arr.length; j++){
            if(min > arr[j]) {
                flag = true;
                minIndex = j;
                min = arr[j];
            }
        }
        if(flag) {//找到新的最小值才要交换
            //将当前的数值和最小值交换
            arr[minIndex] = arr[2];
            arr[2] = min;
            flag = false;//重置flag
        }
        //显示本轮经过交换的情况
        System.out.println("第3轮交换后的结果:" + Arrays.toString(arr));

         */
    }
}

插入排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 插入排序算法规则:
 * 1.排序数组分为两部分:有序表和无序表(默认数组的第一个数为有序表,剩余的元素都是无序表)
 * 2.每次去除无序表的第一个元素a和有序表的最后一个元素b进行比较(我们这里是按照从小到大排序),
 *   如果发现a<b证明还没有找到插入位置,需要将b后移一个位置,a再和b的前一个数(重新赋值给b)
 *   进行比较当发现a>b时证明已经发现插入的位置,将a插入该位置就行
 * 3.从上面的分析可以知道 插入排序 要得到有序的数组,要经过 数组的长度-1 轮的排序操作
 */
public class InsertSort {
    public static void main(String[] args) {
//        int[] arr = {110, 30, 119, 1};
//        System.out.println("排序前的数组:" + Arrays.toString(arr));
//        insertSort(arr);

        //测试插入排序的耗时
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }

        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        insertSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("插入排序的耗时:" + (end - start) + "ms");//插入排序的耗时:1099ms
    }

    public static void insertSort(int[] arr){

        //从下面的分步分析可以得到规律
        int insertIndex = 0;//假设从无需表中选出的数据将要插到数组的位置
        int insertValue = 0;//从无序表中选出的数据
        for (int i = 0; i < arr.length - 1; i++) {
            insertIndex = i;//假设从无需表中选出的数据将要插到数组的位置
            insertValue = arr[insertIndex + 1];//从无序表中选出的数据
            //查找选出的数据要插入的位置
            //insertIndex >= 0保证不发生数组越界
            //insertValue < arr[insertIndex] 说明还没找到要插入的位置,如果向从大到小可以将<改为>
            while(insertIndex >= 0 && insertValue < arr[insertIndex]){
                //将arr[insertIndex]的数据后移一位
                arr[insertIndex + 1] = arr[insertIndex];
                //将insertIndex向前移动
                insertIndex--;
            }
            //当退出循环说明已经找到了要插入的位置下标就是 insertIndex + 1
            arr[insertIndex + 1] = insertValue;
            //打印本轮排序后的数组
//            System.out.printf("第%d轮排序后的数组:%s\n", (i + 1), Arrays.toString(arr));
        }

        /*
        //案例:将arr = {110, 30, 119, 1}用插入排序算法将arr从小到大排序
        //为了分析,拆分多轮排序
        //第1轮:{110, 30, 119, 1}->{30, 110, 119, 1}
        int insertIndex = 0;//假设从无需表中选出的数据将要插到数组的位置
        int insertValue = arr[insertIndex + 1];//从无序表中选出的数据
        //查找选出的数据要插入的位置
        //insertIndex >= 0保证不发生数组越界
        //insertValue < arr[insertIndex] 说明还没找到要插入的位置
        while(insertIndex >= 0 && insertValue < arr[insertIndex]){
            //将arr[insertIndex]的数据后移一位
            arr[insertIndex + 1] = arr[insertIndex];
            //将insertIndex向前移动
            insertIndex--;
        }
        //当退出循环说明已经找到了要插入的位置下标就是 insertIndex + 1
        arr[insertIndex + 1] = insertValue;
        //打印本轮排序后的数组
        System.out.printf("第%d轮排序后的数组:%s\n", 1, Arrays.toString(arr));

        //第2轮:{30, 110, 119, 1}->{30, 110, 119, 1}
        insertIndex = 1;//假设从无需表中选出的数据将要插到数组的位置
        insertValue = arr[insertIndex + 1];//从无序表中选出的数据
        //查找选出的数据要插入的位置
        //insertIndex >= 0保证不发生数组越界
        //insertValue < arr[insertIndex] 说明还没找到要插入的位置
        while(insertIndex >= 0 && insertValue < arr[insertIndex]){
            //将arr[insertIndex]的数据后移一位
            arr[insertIndex + 1] = arr[insertIndex];
            //将insertIndex向前移动
            insertIndex--;
        }
        //当退出循环说明已经找到了要插入的位置下标就是 insertIndex + 1
        arr[insertIndex + 1] = insertValue;
        //打印本轮排序后的数组
        System.out.printf("第%d轮排序后的数组:%s\n", 2, Arrays.toString(arr));

        //第2轮:{30, 110, 119, 1}->{1, 30, 110, 119}
        insertIndex = 2;//假设从无需表中选出的数据将要插到数组的位置
        insertValue = arr[insertIndex + 1];//从无序表中选出的数据
        //查找选出的数据要插入的位置
        //insertIndex >= 0保证不发生数组越界
        //insertValue < arr[insertIndex] 说明还没找到要插入的位置
        while(insertIndex >= 0 && insertValue < arr[insertIndex]){
            //将arr[insertIndex]的数据后移一位
            arr[insertIndex + 1] = arr[insertIndex];
            //将insertIndex向前移动
            insertIndex--;
        }
        //当退出循环说明已经找到了要插入的位置下标就是 insertIndex + 1
        arr[insertIndex + 1] = insertValue;
        //打印本轮排序后的数组
        System.out.printf("第%d轮排序后的数组:%s\n", 3, Arrays.toString(arr));

         */
    }
}

希尔排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 希尔排序算法:(实现方法分为交换式和移位式)
 *
 * 引出希尔排序:看看插入算法存在的问题,当一个有较小的数排在后面而前
 * 面的数都是比较有序的数组需要排序时,比如{2, 3, 4, 5, 6, 1}即使
 * 前五个数都是有序的,但是还是要经过 5 轮的排序:
 * {2, 3, 4, 5, 6, 1}->{2, 3, 4, 5, 6, 1}->{2, 3, 4, 5, 6, 1}->{2, 3, 4, 5, 6, 1}
 * ->{2, 3, 4, 5, 6, 1}->{1, 2, 3, 4, 5, 6}, 所以插入排序在某些排序中的效率不高
 *
 * 希尔排序是插入排序的升级版,又称缩小增量排序
 * 希尔排序算法思想:
 * 假设需要排序的数组为:arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0}
 * 第1轮排序,将arr分成(arr.length/2) = 5组:就是步长为5的 5组数据:
 * (8, 3), (9, 5), (1, 4), (7, 6), (2, 0)
 * 在5组数据内进行各自排序,所以第1轮排序后的数组为{3, 5, 1, 6, 0, 8, 9, 4, 7, 2}
 *
 * 第2轮排序,将arr分成(arr.length/2/2) = 2组:就是步长为2的 2组数据:
 * (3, 1, 0, 9, 7), (5, 6, 8, 4, 2)
 * 两组数据内进行各自排序,所以第二轮排序后的数组为{0, 2, 1, 4, 3, 5, 7, 6, 9, 8}
 *
 * 第3轮排序,将arr分为(arr.length/2/2/2) = 1组,就是所有数据为一组:
 * (0, 2, 1, 4, 3, 5, 7, 6, 9, 8)
 * 这一组数据内进行插入排序,所以第3轮排序后的数组为{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 *
 * 从上面的分析来看,希尔排序就是将较小(前提是从小到大排序)的数据方法数组较前的位置,
 * 避免插入排序的弊端问题
 */
public class ShellSort {
    public static void main(String[] args) {
//        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
//        System.out.println("排序前:" + Arrays.toString(arr));
//        shellSort2(arr);

        //测试希尔排序的耗时(交换式)
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }

//        System.out.println("排序中...");
//        long start = System.currentTimeMillis();
//        shellSort(arr);
//        long end = System.currentTimeMillis();
//        System.out.println("希尔排序(交换式)的耗时:" + (end - start) + "ms");//希尔排序(交换式)的耗时:9863ms

        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        shellSort2(arr);
        long end = System.currentTimeMillis();
        System.out.println("希尔排序(移位式)的耗时:" + (end - start) + "ms");//希尔排序(移位式)的耗时:31ms
    }

    //交换式实现希尔排序(效率较低)
    public static void shellSort(int[] arr){

        //根据下面的分析可以总结规律
        int temp = 0;
        int count = 0;//统计轮数
        for (int gap = (int)arr.length / 2; gap >= 1; gap = (int)(gap / 2)){
            for (int i = gap; i < arr.length; i++) {
                for (int j = i - gap; j >= 0; j -= gap) {
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
//            System.out.printf("第%d轮排序后:%s\n", ++count, Arrays.toString(arr));
        }

/*
        //为类更清楚分析希尔排序,将分步骤实现希尔排序
        //第1轮排序
        int temp = 0;
        for (int i = 5; i < arr.length; i++) {
            for (int j = i - 5; j >= 0; j-=5) {
                if(arr[j] > arr[j + 5]){//如果这一组内的数前者比后者大的就进行交换
                    temp = arr[j];
                    arr[j] = arr[j + 5];
                    arr[j + 5] = temp;
                }
            }
        }
        System.out.printf("第%d轮排序后:%s\n", 1, Arrays.toString(arr));

        //第2轮排序
        temp = 0;
        for (int i = 2; i < arr.length; i++) {
            for (int j = i - 2; j >= 0; j-=2) {
                if(arr[j] > arr[j + 2]){//如果这一组内的数前者比后者大的就进行交换
                    temp = arr[j];
                    arr[j] = arr[j + 2];
                    arr[j + 2] = temp;
                }
            }
        }
        System.out.printf("第%d轮排序后:%s\n", 2, Arrays.toString(arr));

        //第3轮排序
        temp = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0; j-=1) {
                if(arr[j] > arr[j + 1]){//如果这一组内的数前者比后者大的就进行交换
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        System.out.printf("第%d轮排序后:%s\n", 3, Arrays.toString(arr));

 */
    }

    //移位式实现希尔排序(就是结合插入排序)
    public static void shellSort2(int[] arr){
        int index = 0;//表示将要插入的位置
        int value = 0;//表示将要
        int count = 0;//统计轮数
        for(int gag = arr.length / 2; gag > 0; gag /= 2){
            for (int i = gag; i < arr.length; i++) {
                index = i - gag;
                value = arr[i];
                //查找要插入的位置
                while(index >= 0 && value < arr[index]){
                    //将当前index的值赋值给index+gag也就是i下标的位置
                    arr[index + gag] = arr[index];
                    //将index向前移动gap个位置
                    index -= gag;
                }
                //退出循环证明已经找到插入的位置了为index+gap
                arr[index + gag] = value;
            }
//            System.out.printf("第%d轮排序后:%s\n", ++count, Arrays.toString(arr));
        }
    }
}

快速排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 快速排序思想:以将数组arr = {7, 4, 1, 9, 5, 3, 2, 6, 0, 8}从小到大排序为例讲解
 * 1.确定基准点本例的基准点为int mid=(right+left)/2=4, left=0, right=arr.length-1=9
 *   基准点将数组分为左右两边
 * 2.定义变量的记录基准点的值midVal = arr[mid] = 5, 定义两个游标r = 9, l = 0方便遍历数组
 *   左右两边,将比midVal小的数放到midVal的左边,将比midVal大的数放到midVal的右边
 * 3.比较两个游标所指元素和midVal的大小,在右边while(arr[r]>midVal){r-=1}找到arr[r]<midVal
 *   的数,在左边while(arr[l]<midVal){l+=1}找到arr[l]>midVal的数,将arr[r]和arr[l]交换,
 * 4.继续第3步的操作,直到r==l时证明midVal左边的数都小于midVal,右边的数都大于midVal,结束本次循环,
 *   还有如果数组中的有相同元素时,当arr[r]==arr[l]也应该终止循环,否则会无限循环
 * 5.然后使用递归将midVal左右两边的进行2,3,4的操作
 */
public class QuickSort {
    public static void main(String[] args) {
//        int[] arr = {7, 4, 1, -1, -340, 70, -3, 5, 80, 9, 5, 3, 2, 6, 0, 8};
//        System.out.println("排序前:" + Arrays.toString(arr));
//        quickSort(arr, 0, arr.length - 1);
//        System.out.println("排序后:" + Arrays.toString(arr));

        //测试快速排序的效率
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }
        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        quickSort(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();
        System.out.println("快速排序的耗时:" + (end - start) + "ms");//快速排序的耗时:68ms

    }

    public static void quickSort(int[] arr, int left, int right){
        //定义两个游标并初始化
        int l = left;
        int r = right;
        //定义基准点,并初始化
        int mid = (l + r) / 2;
        //定义变量记录基准点的值
        int midVal = arr[mid];
        //临时变量用于交换
        int temp = 0;
        while(l < r){//直到r==l时证明midVal左边的数都小于midVal,右边的数都大于midVal,结束本次循环
            //在左边找到一个大于midVal的元素,到退出循环时说明已经找到
            while(arr[l] < midVal){
                l += 1;
            }
            //在右边找到一个小于midVal的元素,到退出循环时说明已经找到
            while(arr[r] > midVal){
                r -= 1;
            }
            //当遇到arr[r]与arr[l]相等时也许时r,l重叠,也许是数组中有相同元素,
            // 此时应该终止循环,否则就会无限循环
            if(arr[r] == arr[l]){
                break;
            }
            if(!(r == l)) {//当r和l相等的时候不用进行交换操作
                //将左右两边找到的数交换
                temp = arr[r];
                arr[r] = arr[l];
                arr[l] = temp;
            }
        }

        //将r, l重新赋值
        r -= 1;//将r移动到midVal的前一个位置,作为下一次左递归的右起点
        l += 1;//将l移动到midVal的后一个位置,作为下一次右递归的左起点

        //左边递归
        if(r > left){
            quickSort(arr, left, r);
        }
        //左边递归
        if(l < right){
            quickSort(arr, l, right);
        }

    }
}

归并排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 归并排序算法:以将数组arr = {7, 4, 1, 9, 5, 3, 2, 6, 0, 8}从小到大排序为例讲解
 * 归并排序的思想是分治算法,先分后治,分:将arr拆分为单独的元素,治:将拆分的元素按顺序归并
 * 分的步骤:
 * 1.定义左右标志并初始化int left = 0, int right = arr.length - 1;
 * 2.定义中间标志int mid = (left + right) / 2,将数组从mid成两部分分别是:
 *   {arr[left]-arr[mid]}和{arr[mid+1]-arr[right]}
 * 3.使用递归算法将数组分为单独的元素
 *
 * 治的步骤:就是合并被分开的每个元素(合并的次数: arr.length - 1)
 * 1.传入的参数是:int[] arr, int left, int right, int[] temp;temp数组用于暂存排序后的元素
 * 2.定义两个游标并初始化:int r = left, int l = (left + right)/2 + 1,r,l分别指向需要合并的
 *   两个分组的最开始位置,定义并初始化int t = 0,指向temp数组kai开始位置
 * 3.开始合并的第一步:比较arr[r]与arr[l],
 *   if(arr[l]>arr[r]){temp[t] = arr[r]; r+=1; t+=1}
 *   if(arr[l]<arr[r]){temp[t] = arr[l]; l+=1; t+=1}
 * 4.合并的第二步:将左或右其中还没加入temp数组中的元素继续加入temp中
 *   while(r<(left+right)/2){temp[t]=arr[r]; r+=1; t+=1}
 *   或
 *   while(l<(left+right)/2){temp[t]=arr[l]; l+=1; t+=1}
 * 5.合并的第三步:将temp中的值赋值给arr,并不是每次合并的后都要将temp中的所有元素都赋值给arr
 *   只需要将新加入的值赋值给arr的对应位置就可以,从arr[left]到arr[right]
 *
 *                    {7, 4, 1, 9, 5, 3, 2, 6, 0, 8}
 *         {7, 4, 1}    {9, 5}                {3, 2, 6}   {0, 8}
 *     {7, 4}   {1}    {9}  {5}            {3, 2}  {6}   {0}  {8}
 *   {7}   {4}                            {3}  {2}
 *   {4, 7}                               {2, 3}
 *     {1, 4, 7}      {5, 9}              {2, 3, 6}     {0, 8}
 *     {1, 4, 5, 7, 9}                       {0, 2, 3, 6, 8}
 *                    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 */
public class MergeSort {
    public static void main(String[] args) {
//        int[] arr = {7, 4, 1, 9, 5, 3, 2, 6, 0, 8};
//        int[] temp = new int[arr.length];//归并排序需要符出更大的空间代价,以空间换时间
//        System.out.println("排序前:" + Arrays.toString(arr));
//        mergeSort(arr, 0, arr.length-1, temp);
//        System.out.println("排序后:" + Arrays.toString(arr));

        //测试归并排序的效率
        int[] arr = new int[80000];
        int[] temp = new int[arr.length];//归并排序需要符出更大的空间代价,以空间换时间
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }
        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        mergeSort(arr, 0, arr.length - 1, temp);
        long end = System.currentTimeMillis();
        System.out.println("归并排序的耗时:" + (end - start) + "ms");//归并排序的耗时:21ms
    }

    //归并排序
    public static void mergeSort(int[] arr, int left, int right, int[] temp){
        //定义变量记录中间下标
        int mid = (left + right) / 2;
        //循环递归分割
        if (left < right){
            mergeSort(arr, left, mid, temp);
            mergeSort(arr, mid+1, right, temp);
            merge(arr, left, mid, right, temp);
        }
    }

    //合并
    public static void merge(int[] arr, int left, int mid, int right, int[] temp){
        //定义两个游标并初始化:int r = left, int l = (left + right) + 1,r,l
        // 分别指向需要合并的两个分组的最开始位置
        int l = left;
        int r = mid + 1;
        // 定义并初始化int t = 0,指向temp数组开始位置
        int t = 0;
        //开始合并的第一步:比较arr[r]与arr[l],
        // *   if(arr[l]>arr[r]){temp[t] = arr[r]; r+=1; t+=1}
        // *   if(arr[l]<arr[r]){temp[t] = arr[l]; l+=1; t+=1}
        while(l <= mid && r<= right){//当退出循环时,证明有一边已经遍历完毕
            if(arr[l] <= arr[r]){
                temp[t] = arr[l];
                t += 1;
                l += 1;
            }else{
                temp[t] = arr[r];
                t += 1;
                r += 1;
            }
        }

        //合并的第二步:将左或右其中还没加入temp数组中的元素继续加入temp中
        // *   while(r<(left+right)/2){temp[t]=arr[r]; r+=1; t+=1}
        // *   或
        // *   while(l<(left+right)/2){temp[t]=arr[l]; l+=1; t+=1}
        //当左边没有遍历完,需要继续将左边的元素加入到temp中
        while(l <= mid){
            temp[t] = arr[l];
            t += 1;
            l += 1;
        }
        //当右边没有遍历完,需要继续将左边的元素加入到temp中
        while(r <= right){
            temp[t] = arr[r];
            t += 1;
            r += 1;
        }

        //合并的第三步:将temp中的值赋值给arr,并不是每次合并的后都要将temp中的所有元素都赋值给arr
        // *   只需要将新加入的值赋值给arr的对应位置就可以,从arr[left]到arr[right]
        int cur = left;
        //将t重新指向temp数组的起始位置
        t = 0;
        while(cur <= right){
            arr[cur] = temp[t];
            cur += 1;
            t += 1;
        }
    }
}

基数排序

package com.weeks.sort;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 *
 * 基数排序:以将arr = {543, 68, 8, 52, 73, 46, 9, 775} 从小到大排序为例
 *
 * 桶排序是以空间换时间的算法,如果要排序大量的数据,基数排序不适用,桶排序需要消耗极大的内存空间
 * 还有如果数组中有负数要注意以下算法需要适当调整
 */
public class BucketSort {
    public static void main(String[] args) {
//        int[] arr = {543, 68, 8, 52, 73, 46, 9, 775};
//        bucketSort(arr);
        //测试快速排序的效率
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int)(Math.random() * 80000);
        }
        System.out.println("排序中...");
        long start = System.currentTimeMillis();
        bucketSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("桶排序的耗时:" + (end - start) + "ms");//桶排序的耗时:35ms
    }

    public static void bucketSort(int[] arr){

        //从下面详细分析可找到规律
        //首先要获得数组中最大数的位数
        //查找最大数
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if(max < arr[i]){
                max = arr[i];
            }
        }
        //获得最大数的长度(位数个数)
        int maxLen = (max + "").length();
        //初始化一个二维数组,二维数组的行数是10,代表十个桶分别装0-9这10个数
        //每一行就是一个一维数组,一维数组的大小是arr.length,防止溢出(比如每个数的个位都是1,
        // 那么就只能将所有元素都装在1这个桶里)
        int[][] buckets = new int[10][arr.length];
        //初始化一个一位数组,表示每个桶中装了几个有效元素
        int[] elements = new int[10];


        for (int l = 0, n = 1; l < maxLen; l++, n *= 10){
            //将每个元素按照 对应位 的大小分别放入对应的桶中
            for (int i = 0; i < arr.length; i++) {
                //获得 个位 的数字
                int data = arr[i] / n % 10;
                //将当前元素按照放入data桶中
                buckets[data][elements[data]++] = arr[i];
            }
            //将通过的数据,按照桶的编号0-9,依次取出有效数据放入arr中
            int index = 0;//索引arr数组
            //遍历所有的桶
            for (int i = 0; i < buckets.length; i++) {
                //遍历每个桶中的有效数据
                for (int j = 0; j < elements[i]; j++) {
                    //将桶中数据放回arr中
                    arr[index++] = buckets[i][j];
                }
            }
            //取出所有数据后需要将桶中的有效数据清零
            for (int i = 0; i < elements.length; i++) {
                elements[i] = 0;
            }
//            System.out.println("第" + (l + 1) + "轮排序后:" + Arrays.toString(arr));
        }

        /*
        //分部讲解基数排序排序的过程
        //初始化一个二维数组,二维数组的行数是10,代表十个桶分别装0-9这10个数
        //每一行就是一个一维数组,一维数组的大小是arr.length,防止溢出(比如每个数的个位都是1,
        // 那么就只能将所有元素都装在1这个桶里)
        int[][] buckets = new int[10][arr.length];
        //初始化一个一位数组,表示每个桶中装了几个有效元素
        int[] elements = new int[10];
        //第一轮排序
        //将每个元素按照 个位 的大小分别放入对应的桶中
        for (int i = 0; i < arr.length; i++) {
            //获得 个位 的数字
            int data = arr[i] % 10;
            //将当前元素按照放入data桶中
            buckets[data][elements[data]++] = arr[i];
        }
        //将通过的数据,按照桶的编号0-9,依次取出有效数据放入arr中
        int index = 0;//索引arr数组
        //遍历所有的桶
        for (int i = 0; i < buckets.length; i++) {
            //遍历每个桶中的有效数据
            for (int j = 0; j < elements[i]; j++) {
                //将桶中数据放回arr中
                arr[index++] = buckets[i][j];
            }
        }
        //取出所有数据后需要将桶中的有效数据清零
        for (int i = 0; i < elements.length; i++) {
            elements[i] = 0;
        }
        System.out.println("第1轮排序后:" + Arrays.toString(arr));


        //第二轮排序
        //将每个元素按照 个位 的大小分别放入对应的桶中
        for (int i = 0; i < arr.length; i++) {
            //获得 十位 的数字
            int data = arr[i] / 10 % 10;
            //将当前元素按照放入data桶中
            buckets[data][elements[data]++] = arr[i];
        }
        //将通过的数据,按照桶的编号0-9,依次取出有效数据放入arr中
        index = 0;//索引arr数组
        //遍历所有的桶
        for (int i = 0; i < buckets.length; i++) {
            //遍历每个桶中的有效数据
            for (int j = 0; j < elements[i]; j++) {
                //将桶中数据放回arr中
                arr[index++] = buckets[i][j];
            }
        }
        //取出所有数据后需要将桶中的有效数据清零
        for (int i = 0; i < elements.length; i++) {
            elements[i] = 0;
        }
        System.out.println("第2轮排序后:" + Arrays.toString(arr));

        //第三轮排序
        //将每个元素按照 个位 的大小分别放入对应的桶中
        for (int i = 0; i < arr.length; i++) {
            //获得 十位 的数字
            int data = arr[i] / 100 % 10;
            //将当前元素按照放入data桶中
            buckets[data][elements[data]++] = arr[i];
        }
        //将通过的数据,按照桶的编号0-9,依次取出有效数据放入arr中
        index = 0;//索引arr数组
        //遍历所有的桶
        for (int i = 0; i < buckets.length; i++) {
            //遍历每个桶中的有效数据
            for (int j = 0; j < elements[i]; j++) {
                //将桶中数据放回arr中
                arr[index++] = buckets[i][j];
            }
        }
        //取出所有数据后需要将桶中的有效数据清零
        for (int i = 0; i < elements.length; i++) {
            elements[i] = 0;
        }
        System.out.println("第3轮排序后:" + Arrays.toString(arr));

         */
    }
}

堆排序(要运用到二叉树的知识)

package com.weeks.tree;

import java.util.Arrays;

/**
 * @author 达少
 * @version 1.0
 * 堆排序:
 * 1.将一个需要排序的数组以大顶堆的方式排列(升序大顶堆,降序小顶堆)
 * 2.从左往右,从下至上调整每个非叶子节点为大顶堆
 * 3.完成步骤2时,将根节点与末尾结点交换,最大值就在末尾了
 * 4.将数组长度减一,就是不带上末尾元素,重复2和3
 *
 * 大顶堆:每个非叶子结点的数值大于或等于左右结点的数值,就是
 *       arr[i] >= arr[2 * i + 1] && arr[i] >= arr[2 * i + 2]
 * 小顶堆:每个非叶子结点的数值小于或等于左右结点的数值,就是
 *        arr[i] <= arr[2 * i + 1] && arr[i] <= arr[2 * i + 2]
 */
public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        System.out.println("排序前:" + Arrays.toString(arr));
        headSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }

    public static void headSort(int[] arr){
        //首先将无序的数组,构建大顶堆
        for (int i = arr.length / 2 -1; i >= 0; i--) {
            adjust(arr, i, arr.length);
        }
        int temp = 0;//辅助交换
        for (int j = arr.length - 1; j > 0; j--){
            //交换堆顶和末尾元素
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] =temp;

            //交换后导致了大顶堆的结构破环,需要重新调整除刚刚交换后到末尾的元素外
            adjust(arr, 0, j);
        }
    }

    //将某个结点调节为大顶堆
    /**
     *
     * @param arr 需要调整的数组
     * @param i   调整的非叶子结点在数组中的下标
     * @param length 表示对多长的数组元素调整,length会逐渐减少
     */
    public static void adjust(int[] arr, int i, int length){
        //先取出下标为i的值,保存在临时变量中
        int temp = arr[i];
        //开始调整
        for (int k = 2 * i + 1; k < length; k = 2 * k + 1){
            if((k + 1) < length && arr[k] < arr[k + 1]){//左右结点的值比较大小
                k++;//如果右结点的数大于左结点的数,就将k++,让k指向右结点
            }
            if(arr[k] > temp){//比较arr[k]与temp的大小
                //如果条件成立
                arr[i] = arr[k];
                i = k;//i指向k,让下次循环看看是否本次的调整影响到下面的顺序
            }else{
                //这里能直接break,是因为堆排序调整是从左到右,从下至上开始调整
                //如果当没有发现当前值比下面的结点小的时候,证明下面的已经调整好了
                break;
            }
        }
        //将temp的值放在最后的调整的位置
        arr[i] = temp;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值