排序算法在日常的开发中十分常见。常见的七种排序算法有:直接插入排序、希尔排序、简单选择排序、堆排序、冒泡排序、快速排序、归并排序。
还有另外三种排序算法:基数排序、计数排序、桶排序。
它们的特性如下:
目录
首先我们讨论常见的七种排序算法:
直接选择排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 每次选择一个元素,并且将这个元素和整个数组中的所有元素进行比较,然后插入到合适的位置,
- * 时间复杂度:平均: O(n^2),最好:O(n),最坏:O(n^2),辅助空间:O(1),稳定
- */
- public class 插入排序 {
- public void insert_sort(int[] nums) {
- int i, j;
- int length = nums.length;
- for (i = 1; i < length; i++) {
- int temp = nums[i];
- for (j = i; j > 0 && nums[j - 1] > temp; j--) {
- nums[j] = nums[j - 1];
- }
- nums[j] = temp;
- }
- }
- public static void main(String[] args) {
- 插入排序 t1 = new 插入排序();
- int[] arr = {52,5,8,6,2,5,4,2};
- System.out.println(Arrays.toString(arr));
- t1.insert_sort(arr);
- System.out.println(Arrays.toString(arr));
- }
-
}
希尔排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 插入排序的修改版,根据步长由长到短分组,进行排序,直到步长为1为止,属于插入排序的一种。
- * 时间复杂度:平均:O(nlogn)-O(n^2),最好:O(n^1.3),最坏:O(n^2),辅助空间:O(1),不稳定
- */
- public class 希尔排序 {
- public void shellSort(int[] nums) {
- int length = nums.length;
- for (int gap = length / 2; gap > 0; gap /= 2) {
- for (int i = gap; i < length; i++) {
- int temp = nums[i];
- int j;
- for (j = i; j >= gap && nums[j - gap] > temp; j -= gap) {
- nums[j] = nums[j - gap];
- }
- nums[j] = temp;
- }
- }
- }
- public static void main(String[] args) {
- 希尔排序 t1 = new 希尔排序();
- int[] nums = {5, 5, 2, 9, 3, 2, 3, 4, 8};
- System.out.println(Arrays.toString(nums));
- t1.shellSort(nums);
- System.out.println(Arrays.toString(nums));
- }
- }
简单选择排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 通过 n-i 次关键字间的比较,从 n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换之
- * 时间复杂度:平均:O(n^2),最好:O(n^2),最坏:O(n^2),辅助空间:O(1),稳定
- */
- public class 简单选择排序 {
- private void selectSort(int[] nums) {
- int i, j, min;
- int length = nums.length;
- for (i = 0; i < length; i++) {
- min = i;
- for (j = i + 1; j < length; j++) {
- if (nums[min] > nums[j]) {
- min = j;
- }
- }
- if (min != i) {
- swap(nums, min, i);
- }
- }
- }
- private void swap(int[] nums, int min, int i) {
- int temp = nums[min];
- nums[min] = nums[i];
- nums[i] = temp;
- }
- public static void main(String[] args) {
- 简单选择排序 t1 = new 简单选择排序();
- int[] nums = {5,96,2,3,8,4,97};
- System.out.println(Arrays.toString(nums));
- t1.selectSort(nums);
- System.out.println(Arrays.toString(nums));
- }
- }
堆排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 堆排序是一种基于二叉堆(Binary Heap)结构的排序算法,
- * 所谓二叉堆,我们通过完全二叉树来对比,只不过相比较完全二叉树而言,二叉堆的所有父节点的值都大于(或者小于)它的孩子节点
- * 首先需要引入最大堆的定义:
- * 1、最大堆中的最大元素值出现在根结点(堆顶)
- * 2、堆中每个父节点的元素值都大于等于其孩子结点
- * <p>
- * 堆排序的方法如下:把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
- *
- * 时间复杂度:平均:O(nlogn),最好:O(nlogn),最坏:O(nlogn),辅助空间:O(1),不稳定
- */
- public class 堆排序 {
- /**
- * 建立堆函数,不断构建二叉堆
- *
- * @param nums:待排序的数组
- * @param n:数组的长度
- * @param i:数组中最大值的下标
- */
- public void heapify(int[] nums, int n, int i) {
- int largest = i; //将最大元素设置为堆顶元素,假设现在nums[i]是最大元素
- int l = 2 * i + 1; //left = 2*i+1,相当于左孩子
- int r = 2 * i + 2; //right = 2*i+2,相当于右孩子
- //如果 left 比 root 大的话
- if (l < n && nums[l] > nums[largest]) {
- largest = l;
- }
- //如果 right 比 root 大的话
- if (r < n && nums[r] > nums[largest]) {
- largest = r;
- }
- if (largest != i) {
- swap(nums, i, largest);
- //递归的定义子堆
- heapify(nums, n, largest);
- }
- }
- private void swap(int[] nums, int i, int largest) {
- int temp = nums[i];
- nums[i] = nums[largest];
- nums[largest] = temp;
- }
- /**
- * 堆排序函数
- *
- * @param nums:待排序的数组
- * @param n:数组的长度
- */
- public void heapSort(int[] nums, int n) {
- //建立堆
- for (int i = n / 2 - 1; i >= 0; i--) {
- heapify(nums, n, i);
- }
- //一个个从堆顶取出元素
- for (int i = n - 1; i >= 0; i--) {
- //将最大值放到最后一位,相当于把根节点与数组最后一位元素交换
- swap(nums, 0, i);
- //每次改变最大值的位置后,要重新构建二叉堆
- heapify(nums, i, 0);
- }
- }
- public static void main(String[] args) {
- 堆排序 t1 = new 堆排序();
- int[] nums = {5, 9, 2, 6, 8, 4, 6, 2, 9};
- System.out.println(Arrays.toString(nums));
- t1.heapSort(nums, nums.length);
- System.out.println(Arrays.toString(nums));
- }
- }
冒泡排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 每次选择两个元素,按照需求进行交换(比如需要升序排列的话,把较大的元素放在靠后一些的位置),
- * 循环 n 次(n 为总元素个数),这样小的元素会不断 “冒泡” 到前面来
- * 时间复杂度:平均:O(n^2),最好:O(n),最坏:O(n^2),辅助空间:O(1),稳定
- */
- public class 冒泡排序 {
- public void bubbleSort(int[] nums) {
- int length = nums.length;
- boolean swap = true;
- while (swap) {
- swap = false;
- for (int i = 0; i < length - 1; i++) {
- if (nums[i] > nums[i + 1]) {
- nums[i] += nums[i + 1];
- nums[i + 1] = nums[i] - nums[i + 1];//这里已经将nums[i]的值赋给nums[i+1]
- nums[i] -= nums[i + 1];//减去之后nums[i]的值已经变为原来的nums[i+1]
- swap = true;
- }
- }
- }
- }
- public static void main(String[] args) {
- 冒泡排序 t1 = new 冒泡排序();
- int[] nums = {5, 6, 7, 6, 9, 4, 1, 7, 5};
- System.out.println(Arrays.toString(nums));
- t1.bubbleSort(nums);
- System.out.println(Arrays.toString(nums));
- }
- }
快速排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- *简称快排,时间复杂度并不固定,如果在最坏情况下(元素刚好是反向的)速度比较慢,达到 O(n^2)(和选择排序一个效率),但是如果在比较理想的情况下时间复杂度 O(nlogn)。
- *
- * 快排也是一个分治的算法,快排算法每次选择一个元素并且将整个数组以那个元素分为两部分,根据实现算法的不同,元素的选择一般有如下几种:
- *
- * 永远选择第一个元素
- * 永远选择最后一个元素
- * 随机选择元素
- * 取中间值
- * 整个快速排序的核心是分区(partition),分区的目的是传入一个数组和选定的一个元素,把所有小于那个元素的其他元素放在左边,大于的放在右边。
- *
- * 其思路是每次从最左元素中选择一个元素并且将小于等于那个元素的元素的下标标记为 i ,在整个遍历过程中,如果我们找到一个更加小的元素,我们就把这
- * 个元素和数组中第 i 个元素交换
- */
- public class 快速排序 {
- /**
- * 排序函数
- *
- * @param nums:待排序的数组
- * @param low:低位
- * @param high:高位
- */
- public void quickSort(int[] nums, int low, int high) {
- int pivot;
- if (low < high) {
- pivot = partition(nums, low, high);
- quickSort(nums, low, pivot - 1);
- quickSort(nums, pivot + 1, high);
- }
- }
- /**
- * 分区函数
- * @param nums:
- * @param low:
- * @param high:
- * @return
- */
- public int partition(int[] nums, int low, int high) {
- int pivotkey;
- pivotkey = nums[low]; //用子表的第一个记录作为枢轴记录
- while (low < high) { //从表的两端交替向中间扫描
- while (low < high && nums[high] >= pivotkey) {
- high--;
- }
- swap(nums, low, high); //将比枢轴记录小的记录交换到低端
- while (low < high && nums[low] <= pivotkey) {
- low++;
- }
- swap(nums, low, high); //将比枢轴记录大的记录交换到高端
- }
- return low; //返回枢轴所在位置
- }
- private void swap(int[] nums, int i, int largest) {
- int temp = nums[i];
- nums[i] = nums[largest];
- nums[largest] = temp;
- }
- public static void main(String[] args) {
- 快速排序 t1 = new 快速排序();
- int[] nums = {5,7,6,2,3,98,6,21};
- System.out.println(Arrays.toString(nums));
- t1.quickSort(nums,0,nums.length-1);
- System.out.println(Arrays.toString(nums));
- }
- }
归并排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 归并排序相比较之前的排序算法而言加入了分治法的思想,其算法思路如下:
- * <p>
- * 1.如果给的数组只有一个元素的话,直接返回(也就是递归到最底层的一个情况)
- * <p>
- * 2.把整个数组分为尽可能相等的两个部分(分)
- * <p>
- * 3.对于两个被分开的两个部分进行整个归并排序(治)
- * <p>
- * 4.把两个被分开且排好序的数组拼接在一起
- *
- * 时间复杂度:平均:O(nlogn),最好:O(nlogn),最坏:O(nlogn),辅助空间:O(n),稳定
- */
- public class 归并排序 {
- public void merge(int[] nums, int l, int m, int r) {
- int i, j, k;
- int n1 = m - l + 1;
- int n2 = r - m;
- int[] L = new int[n1];
- int[] R = new int[n2];
- for (int n = 0; n < n1; n++) {
- L[n] = nums[l + n];
- }
- for (int n = 0; n < n2; n++) {
- R[n] = nums[m + 1 + n];
- }
- i = 0;
- j = 0;
- k = l;
- while (i < n1 && j < n2) {
- if (L[i] <= R[j]) {
- nums[k] = L[i];
- i++;
- } else {
- nums[k] = R[j];
- j++;
- }
- k++;
- }
- while (i < n1) {
- nums[k] = L[i];
- i++;
- k++;
- }
- while (j < n2) {
- nums[k] = R[j];
- j++;
- k++;
- }
- }
- public void mergeSort(int[] nums, int l, int r) {
- if (l < r) {
- int m = l + (r - l) / 2;
- mergeSort(nums, l, m);
- mergeSort(nums, m + 1, r);
- merge(nums, l, m, r);
- }
- }
- public static void main(String[] args) {
- 归并排序 t1 = new 归并排序();
- int[] nums = {1, 5, 7, 6, 3, 8, 9, 5, 2, 7, 6};
- int length = nums.length;
- System.out.println(Arrays.toString(nums));
- t1.mergeSort(nums,0,length-1);
- System.out.println(Arrays.toString(nums));
- }
- }
另外三种排序算法Java实现如下:
基数排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。
- * 排序过程是将所有待比较数值统一为同样的数位长度,数位较短的数前面补零,然后从最低位开始,依次进行一次排序。
- * 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
- */
- public class 基数排序 {
- private int getMax(int[] arr) {
- int max = arr[0];
- int length = arr.length;
- for (int i = 1; i < length; i++) {
- if (arr[i] > max) {
- max = arr[i];
- }
- }
- return max;
- }
- private void countSort(int[] arr, int n, int exp) {
- int output[] = new int[n];
- int i;
- int[] count = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- for (int j = 0; j < n; j++) {
- count[(arr[j] / exp) % 10]++;
- }
- for (int j = 1; j < 10; j++) {
- count[j] += count[j - 1];
- }
- for (int j = n - 1; j >= 0; j--) {
- output[count[(arr[j] / exp) % 10] - 1] = arr[j];
- count[(arr[j] / exp) % 10]--;
- }
- for (int j = 0; j < n; j++) {
- arr[j] = output[j];
- }
- }
- private void radixSort(int[] arr, int n) {
- int m = getMax(arr);
- for (int exp = 1; m / exp > 0; exp *= 10) {
- countSort(arr, n, exp);
- }
- }
- public static void main(String[] args) {
- 基数排序 t1 = new 基数排序();
- int[] arr = {6, 8, 6, 1, 3, 7, 9, 2, 3, 55, 23};
- System.out.println(Arrays.toString(arr));
- int length = arr.length;
- t1.radixSort(arr, length);
- System.out.println(Arrays.toString(arr));
- }
- }
计数排序:
- package 排序算法;
- /**
- * 计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数
- * (此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。
- * 一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。
- * 例如,如果输入序列中只有17个元素的值小于x 的值,则 x 可以直接存放在输出序列的第18个位置上。
- * 当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,
- * 优化后的计数排序算法如下:时间复杂度为Ο(n+k)(其中k是整数的范围),稳定
- */
- public class 计数排序 {
- public static int[] countSort(int[] a) {
- int b[] = new int[a.length];
- int max = a[0], min = a[0];
- for (int i : a) {
- if (i > max) {
- max = i;
- }
- if (i < min) {
- min = i;
- }
- }
- //这里k的大小是要排序的数组中,元素大小的极值差+1
- int k = max - min + 1;
- int c[] = new int[k];
- for (int i = 0; i < a.length; ++i) {
- c[a[i] - min] += 1;//优化过的地方,减小了数组c的大小
- }
- for (int i = 1; i < c.length; ++i) {
- c[i] = c[i] + c[i - 1];
- }
- for (int i = a.length - 1; i >= 0; --i) {
- b[--c[a[i] - min]] = a[i];//按存取的方式取出c的元素
- }
- return b;
- }
- public static void main(String[] args) {
- //排序的数组
- int a[] = {100, 93, 97, 92, 96, 99, 92, 89, 93, 97, 90, 94, 92, 95};
- int b[] = countSort(a);
- for (int i : b) {
- System.out.print(i + " ");
- }
- System.out.println();
- }
- }
桶排序:
- package 排序算法;
- import java.util.Arrays;
- /**
- * 桶排序的思路:首先找到数组中的最大值,然后新建一个数组,bucket 此数组的长度是数组最大值+1,其实新建的这个数组中的下标值就是原数组的数据值,这里为什么长度是数组最大值加一呢
- * 注意*:是因为比如数组最大值是9,然后如果你设置bucket数组的长度为9,那么他的下标最大值就是8,那么原数组的9 就没有桶存了
- * 好了继续,找到最大值后,开始遍历原数组,把原数组的数据加入bucket的下表中,bucket[i],每当有1个i bucket[i]的值就加一,
- * 然后已经装入桶后, 遍历桶,如果bucket[j]位置-->0就说明此下标有数据,也就是说,此下标在原数组里有这个值, 然后排序 就是从大到小了 arr[i++]=j;
- *
- * 时间复杂度:O(n),辅助空间:O(n),不稳定
- *
- */
- public class 桶排序 {
- public void bucketSort(int[] arr) {
- int length = arr.length;
- int max = Integer.MIN_VALUE;
- for (int i = 0; i < length; i++) {
- max = Math.max(max, arr[i]);
- }
- int[] bucket = new int[max + 1];
- for (int i = 0; i < length; i++) {
- bucket[arr[i]]++;
- }
- int i = 0;
- for (int j = 0; j < bucket.length; j++) {
- while (bucket[j]-- > 0) {
- arr[i++] = j;
- }
- }
- }
- public static void main(String[] args) {
- 桶排序 t1 = new 桶排序();
- int[] nums = {5, 6, 87, 9, 3, 1};
- System.out.println(Arrays.toString(nums));
- t1.bucketSort(nums);
- System.out.println(Arrays.toString(nums));
- }
- }
以上就是对这十大排序算法的概括。相比之下,从最好情况看,反而冒泡和直接插入排序要更胜一筹,也就是说,如果你的待排序序列总是基本有序,优先考虑这两种排序算法。
从最坏情况看,堆排序与归并排序又强过快速排序以及其他简单排序。
从待排序记录的个数上来说,待排序的个数n越小,采用简单排序方法(如冒泡排序、简单选择排序、直接插入排序)越合适。反之,n越大,采用改进排序方法越合适。所以在使用快速排序等复杂排序算法时,可以增加一个阈值,低于阈值时换做简单排序算法可提高效率。