排序算法总结

算法的复杂度

在这里插入图片描述

工具方法
	public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    public static void printArr(int[] arr) {
        System.out.print("[ ");
        for (int i : arr) {
            System.out.print(i + ",");
        }
        System.out.println("]");
    }

1.冒泡排序

通过相邻元素的比较和交换,使得每一趟循环都能找到未有序数组的最大值或最小值。

  • 最好:O(n),元素有序,只需要冒泡一次就可以
  • 最坏:O(n²)
  • 平均: O(n²)

冒泡排序算法思想是:

  • 进行 n-1 次排序
  • 每次排序从0~n-1-i之间进行相邻比较,使最大的数,向后交换,每次排序排好一个最值。
算法:
	//经典的冒泡排序算法
    public void sort1(int[] arr) {
        //需要循环几次,每次循环出来一个最值,当n-1个数,排序完,最后一个则不要
        for (int i = 0, len = arr.length; i < len - 1; i++)
            for (int j = 0; j < len - 1 - i; j++)//将前一个和后一个进行比较
                if (arr[j] > arr[j + 1])
                    swap(arr, j, j + 1);
    }

	//或者 边界值进行更改
	public void sort2(int[] arr) {
        for (int i = arr.length - 1; i > 0; i--)
            for (int j = 0; j < i; j++)
                if (arr[j] > arr[j + 1])
                    swap(arr, j, j + 1);
    }
优化一:

加入mark标记,标记一次排序中,是否有数值交换,如果没有,则说明数组是有序数组。

	/**
     * 第一次优化: mark
     * 加入标记,判断是否已经排好序了
     * mark;
     */
    public void sort3(int[] arr) {
        for (int i = arr.length - 1; i > 0; i--) {
            boolean mark = true; //数组是否有序标记
            for (int j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    mark = false;
                }
            }
            if (mark) return;//数组是有序的,则结束其他排序
        }
    }
优化二:

加入border,记录上一次排序中,最后一次的交换元素的下标border,border之后的元素是排好序的。

	/**
     * 第二次优化: border
     * 记录上次排序最后一次交换的位置,作为下一次排序,元素比较交换的结束位置。
     * 如果上一次排序的最后交换位置,是最后一个元素,则就是冒泡排序算法,
     * 如果是最后位置,是前几个,则证明前几个后的元素是排好序的,无需在排,可以跳过。
     */
    public void sort4(int[] arr) { 
       for (int i = 0, len = arr.length; i < len - 1; i++) {
            int border = 0;//每次是0,如果元素有序了,则border就是0,i=len-1,排序结束
            for (int j = 0; j < len - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    border = j + 1;//只需要记住,border记录的是最后一次的数值交换
                }
            }
            i = len - border - 1; //i的值,是数组尾部有多少排好的元素
        }
    }
优化三(鸡尾酒排序):

实现一次遍历出现一个最大值和一个最小值。它的改进在于同时的冒泡两边,从低到高,然后从高到低,相当于顺便把最小的数也冒泡到最前面这个方法比冒泡更加高效一点:

/**
 * 鸡尾酒排序:
 * 实现一次遍历出现一个最大值,一个最小值
 * <p>
 * (可以使用加入标记进行优化)
 */
public void sort5(int[] arr) {
    int L = 0, R = arr.length - 1;
    while (L < R) {//L=R时,表示只剩一个元素没有排序,这一个元素也是有序的
        
        for (int i = L; i < R; i++) if (arr[i] > arr[i + 1]) swap(arr,i,i + 1);
        R--; //记录末尾尚未排序的下标
        
        for (int i = R; i > L; i--) if (arr[i] < arr[i - 1]) swap(arr, i, i - 1); 
        L++;//记录开头尚未排序元素的下标
    }
}

2.选择排序

将数值中的每一个元素,与其后面的所有元素比较,选出最小值。

  • 最好:O(n²)
  • 最坏:O(n²)
  • 平均:O(n²)

算法思想:

  • 在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;
算法:
	public void sort(int[] arr) {
        for (int i = 0, len = arr.length; i < len; i++) {
            int minIndex = i;//记录最小值下标,和arr[i]交换位置
            for (int j = i + 1; j < len; j++) {
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;//更新最小值元素下标
            }
            swap(arr, i, minIndex);//交换位置
        }
    }

3.插入排序

将数值中的每个元素,插入到有序数组的合适位置,有序数组用原数组的第一个元素表示。

  • 最好:O(n),原数组已经是升序的。
  • 最坏:O(n²)
  • 平均:O(n²)

算法思想:

  • 让元素数组的第一个元素,看成一个有序数组
  • 让1~n-1 元素下标,每个都插入到有序数组的合适位置

案例:

​ 比如数组是2 1 5 7 6 3 ,2作为有序数组,第一次key为1,将1与有序数组比较,如果有序数组元素大于key,则将该元素后移一位,即arr[j+1]=arr[j],覆盖的数组中元素是key的值,key已经存储了;如果不大于,则让arr[j+1]=key,即让key插入有序数组,数组任然有序,这就是插入排序。

移位法
	
	public void sort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int key = arr[i], j;//要插入的元素
            for (j = i - 1; j >= 0 && arr[j] > key; j--) {
                arr[j + 1] = arr[j];
            }
            arr[j + 1] = key;
			//两种写法,一个含义
            /*j = i-1;
            while(j>=0&&arr[j]>key){
                arr[j+1] = arr[j];
                j--;
            }
            arr[j+1] = key;*/
        }     
    }
交换法

类似于冒泡排序,在新的待排序数,进行一次冒泡排序

 //交换法
    public void sort2(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j + 1);
            }
        }
        printArr(arr);
    }

4.希尔排序

希尔排序的思想是进行分组排序,通过分组,通过在分组内,将每个分组的序列排序成有序的,然后逐渐缩小分组的个数,也就是步数。在每个分组内,进行的是插入排序,这样做的好处是,减少了数组中大量的移动操作。可以认为,希尔排序是插入排序的增强。

  • 最好:O(n),本来数组就是有序的递增
  • 最坏:O(n²)
  • 平均:O(n^1-3)
	static int[] arr = {8, 9, 1, 7, 2, 3, 4, 6, 0};

	//9个数进行排序
    //希尔排序的过程,用交换法
    public static void main(String[] bs) {
        System.out.println(Arrays.toString(arr));
       //用到的是插入排序,要尽到第二个元素的位置,所以i的值,就是每组数的第二个元素的下标,最小的那个
        //进行第一轮排序 9/2 = 4
        for (int i = 4; i < arr.length; i++) {
            for (int j = i - 4; j >= 0 && arr[j] > arr[j + 4]; j -= 4) {
                swap(arr, j, j + 4);
            }
        }
        System.out.println(Arrays.toString(arr));

        //进行第二轮排序 4/2 = 2,i的值变成了第一个分组中的第二个元素,这一次采用移位法
        for (int i = 2; i < arr.length; i++) {
            int key = arr[i], j;
            for (j = i - 2; j >= 0 && arr[j] > key; j -= 2)//用前一个元素,和后一个比较,如果前一个大,后移
                arr[j + 2] = arr[j];
            arr[j + 2] = key;//合适位置插入
        }
        System.out.println(Arrays.toString(arr));


        for (int i = 1; i < arr.length; i++) {
            int key = arr[i], j;
            for (j = i - 1; j >= 0 && arr[j] > key; j--)
                arr[j + 1] = arr[j];
            arr[j + 1] = key;
        }
        System.out.println(Arrays.toString(arr));
    }


    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
算法:
	//基于交换法的希尔排序
    public void sort(int[] arr) {
        //需要进行几次步数排序,每次步数是gap
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {//在跨步数之间,进行增量排序
                for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap)
                    swap(arr, j, j + gap);// 如果当前元素大于加上步长后的那个元素,说明交换
            }
        }
    }

    //基于移位法的希尔排序
    public void sort2(int[] arr) {
        //需要进行几次步数排序,每次步数是gap
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {//在跨步数之间,进行增量排序
                int key = arr[i], j;
                for (j = i - gap; j >= 0 && arr[j] > key; j -= gap)
                    arr[j + gap] = arr[j];
                arr[j + gap] = key;
            }
        }
    }

5.快速排序

算法:

选择一个元素作为基数(通常是第一个元素),把比基数小的元素放到它左边,比基数大的元素放到它右边(相当于二分),再不断递归基数左右两边的序列。

  • 最好:O(n * logn),所有数均匀分布在基数的两边,此时的递归就是不断地二分左右序列。

  • 最坏:O(n²) ,所有数都分布在基数的一边,此时划分左右序列就相当于是插入排序。

  • 平均:O(n * logn)

  • /**
        * 快速排序经典算法:(交换)
        * 从左右两边向中间推进的时候,遇到不符合的数就两边交换值。
        * <p>
        * 左右设置两个哨兵,右哨兵移动,找到小于key的元素,然后左哨兵移动,
        * 最后确定哨兵的位置。
        */
       public void sort(int[] arr) {
           if (arr == null || arr.length == 0)
               return;
           quickSort(0, arr.length - 1, arr);
       }
     
       //[5,1,1,2,0,0]
       public void quickSort(int begin, int end, int[] arr) {
     
           if (begin >= end)
               return;
     
           int l = begin;
           int r = end;
           int key = arr[begin];
           while (l < r) {//将元素分类存放,以key大小比较
               //顺序很重要,从右面开始找
               while (l < r && arr[r] >= key)
                   r--;
               while (l < r && arr[l] <= key)
                   l++;
               if (l < r) {//表示存在不相符的元素
                   int t = arr[l];
                   arr[l] = arr[r];
                   arr[r] = t;
               }
           }
           //交换基数,让key归位
           arr[begin] = arr[l];
           arr[l] = key;
     
           quickSort(begin, l - 1, arr);
           quickSort(l + 1, end, arr);
       }
    

6.归并排序

​ 归并排序的思想是先进行递归拆分,将数组分割成元素个数为1的子数组,这样子数组是有序的,然后进行左右 两边的合并。

算法:
/**
 * 归并排序
 */
public class MegerSort {
 
    @Test
    public void testSort() {
        // 创建要给80000个的随机的数组
        for (int k = 0; k <= 10; k++) {
            int n = k;
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = (int) (Math.random() * 100); // 生成一个[0, 8000000) 数
            }
            System.out.println(Arrays.toString(arr));
            int[] temp = new int[k];
            new MegerSort().sort(arr, 0, arr.length - 1, temp);

            System.out.println(Arrays.toString(arr));
        }
    }

    /**
     * 归并排序,先将数据进行拆分,然后进行两个有序数组的合并,归并思想
     *
     * @param arr   数组
     * @param left  数组起始索引
     * @param right 数组结束索引
     * @param temp  临时数组
     */
    public void sort(int[] arr, int left, int right, int[] temp) {
        //先进行查分,查分成左右只有一个元素的数组
        if (left < right) {
            int mid = (left + right) / 2;
            //继续进行拆分,拆分左边的
            sort(arr, left, mid, temp);
            //查分右边
            sort(arr, mid + 1, right, temp);

            //当两边只有一个元素了,进行合并
            meger(arr, left, mid, right, temp);
        }

    }

    /**
     * 合并两个有序数组,arr[left~mid] 和 arr[mid+1~right]
     *
     * @param arr
     * @param left
     * @param mid
     * @param right
     * @param temp
     */
    public void meger(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int t = 0;//临时数组存储合并后的元素
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[t++] = arr[i++];
            } else {
                temp[t++] = arr[j++];
            }
        }

        //剩余元素
        while (i <= mid) {
            temp[t++] = arr[i++];
        }
        while (j <= right) {
            temp[t++] = arr[j++];
        }

        //拷贝进原数组
        t = 0;
        while (left <= right) {
            arr[left++] = temp[t++];
        }
    }
}

7.基数排序

基数排序是将每个元素的数值进行查分,将拆分的个位数,放置到合适的位桶数组中,查分完毕后,按照位桶数组顺序进行进行组合,次数是最大值是几位数。

/**
 * 基数排序:
 */
public class RadixSort {

    /**
     * 基数排序是桶排序变换而来,他将元素的每个位数的数字,进行桶归位,
     * 直达最高位数归位结束,之后顺序输出就是结果;
     *
     * @param arr
     */
    public static void sort(int[] arr) {

        //定义位桶,和位桶的计数器
        int[][] bucket = new int[10][arr.length];//记录存在桶中的数据
        int[] bucketCount = new int[10];//记录每个位桶中的有效数字个数
        int maxEle = 3;//循环次数,最大值的位数
        int div = 1;//求个位,十位的除数
        for (int i = 0; i < maxEle; i++, div *= 10) {//根据最大值的位数,决定循环次数
            //1. 放置元素
            for (int j = 0; j < arr.length; j++) {//将每个元素按照位数入桶
                int digitOfElement = (arr[j] / div) % 10;
                //arr[j]根据余数放置到相同位桶中
                bucket[digitOfElement][bucketCount[digitOfElement]] = arr[j];
                bucketCount[digitOfElement]++;//计数器增加
            }

            //2.顺序取出元素,放入数组
            int index = 0;
            for (int k = 0; k < bucket.length; k++) {
                if (bucketCount[k] > 0) {//桶中存有元素
                    for (int l = 0; l < bucketCount[k]; l++) {
                        arr[index++] = bucket[k][l];
                    }
                    bucketCount[k] = 0;
                }
            }
            System.out.println(Arrays.toString(arr));
        }
    }

    public static void main(String[] args) {
        int arr[] = {53, 3, 542, 748, 14, 214};
        sort(arr);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值