java实现十大经典排序算法

前言

排序算法在编程生涯中虽然用的不多,但是作为基本功,还是要掌握一下。

冒泡排序(Bubble Sort)

冒泡排序(Bubble Sort)
是一种交换排序基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
在最好的情况下,也就是数列本身是排好序的,需要进行 n - 1 次比较;
在最坏的情况下,也就是数列本身是逆序的,需要进行 n(n-1)/2 次比较。因此冒泡排序总的时间复杂度是 O(n^2)。

 public static void bubbleSort(List<Integer> list) {
        //外面循环每次找出最小的数(数列本身是排好序的,需要进行 n - 1 次比较)
        for (int i = 0; i < list.size(); i++) {
            //内循环找出第i小的元素
            for (int j = i + 1; j < list.size(); j++) {
                if (list.get(j).compareTo(list.get(i)) < 0) {
                    int tmp = list.get(i);
                    list.set(i, list.get(j));
                    list.set(j, tmp);
                }
            }
        }
    }

选择排序(Selection Sort)

基本思想是每一趟在n - i + 1 (i = 1,2,***,n - 1)个记录中选取关键字最小(或最大)的记录作为有序序列的 第 i 个记录,
直到所有元素排序完成。选择排序是不稳定的排序算法。
选择排序的时间复杂度为 O(n^2)。

   public static void selectionSort(List<Integer> list) {

        for (int i = 0; i < list.size(); i++) {
            int min = i;
            for (int j = i + 1; j < list.size(); j++) {
                if (Integer.compare(list.get(min), list.get(j)) == 1) {
                    min = j;
                }
            }
            if (i != min) {
                int tmp = list.get(min);
                list.set(min, list.get(i));
                list.set(i, tmp);
            }
        }
    }

插入排序(Insertion Sort)

将一个记录插入到已经排好序的有序数列中,从而得到一个有序但记录数加一的有序数列。
插入排序的时间复杂度为 O(n^2),是稳定的排序方法,适用于数量较少的排序。

 public static void insertionSort(List<Integer> list) {
     for (int i = 1; i < list.size(); i++) {
         for (int j = i; j > 0; j--) {
             int tmp;
             if (list.get(j).compareTo(list.get(j - 1)) < 0) {
                 tmp = list.get(j);
                 list.set(j, list.get(j - 1));
                 list.set(j - 1, tmp);
             } else {
                 break;
             }
         }
     }
 }

鸡尾酒排序(Cocktail Sort)

鸡尾酒排序是冒泡排序的一种变形。
先找到最小的数字,放在第一位,再找到最大的数字放在最后一位。
然后再找到第二小的数字放到第二位,
再找到第二大的数字放到倒数第二位。以此类推,直到完成排序。
鸡尾酒排序的时间复杂度为 O(n^2)。

 public static void cocktailSort(List<Integer> list) {
     for (int i = 0; i < (list.size() >> 1); i++) {
         //先找到最小的数字,放在第一位
         for (int j = i + 1; j < list.size(); j++) {
             if (list.get(i).compareTo(list.get(j)) > 0) {
                 int tmp = list.get(i);
                 list.set(i, list.get(j));
                 list.set(j, tmp);
             }
         }
         //再找到最大的数字放在最后一位
         for (int l = i + 1; l < list.size() - i; l++) {
             int maxIndex = list.size() - i - 1;
             if (list.get(l).compareTo(list.get(maxIndex)) > 0) {
                 int tmp = list.get(l);
                 list.set(l, list.get(maxIndex));
                 list.set(maxIndex, tmp);
             }
         }
     }
 }

希尔排序(Shell Sort)

希尔排序(Shell Sort)是插入排序的一种,是针对直接插入排序算法的改进。
基本思想是将相距某个增量 d 的记录组成一个子序列,通过插入排序使得这个子序列基本有序,然后 减少增量继续排序。
操作上先取一个小于 n 的整数 d1 作为第一个增量,把全部记录分成 d1 个组,所有距离为 dl 的倍数的记录放在同一个组中。
先在各组内进行直接插人排序,然后取第二个增量d2 < d1 重复上述的分组和排序,
直至所取的增量 dt = 1 (dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
希尔排序的时间复杂度可以达到 O(n^(3/2)),要好于前面几种算法。

    public static void shellSort(List<Integer> list) {
        int j;
        //分层 list.size() >> 1 组,每组单独排序 第二次循环向量再次缩小2倍
        for (int gap = list.size() >> 1; gap > 0; gap = gap >> 1) {
            for (int i = gap; i < list.size(); i++) {
                Integer tmp = list.get(i);
                for (j = i; j >= gap && tmp.compareTo(list.get(j - gap)) < 0; j -= gap) {
                    list.set(j, list.get(j - gap));
                }
                list.set(j, tmp);
            }
        }
    }

梳排序(Comb Sort)

梳排序和希尔排序很类似。
希尔排序是在直接插入排序的基础上做的优化,而梳排序是在冒泡排序的基础上做的优化,
也就是将相距某个增量 d 的记录组成一个子序列,通过冒泡排序使得这个子序列基本有序,然后减少 增量继续排序。
梳排序的时间复杂度是 O(nlogn)。

    public static void combSort(List<Integer> list) {
        int n = list.size();
        int step = n;
        int k;
        //将数组长度/1.3得到本次的gap;   当step=1时,相当于最后进行了一次冒泡排序
        while ((step /= 1.3) >= 1) {
            for (int i = n - 1; i >= step; i--) {
                k = i - step;
                //如果前者大于后者,则进行交换
                if (list.get(k) > list.get(i)) {
                    int tmp = list.get(k);
                    list.set(k, list.get(i));
                    list.set(i, tmp);
                }
            }
        }
    }

归并排序(Merge Sort)

归并排序(MERGE-SORT) 是一种分治算法,是建立在归并操作上的一种有效的排序算法。
常用的 2 路归并排序假设初始序列有 n 个记录,可以看成是 n 个长度为 1 的子序列,进行两两归并,可以得到 n / 2 个长度为 2 或 1 的子序列;再两两归并,直到得到一个长度为 n 的有序序列为止。
归并排序的时间复杂度是 O(nlogn),是一种效率高且稳定的算法。

   private static void mergeSort(List<Integer> list, int left, int right, int[] temp) {
       if (left < right) {
           int mid = (left + right) >> 1;
           //左边归并排序,使得左子序列有序
           mergeSort(list, left, mid, temp);
           //右边归并排序,使得右子序列有序 右边第一个是左边的最后一个+1
           mergeSort(list, mid + 1, right, temp);
           //左序列指针
           int i = left;
           //右序列指针
           int j = mid + 1;
           //临时数组指针
           int t = 0;
           while (i <= mid && j <= right) {
               if (list.get(i).compareTo(list.get(j)) <= -1) {
                   temp[t++] = list.get(i++);
               } else {
                   temp[t++] = list.get(j++);
               }
           }
           //将左边剩余元素填充进temp中
           while (i <= mid) {
               temp[t++] = list.get(i++);
           }
           //将右序列剩余元素填充进temp中
           while (j <= right) {
               temp[t++] = list.get(j++);
           }
           t = 0;
           //将temp中的元素全部拷贝到原数组中
           while (left <= right) {
               list.set(left++, temp[t++]);
           }
       }
   }

快速排序(Quick Sort)

快速排序(Quicksort)是对冒泡排序的一种改进。
基本思想是通过一趟排序将待排记录分割成独立的两部分,
其中一部分的记录都比另一部分小,然后再分别对这两个部分进行快速排序,最终实现整个序列的排序。
快速排序的时间复杂度为 O(nlogn),是一种不稳定的排序算法

 public static void quickSort(int[] arr, int left, int right) {
     if (left < right) {
         // 设定基准值(pivot)
         int index = left + 1;
         for (int i = index; i <= right; i++) {
             if (arr[i] < arr[left]) {
                 swap(arr, i, index);
                 index++;
             }
         }
         swap(arr, left, index - 1);
         int partitionIndex = index - 1;
         quickSort(arr, left, partitionIndex - 1);
         quickSort(arr, partitionIndex + 1, right);
     }
 }


  /**
   * 交换元素
   *
   * @param arr
   * @param a
   * @param b
   */
 public static void swap(int[] arr, int a, int b) {
     int temp = arr[a];
     arr[a] = arr[b];
     arr[b] = temp;
 }

堆排序(Heap Sort)

堆排序(Heap sort)是指利用堆这种数据结构所设计的一种排序算法。
堆是具有下列性质的完全二叉树:
1. 每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;
2. 每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
基本思想是把待排序的序列构造成一个大顶堆,此时序列的最大值就是队顶元素,把该元素放在最后,然后对剩下的 n - 1 个元素继续构造大顶堆,直到排序完成。
堆排序的时间复杂度为 O(nlogn),由于要构造堆,因此不适用于序列个数较少的情况.

    public static void heapSort(int[] arr) {
        //1.构建大顶堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr, i, arr.length);
        }
        //2.调整堆结构+交换堆顶元素与末尾元素
        for (int j = arr.length - 1; j > 0; j--) {
            //将堆顶元素与末尾元素进行交换
            swap(arr, 0, j);
            //重新对堆进行调整
            adjustHeap(arr, 0, j);
        }
    }

    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     *
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int[] arr, int i, int length) {
        //先取出当前元素i
        int temp = arr[i];
        //从i结点的左子结点开始,也就是2i+1处开始
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
            //如果左子结点小于右子结点,k指向右子结点
            if (k + 1 < length && arr[k] < arr[k + 1]) {
                k++;
            }
            //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
            if (arr[k] > temp) {
                arr[i] = arr[k];
                i = k;
            } else {
                break;
            }
        }
        //将temp值放到最终的位置
        arr[i] = temp;
    }

    /**
     * 交换元素
     *
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

基数排序(Radix Sort)

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

  public static int[] sort(int[] sourceArray)  {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }

    /**
     * 获取最高位数
     */
    private static int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    private static int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    protected static int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }

    private  static  int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                int bucket = ((arr[j] % mod) / dev) + mod;
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private static int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }

总结

感谢您的阅读

如果你发现了错误的地方,可以在留言区提出来,我对其加以修改

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值