当前位置:   article > 正文

(四)排序算法(Java实现)_java选择排序法代码

java选择排序法代码

本篇博客中总结了以下经典排序算法:

冒泡排序、快速排序、简单插入排序、希尔排序、简单选择排序、堆排序、二路归并排序、计数排序、桶排序、基数排序。

经典的排序算法分为下面两大类:

非线性时间比较类排序:通过比较来决定元素间的相对次序。时间复杂度不能突破O(nlogn)而得此名称。

线性时间非比较类排序:不能通过比较来决定元素间的相对次序。它可以突破比较排序的时间下限,以线性时间运行而得此称。

 


目录

一、交换排序

1.1、冒泡排序

1.2、快速排序

二、插入排序

2.1简单插入排序

2.2、希尔(shell)排序

三、选择排序

3.1、简单选择排序

3.2、堆排序

四、归并排序

4.1、二路归并排序

4.2、多路归并排序

五、线性时间非比较类排序

5.1、计数排序

5.2、桶排序

5.3、基数排序


 

一、交换排序

1.1、冒泡排序

   在深入学习更多排序算法后和在实际使用情况中,冒泡排序的使用还是极少的。它适合数据规模很小的时候,而且它的效率也比较低,但是作为入门的排序算法,还是值得学习的。

    冒泡排序是一种简单的排序算法。它重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法步骤描述:

①比较两个相邻的元素。如果第一个比第二个大,就交换它们两个;

②对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;

③针对所有的元素重复以上的步骤,除了最后一个;

④重复步骤①~③,直到排序完成。

 

代码实现:

  1. package com.review08.sort;
  2. public class BubbleSort {
  3. public static void bubblesort(int arr[]) {
  4. int temp = 0;
  5. for(int i=0; i<arr.length-1; i++) { //外层循环控制排序的趟数
  6. for(int j=0; j<arr.length-1-i; j++) { //内层循环控制比较次数
  7. if(arr[j]>arr[j+1]) { //相邻两个数比较,前面的数大就交换
  8. temp = arr[j];
  9. arr[j] = arr[j+1];
  10. arr[j+1] = temp;
  11. }
  12. }
  13. System.out.println("\n第"+(i+1)+"趟:");
  14. printarr(arr);
  15. }
  16. }
  17. public static void printarr(int [] arr) {
  18. for (int i=0; i<arr.length; i++) {
  19. System.out.print(arr[i] +" ");
  20. }
  21. }
  22. public static void main(String[] args) {
  23. int[] arr = {9,8,7,6,5,4,3,2,1};
  24. System.out.println("排序前:");
  25. printarr(arr);
  26. bubblesort(arr);
  27. }
  28. }

 

选择排序方法时,冒泡排序显然不够理想,看看下面改良后的快速排序。

 

 

1.2、快速排序

    所谓快速排序:基于分治的思想,是冒泡排序的改进型。首先在数组中选择一个基准点并把基准点放于序列的开头,这里就基准点就选取数组的第一个元素; 然后分别从数组的两端扫描数组,设两个指示标志(i 指向起始位置,j 指向末尾),首先从后半部分开始,如果发现有元素比该基准点的值小,就交换 i 和 j 位置的值,然后从前半部分开始扫秒,发现有元素大于基准点的值,就交换 i 和 j 位置的值,如此往复循环,直到 i >= j ,然后把基准点的值放到 i 这个位置,一次排序就完成了;之后再采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组自然也就有序了。

看下面的例子:给5,8,3,6,7,9,2用快速排序法排序

第一趟:

快速排序代码实现(用到了递归哦!):

  1. package com.review08.sort;
  2. import java.lang.reflect.Array;
  3. public class QuickSort {
  4. /**寻找所标记数的每一趟排序后的最终位置(数组下标)
  5. * @param arr 要排序的数组
  6. * @param s 开始位置
  7. * @param e 结束位置
  8. * @return i 中间位置
  9. */
  10. public static int sort(int arr[], int s, int e) { //寻找中间位置
  11. int mark = arr[s];
  12. int i = s;
  13. int j = e;
  14. while(i<j) { //直到i、j相遇
  15. while (i < j) { //j负责找小的,扔给i
  16. if(arr[j] < mark) {
  17. arr[i] = arr[j];
  18. break; //扔给j后,退出里面的while循环,让i走
  19. }
  20. j--; //所在位置比mark的值大,就继续向前走
  21. }
  22. while (i < j) {
  23. if(arr[i] >= mark) { //i负责找大的,扔给j
  24. arr[j] = arr[i];
  25. break; //扔给i后,退出里面的while循环,让i走
  26. }
  27. i++; //所在位置比mark的值小,就继续向前走
  28. }
  29. }
  30. arr[i] = mark; //把标记的那个数放在i位置上
  31. return i;//最后i所在位置就是中间位置(i所在位置的前面的数都比它小,后面的都比它大)
  32. }
  33. public static void quicksort(int arr[], int s, int e) { //这里用到了递归算法!
  34. if(s<e) {
  35. int index = sort(arr, s, e); //中间那个位置
  36. quicksort(arr, s, index-1); //中间位置的左边
  37. quicksort(arr, index+1, e); //中间位置的右边
  38. }
  39. }
  40. public static void printarr(int arr[]) { //打印数组
  41. for (int i=0; i<arr.length; i++) {
  42. System.out.print(arr[i]+" ");
  43. }
  44. }
  45. public static void main(String[] args) {
  46. int[] arr = {6,7,5,4,8,9,1,2};
  47. sort(arr, 0, arr.length-1);
  48. System.out.println("第一趟排序结果:");
  49. printarr(arr);
  50. quicksort(arr, 0, arr.length-1);
  51. System.out.println("\n最终排序结果:");
  52. printarr(arr);
  53. }
  54. }

 

 

 

二、插入排序

2.1简单插入排序

 

把表分成两部分,前半部分已排序,后半部分未排序,后半部分元素依次一个一个插入前半部分适当的位置。

假设线性中前 j-1元素已经有序,现在要将线性表中第 j 个元素插入到前面的有序子表中,插入过程如下:

将第 j 个元素放到一个变量T中,然后从有序子表的最后一个元素(即线性表中第 j-1个元素)开始,往前逐个与T进行比较,将大于T的元素均依次向后移动一个位置,直到发现一个元素不大于T为止,此时就将T(即原线性表中的第 j 个元素)插入到刚移出的空位置上,有序子表的长度就变为 j 了。效率与冒泡排序法相同。

 

例:15,45,84,48,79,10,35,34,88

(15) |   45    84    48     79    10    35    34    88   

(15    45) |   84    48     79    10    35    34    88   

(15    45     84) |  48     79    10    35    34    88   

(15    45     48    84) |   79    10    35    34    88   

(15    45     48    79     84) |  10    35    34    88   

(10    15     45    48     79    84) |  35    34    88   

(10    15     35    45     48    79    84) 34    88   

(10    15     34    35     45    48    79    84) |  88  

(10    15     34    35     45    48    79    84    88) 

 

代码实现:

  1. package com.review08.sort;
  2. public class SimpleInsertSort {
  3. public static void simpleinsert(int[] arr) {
  4. if(arr.length<1) {
  5. return;
  6. }
  7. for(int i=1; i<arr.length; i++) {//注意,i从1开始,我们是从arr[1]开始与前面的元素比较的
  8. //因为0~i-1是有序的,如果arr[i]>arr[i-1],则0~i也是有序的
  9. if(arr[i]<arr[i-1]) {
  10. int temp = arr[i];//先保存i位置元素的值
  11. int j = i-1;//从i-1开始向前找,一直找到比i位置元素小的位置,然后插入
  12. for(; j>=0&&arr[j]>temp; j--) {
  13. arr[j+1] = arr[j];//没有找到就将此位置的元素向后移一位,腾出位置
  14. }
  15. arr[j+1] = temp;//将i位置元素放在腾出的位置上面
  16. }
  17. }
  18. }
  19. public static void main(String[] args) {
  20. int[] arr = {41,12,8,65,32,75,11,29};
  21. System.out.println("简单插入排序前:");
  22. printArr(arr);
  23. simpleinsert(arr);
  24. System.out.println("\n简单插入排序后:");
  25. printArr(arr);
  26. }
  27. public static void printArr(int[] arr) {
  28. for(int i=0; i<arr.length; i++) {
  29. System.out.print(arr[i]+" ");
  30. }
  31. }
  32. }

 

 

 

2.2、希尔(shell)排序

 希尔排序严格来说是基于插入排序的思想,又被称为缩小增量排序。

 具体流程如下:

 1、将包含n个元素的数组,分成d=n/2个数组序列,第一个数据和第n/2+1个数据为一对...

 2、对每对数据进行比较和交换,排好顺序;

 3、然后分成d=n/4个数组序列,再次排序;

 4、不断重复以上过程,随着d减少并直至为1,排序完成。

下面举个例:

原始序列:18,35,94,47,61,28,17,98,共8个数

代码实现

  1. public class ShellSort {
  2. //法一:
  3. public static void shellSort1(int[] arr) {
  4. int i,j,r,temp;
  5. r = arr.length/2;
  6. for( ; r>=1; r=r/2) { //划分组,间隔为r
  7. for(i=r;i<arr.length;i++) {
  8. temp = arr[i];
  9. j = i - r;
  10. //一轮排序
  11. while(j >=0 && temp <arr[j]) {
  12. arr[j+r] = arr[j];
  13. j -= r;
  14. }
  15. arr[j+r] = temp;
  16. }
  17. }
  18. }
  19. //法二:
  20. public static void shellSort2(int[] arr) {
  21. if(arr == null && arr.length <= 1) {
  22. return;
  23. }
  24. int increment = arr.length/2;//增量
  25. while(increment >= 1) {
  26. for(int i=0; i<arr.length-increment; i++) {
  27. for(int j=i; j<arr.length-increment; j=j+increment) {
  28. if(arr[j]>arr[j+increment]) {
  29. int temp = arr[j];
  30. arr[j] = arr[j+increment];
  31. arr[j+increment] = temp;
  32. }
  33. }
  34. }
  35. increment = increment/2; //设置新的增量
  36. }
  37. }
  38. }

 

 

三、选择排序

3.1、简单选择排序

基本思想(数组长度为n):

第一次遍历n-1个数,找到最小的数值与第一个元素交换; 

第二次遍历n-2个数,找到最小的数值与第二个元素交换;

...

第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成

看下面的例子:

代码实现简单交换排序

思路:第一个层循环遍历数组,第二层循环找到剩余元素中最小值的索引,内层循环结束,交换数据。 内层循环每结束一次,排好一位数据。两层循环结束,数据排好有序。

  1. public static void simpleswap(int[] arr) {
  2. for(int i=0; i<arr.length; i++) { //负责遍历索引
  3. int index =i ;//index负责找最小的索引
  4. for(int j=i+1; j<arr.length; j++) { //负责找剩余元素中的最小值
  5. if(arr[j]<arr[index]) {
  6. index = j;
  7. }
  8. }
  9. //每找到一次就与前面的交换一次
  10. if(i != index){
  11. int temp = arr[index];
  12. arr[index] = arr[i];
  13. arr[i] = temp;
  14. }
  15. }
  16. }

 

3.2、堆排序

下一篇文章二叉树里讲的是小根堆实现的堆排序,大根堆实现原理也差不多!

    堆是一棵顺序存储的完全二叉树。完全二叉树中所有非终端节点的值均不大于(或不小于)其左、右孩子节点的值。

    其中每个结点的值小于等于其左、右孩子的值,这样的堆称为小根堆; 其中每个结点的值大于等于其左、右孩子的值,这样的堆称为大根堆。

 

堆排序的大概步骤如下:

  1. 构建大根堆;
  2. 选择顶,并与第0位置元素交换;
  3. 由于步骤2的的交换可能破环了大根堆的性质,第0不再是最大元素,需要调用maxHeap调整堆(沉降法),如果需要重复步骤2。

    堆排序中最重要的算法就是maxHeap,该函数假设一个元素的两个子结点都满足最大堆的性质(左右子树都是最大堆),只有跟元素可能违反最大堆性质,那么把该元素以及左右子结点的最大元素找出来,如果该元素已经最大,那么整棵树都是最大堆,程序退出,否则交换跟元素与最大元素的位置,继续调用maxHeap原最大元素所在的子树。该算法是分治法的典型应用。

 

 

四、归并排序

    归并排序 (merge sort) 是一类与插入排序、交换排序、选择排序不同的另一种排序方法。归并的含义是将两个或两个以上的有序表合并成一个新的有序表。归并排序有多路归并排序、两路归并排序 , 可用于内排序,也可以用于外排序。这里仅仅写到内排序的归并方法!

4.1、二路归并排序

 

代码实现二路归并排序:

  1. package com.review08.sort;
  2. public class TwoWaysMergeSort {
  3. private static int number = 0;
  4. public static void main(String[] args) {
  5. int[] arr = {10,15,33,8,48,11,95,87,38};
  6. printArr("排序前:",arr);
  7. twomergesort(arr);
  8. printArr("\n排序后:",arr);
  9. }
  10. public static void printArr(String pre, int[] a) {
  11. System.out.print(pre);
  12. for (int i = 0; i < a.length; i++) {
  13. System.out.print(a[i] + " ");
  14. }
  15. }
  16. private static void twomergesort(int[] arr) {
  17. System.out.println("\n**************开始排序**************");
  18. sort(arr, 0, arr.length-1);
  19. }
  20. private static void sort(int[] arr, int left, int right) {
  21. if (left >= right) {
  22. return;
  23. }
  24. int middle = (left + right) / 2;
  25. //二路归并里面有两个sort,多路归并里面有多个sort
  26. sort(arr, left, middle);
  27. sort(arr, middle + 1, right);
  28. merge(arr, left, middle, right);
  29. }
  30. private static void merge(int[] arr, int left, int middle, int right) {
  31. int[] temp = new int[arr.length];
  32. int r1 = middle + 1;
  33. int left1 = left;
  34. int left2 = left;
  35. //逐个归并
  36. while (left <= middle && r1 <= right) {
  37. if (arr[left] <= arr[r1]) {
  38. temp[left1++] = arr[left++];
  39. } else {
  40. temp[left1++] = arr[r1++];
  41. }
  42. }
  43. //将左边剩余的归并
  44. while (left <= middle) {
  45. temp[left1++] = arr[left++];
  46. }
  47. //将右边剩余的归并
  48. while (r1 <= right) {
  49. temp[left1++] = arr[r1++];
  50. }
  51. System.out.print("第"+(++number)+"趟排序:");
  52. //从临时数组拷贝到原数组
  53. while(left2 <= right) {
  54. arr[left2] = temp[left2];
  55. //输出中间归并排序结果
  56. System.out.print(arr[left2]+"\t ");
  57. left2++;
  58. }
  59. System.out.println();
  60. }
  61. }

 

 

 

 

 

4.2、多路归并排序

多路归并排序涉及到的情况较为复杂,这里略过!

————————————————————————————————————————————————

    常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。

    计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

——————————————————————————————————————————————————

 

五、线性时间非比较类排序

5.1、计数排序

    计数排序需要占用大量的空间,仅仅只适用要比较的数据范围很集中的情况,比如0~1000,10000~20000,...这样在一定范围的数据。

需要三个数组:
待排序数组 int[] before= new int[]{4,3,6,3,5,1};
辅助计数数组 int[] arr= new int[max - min + 1]; //该数组大小为待排序数组中的最大值减最小值+1
输出数组 int[] res = new int[before.length];

1.求出待排序数组before的最大值max=6, 最小值min=1
2.实例化辅助计数数组arr,arr数组中每个下标对应before中的一个元素,arr用来记录每个元素出现的次数
3.计算 before中每个元素在arr中的位置 position = before[i] - min,此时 arr= [1,0,2,1,1,1]; (3出现了两次,2未出现)
4.根据 arr数组求得排序后的数组,此时 res = [1,3,3,4,5,6]

例:

原始数列:105    110    105    102    111    107    108    104    110    105

 

Java代码实现计数排序:

  1. package com.review08.sort;
  2. public class CountSort {
  3. public static int[] countSort(int[] before) {
  4. //找出数组中的最大值和最小值
  5. int max = Integer.MIN_VALUE;
  6. int min = Integer.MAX_VALUE;
  7. for(int i=0; i<before.length; i++) {
  8. max = Math.max(max,before[i]);
  9. min = Math.min(min,before[i]);
  10. }
  11. int[] arr = new int[max-min+1]; //记录出现次数的数组
  12. //找出每个数字出现的次数
  13. for (int i=0; i<before.length; i++) {
  14. int index = before[i]-min;
  15. arr[index] ++;
  16. }
  17. //计算每个数字在排序后数组中应该出现的位置
  18. for(int i=1; i<arr.length; i++) {
  19. arr[i] = arr[i] +arr[i-1];
  20. }
  21. //根据arr数组排序
  22. int res[] = new int[before.length];
  23. for(int i=0; i<before.length; i++) {
  24. int index = --arr[before[i]-min];
  25. res[index] = before[i];
  26. }
  27. return res;
  28. }
  29.     //测试
  30. public static void main(String[] args) {
  31. int [] before = {105,110,105,102,111,107,108,104,110,105,105};
  32. printArr(countSort(before));
  33. }
  34.     //打印数组元素
  35. public static void printArr(int[]arr) {
  36. for(int i=0; i<arr.length; i++) {
  37. System.out.print(arr[i] + " ");
  38. }
  39. }
  40. }

 

 

 

5.2、桶排序

    桶排序与计数排序恰恰相反,桶排序可用于最大值与最小值相差较大的情况,比如[106,1902,47867,83556,102456]。不过计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。

    桶排序要求数据的分布必须均匀,否则可能导致数据都集中到一个桶中。比如[114,170,127,132,20000], 这种数据会导致前4个数都集中到同一个桶中。导致桶排序失效。

桶排序步骤:

1.找出待排序数组中的最大值max、最小值min
2.我们使用动态数组ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为(max-min)/arr.length+1
3.遍历数组 arr,计算每个元素 arr[i] 放的桶
4.每个桶各自排序
5.遍历桶数组,把排序好的元素放进输出数组

简言之,就是把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并。

 

代码后期更新!

 

 

5.3、基数排序

    思想:把待排序的整数按位分,分为个位,十位.....从小到大依次将位数进行排序。实际上分为两个过程:分配和收集。 分配就是:从个位开始,按位数从小到大把数据排好,分别放进0--9这10个桶中; 收集就是:依次将0-9桶中的数据放进数组中,重复这两个过程直到最高位。

例:

代码实现:

  1. package com.review08.sort;
  2. import java.util.Arrays;
  3. public class RadixSort {
  4. public static void main(String[] args) {
  5. int a[] = {400,52,61,84,96,195,26};
  6. radixSort(a,10,3);
  7. for(int i=0; i<a.length; i++) {
  8. System.out.print(a[i] + " ");
  9. }
  10. }
  11. /**
  12. * 基数排序
  13. * @param arr 待排序数组
  14. * @param radix 基数(10,桶的个数)
  15. * @param distance 待排序中,最大的位数
  16. */
  17. public static void radixSort(int[] arr, int radix, int distance) {
  18. int length = arr.length;
  19. int[] temp = new int[length]; //用于暂存元素
  20. int[] count = new int[radix]; //用于计数排序的桶
  21. int divide = 1;//除数,用于取个位数数字、十位数数字、百位数数字...
  22. for(int i=0; i<distance; i++) {
  23. System.arraycopy(arr,0,temp,0,length);
  24. Arrays.fill(count,0); //桶清空
  25. for(int j=0; j<length; j++) { //这个循环用于吧每个数的个十百千分开,并使相对应号数的桶的个数增加1
  26. //divide:1 10 100 radix:10
  27. int tempKey = (temp[j]/divide) % radix;
  28. count[tempKey] ++;
  29. }
  30. for (int j=1; j<radix; j++) {
  31. count[j] = count[j] + count[j-1];
  32. }
  33. for(int j=length-1; j>=0; j--) {
  34. int tempKey = (temp[j]/divide) % radix;
  35. count[tempKey] --;
  36. arr[count[tempKey]] = temp[j];
  37. }
  38. divide = divide * radix; //1 10 100
  39. }
  40. }
  41. }

 


补充

最后附上最近看马士兵老师讲的视频中的对排序写的一首宋词。

画红圈圈的四种排序算法是一定要掌握的!时间复杂度、空间复杂度和稳定性(参看马士兵老师的宋词)也是要掌握的!

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/909946
推荐阅读
相关标签
  

闽ICP备14008679号