8大排序算法分析和总结

排序算法平均时间复杂度
冒泡排序O(n2)
选择排序O(n2)
插入排序O(n2)
希尔排序O(n1.5)
快速排序O(N*logN)
归并排序O(N*logN)
堆排序O(N*logN)
基数排序O(d(n+r))

8大排序算法分析和总结

代码如下

     
    /**
     * <h1>冒泡排序</h1> 
     * <b>基本思想:两个数比较大小,较大的数下沉,较小的数冒起来。
     * 大小为n的数组,从后向前相邻的数两两比较,最小数不断被交换提前,最小数被交换到起始的位置,
     * 每次循环比较完起始位置+1,直到起始位置为n-1。
     */
    public static void bubbleSort(int[] a) {
        int l = a.length;
        for (int i = 0; i < l - 1; i++) { // 表示趟数,一共arr.length-1次。
            for (int j = l - 1; j > i; j--) {
                if (a[j] < a[j - 1]) {
                    a[j] ^= a[j - 1];
                    a[j - 1] ^= a[j];
                    a[j] ^= a[j - 1];
                }
            }
        }

    }

    // 冒泡排序的2种思想
    // 冒泡 从下往上冒泡
    public static void bubbleSort(int[] a) {
        int l = a.length;
        for (int i = 0; i < l - 1; i++) { // 表示趟数,一共arr.length-1次。
            for (int j = l - 1; j > i; j--) {
                if (a[j] < a[j - 1]) {
                    a[j] ^= a[j - 1];
                    a[j - 1] ^= a[j];
                    a[j] ^= a[j - 1];
                }
            }
        }

    }

    // 冒泡 从上往下沉
    public static void bubbleSort2(int[] a) {
        int l = a.length;
        for (int i = 0; i < l- 1; i++) {
            // 从上往下沉
            for (int j = 0; j < l - 1 - i; j++) {
                if (a[j] > a[j + 1]) {
                    a[j] ^= a[j + 1];
                    a[j + 1] ^= a[j];
                    a[j] ^= a[j + 1];
                }
            }
        }
    }


    /**
     * <h1>选择排序</h1> 
     * <b>基本思想: 在长度为N的无序数组中,
        第1次遍历n-1个数,找到最小的数值与第1个元素交换; 
        第2次遍历n-2个数,找到最小的数值与第2个元素交换;
        ...
        第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。
     */
    public static void selectSort(int[] a) {
        for (int i = 0; i < a.length - 1; i++) { // 表示趟数,一共arr.length-1次。
            int min = i;
            for (int j = i + 1; j > a.length; j++) {
                if (a[j] < a[min]) {
                    min = j;
                }
            }
            if (min != i) {
                a[i] ^= a[min];
                a[min] ^= a[i];
                a[i] ^= a[min];
            }
        }
    }

    // 选择排序排序优化,将最大值和最小值对称排序
    public static void selectSort2(int[] arr) {
        int temp;
        for (int i = 0; i < arr.length / 2; i++) {
            int minIndex = i;
            int maxIndex = arr.length - 1 - i;
            for (int j = i + 1; j < arr.length - i; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
                if (arr[j] > arr[maxIndex]) {
                    maxIndex = j;
                }
            }
            if (minIndex != i) {
                temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
            if (maxIndex != (arr.length - 1 - i)) {
                temp = arr[(arr.length - 1 - i)];
                arr[(arr.length - 1 - i)] = arr[maxIndex];
                arr[maxIndex] = temp;
            }
        }

    }
    
    /**
     * <h3>插入排序</h3> 
     * <b>基本思想: 在要排序的一组数中,
    假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,
    使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
     */
    public static void insertSort(int[] arr) {
        int temp;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = i + 1; j > 0; j--) {
                if (arr[j] < arr[j - 1]) {
                    temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                } else {
                    break;
                }
            }
        }
    }

    /**
     * 希尔排序
     * <b>基本思想:使用希尔增量+插入排序
     */
    public static void shellSort(int[] arr) {
        for (int gap = a.length / 2; gap > 0; gap /= 2) {
            
            for (int i = 0; i < a.length - gap; i ++) {
                for (int j = i + gap; j >= gap; j -= gap) {
                    if (a[j] < a[j - gap]) {
                        exc(a, j, j - gap);
                    } else {
                        break;
                    }
                }
            }
        }
    }

    /**
     * <h3>快速排序</h3> 
     * <b>基本思想:先从数列中取出一个数作为key值;
        将比这个数小的数全部放在它的左边,
        大于或等于它的数全部放在它的右边;
        对左右两个小数列重复第二步,直至各区间只有1个数。
     * 挖坑填数+分治
     */
    public static void quickSort(int[] data, int start, int end) {
        if (data == null || start >= end)
            return;
        int i = start, j = end;
        int pivotKey = data[start];
        while (i < j) {
            while (i < j && data[j] >= pivotKey)// 从右向左找第一个小于key的值
                j--;
            // 挖坑填数
            if (i < j)
                data[i++] = data[j];
            
            while (i < j && data[i] <= pivotKey)// 从左向右找第一个大于key的值
                i++;
            // 挖坑填数
            if (i < j)
                data[j--] = data[i];
        }
        // 循环退出时,i等于j,将第首次的中位数填到这个坑中。
        data[i] = pivotKey;
        // 分治
        quickSort(data, start, i - 1);
        quickSort(data, i + 1, end);
    }
    
    /**
     * 快速排序  方式2
     * 关键点:给定一组数列,随机抽取一个数pivot作为中间数,要求中间数左边的数小于中间数,中间数右边的数大于中间数
     *
     * @param arr
     * @param left
     * @param right
     */
    public static void quickSort2(int[] arr, int left, int right) {
        if (left >= right) return;
        int l = left;
        int r = right;
        int pivot = arr[right];
        while (l < r) {
            while (arr[l] < pivot) l++;// 从右找到大于pivot 的 索引值,没有就 ++
            while (arr[r] > pivot) r--;// 从左找到小于pivot 的 索引值,没有就 ++

            if (l < r) {// 当进行最后一次循环时,l > r,防止交换
                // 交换
                if (arr[l] != arr[r]) { // 值相等时,异或的值为0,交换出错
                    arr[l] ^= arr[r];
                    arr[r] ^= arr[l];
                    arr[l] ^= arr[r];
                }
                //System.out.printf("%s %d %d %d\n", Arrays.toString(arr), l, r, pivot);
                if (arr[l] == pivot && arr[r] == pivot) { // 当pivot重复时候,防止无限循环
                    l++;
                }
            }
        }
        quickSort(arr, left, l - 1);
        quickSort(arr, l + 1, right);

        //System.out.printf("l=%d,r=%d \n", l, r);
    }

    /**
     * <h3>归并排序</h3> 
     * <b>基本思想:首先考虑下如何将2个有序数列合并。
        这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,
        取了后就在对应数列中删除这个数。然后再进行比较,如果有数
        列为空,那直接将另一个数列的数据依次取出即可。
     * 
     * 采用分治法的一个非常典型的应用
     */
    public static void mergeSort(int[] data, int start, int end) {
        if(start >= end)return;
        int mid = (start + end)/2;
        mergeSort(data,start,mid);
        mergeSort(data,mid+1,end);
        // 分治法思想
        mergeArray(data,start,mid,end);
    }
    
    public static void mergeArray(int[] data, int start, int mid, int end) {
        // 声明临时数组用于赋值
        int[] temp = new int[(end-start+1)];
        int i = start, m = mid;
        int j = mid + 1, n = end;

        int k = 0;
        while (i <= m && j <= n) {
            if (data[i] <= data[j]) {
                temp[k++] = data[i++];
            } else {
                temp[k++] = data[j++];
            }
        }
        while (i <= m)
            temp[k++] = data[i++];

        while (j <= n)
            temp[k++] = data[j++];
        // 最后将临时数组的值复制到给定数组的指定位置
        for (int s = 0; s < k; s++) {
            data[start + s] = temp[s];
        }
    }

    /**
     * <h3堆排序</h3> 
     * <b>基本思想:
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
在一个堆中,位置i的结点的父元素的位置是(i+1)/2-1,而它的两个子节点的位置分别是2i+1和2i+2,
这样我们就可以通过计算数组的索引在树中上下移动。
那么我们 进行堆排序, 应该怎么做呢?首先,我们得构建一个堆(大顶堆)。
构建的思路就是:我们将小的元素下沉(sink())即可。
     */
    public static void heapSort(int[] arr) {
        int n = arr.length;
        // 1.构建大顶堆
        // 位置n-1的结点的父元素的位置是(n-1 +1)/2-1
        for (int i = (n - 1) / 2; i >= 0; i--) {
            sink(arr, i, n - 1);
        }
        // 2.进行堆排序
        /* 将对顶的元素移动到最后使得末尾的元素最大,
         * 然后我们继续调用sink函数,又可以使得堆顶的元素最大(实则为总的第二大),
         * 然后继续重复以前的操作即可。*/
        for (int i = n - 1; i >= 1; i--) {
            // 把堆顶元素与最后一个元素交换
            exc(arr, 0, i);
            // 把打乱的堆进行调整,恢复堆的特性
            sink(arr, 0, i - 1);
        }
    }
    // 下沉操作,n为给定的最后截止索引
    public static void sink(int[] arr, int parent, int n) {
        // 临时保存要下沉的元素,用于挖坑填数
        //int temp = arr[parent];
        // 定位左孩子节点的位置2i+1,开始下沉
        int child;
        while ((child = 2 * parent + 1) <= n) {
            // 如果右孩子节点比左孩子大,则定位到右孩子
            if (child + 1 <= n && arr[child] < arr[child + 1])
                child++;
            // 如果孩子节点小于或等于父节点,则下沉结束
            // if (arr[child] <= temp)
            if (arr[child] <= arr[parent])
                break;
            // 父节点进行下沉,挖坑填数
            // arr[parent] = arr[child];
            exc(arr,child,parent);
            // 子节点变成父节点
            parent = child;
        }
        // arr[parent] = temp;
    }
    
    // 交换数组2个索引的值
    public static void exc(int a[],int i,int j){
        // 当他们相等的时候就没必要进行交换
        if(a[i] != a[j]){
            a[i] ^= a[j];
            a[j] ^= a[i];
            a[i] ^= a[j];
        }
    }
    
    /**
     * <h3基数排序</h3> 
     * <b>基本思想:
     先以个位数的大小来对数据进行排序,接着以十位数的大小来多数进行排序,
     接着以百位数的大小……排到最后,就是一组有序的元素了。不过,他在以某位数进行排序的时候,
     是用“桶”来排序的。由于某位数(个位/十位….,不是一整个数)的大小范围为0-9,
     所以我们需要10个桶,然后把具有相同数值的数放进同一个桶里,
     之后再把桶里的数按照0号桶到9号桶的顺序取出来,
     这样一趟下来,按照某位数的排序就完成了
     */
    public static void radixSort(int[] a) {
        // temp:临时数组
        // n:序列的数字个数
        // k:最大的位数2
        // r:基数10
        // cnt:存储bin[i]的个数
        int n = a.length;
        int temp[] = new int[n];
        int r = 10;
        int k = 0;
        // 获取最大位数
        for (int i = 0; i < n; i++) {
            int length = (a[i] + "").length();
            if (length > k)
                k = length;
        }
        for (int i = 0, rtok = 1; i < k; i++, rtok = rtok * r) {
            // 初始化10个箱子
            int[] cnt = new int[r];
            // 计算每个箱子记录的数字个数
            // 计算a[j]在箱子的位置,并在该位置上+1
            for (int j = 0; j < n; j++) {
                cnt[(a[j] / rtok) % r]++;
            }
            // cnt[j]的个数修改为记录前j个箱子共有几个数字
            // 目的:计算a[j]在temp的临时数组位置
            for (int j = 1; j < r; j++) {
                cnt[j] += cnt[j - 1];
            }
            /* 重点理解:cnt[j]保存了前j个箱子的数字个数和,
             * 所以从a[n-1]开始计算,定位箱子得值减1即可获取保存在temp的位置
             */
            for (int j = n - 1; j >= 0; j--) { 
                // 计算a[j]在temp的临时数组位置
                cnt[(a[j] / rtok) % r]--;
                temp[cnt[(a[j] / rtok) % r]] = a[j];
            }
            for (int j = 0; j < n; j++) {
                a[j] = temp[j];
            }
        }
    }
    


    public static void main(String[] args) {
        int[] arr = new Random().ints(10, 0, 100).toArray();
        System.out.println(Arrays.toString(arr));
        quickSort(arr,0,arr.length -1);
        System.out.println(Arrays.toString(arr));

        // 不使用其他变量,交换a,b的值异或方式
        /*int a = 14;
        int b = 5;
        System.out.println(a+","+b);
        a=a^b;
        b=a^b;
        a=a^b;
        System.out.println(a+","+b);
        */
    }

**有一个交换位置的函数exc,不使用临时变量,而是使用异或^=

    /**
     * 交换a数组中i和j的位置
     * @param a 需要交换的数组
     * @param i 位置
     * @param j 位置
     */
    public static void exc(int a[],int i,int j){
        // 当他们相等的时候就没必要进行交换
        if(a[i] != a[j]){
            a[i] ^= a[j];
            a[j] ^= a[i];
            a[i] ^= a[j];
        }
    }

---对算法优化的理解:要想写出最优的程序就需要理解计算机运行程序的过程,代码的每1行都会加载到内存再到CPU运算(包括寻址取值、逻辑计算),那么从减少变量声明和尽可能的复用、减少循环体代码、优化循环判断逻辑和循环运算逻辑、递归的尾调用优化,等等都需要考虑在内,以“挖坑填数”为例,使用循环体外的变量复用使代码运行的最佳。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值