java常用算法之排序算法

12 篇文章 0 订阅
本篇介绍常用排序算法的java实现
借用网上一幅图,排序算法分类如下:

对于各个算法的时间复杂度和空间复杂度,借用网上的图如下:

其中:选择排序算法本身是稳定的,用顺序结构实现时是不稳定的,用链表实现时是稳定的。
         快速排序是平均速度最快的排序算法。
         归并排序需要的辅助空间最多。堆排序需要的辅助空间最少。
所谓不稳定,简单举个例子就是,如果待排序的数组里,如果出现两个相同的数,比如5(1),5(2),排好序后如果顺序变成5(2)、5(1),那么就是不稳定的。
直接插入排序 
假设已经有一组排好序的数组(可以是1个),然后有待加入的一堆数n个,现在把这n个数有序的插入到已经排好序的数组里,一个一个进行,与前面排好序的数进行比较,找到合适的位置插入,如此反复循环,最终形成的数组就是排好序的。
如:23,45,67,12,97,4,32,55,初始选择23作为已经排好序的,而后面剩余的数就是待插入的,接着是43(假设我们升序排序,下面的算法也都是),与23比较,大于23,所以不需要改变位置,接着67,比45大,由于前面的是排好序的,所以一样不需要改变位置,此时:23 45 67  12 97 4 32 55前面3个已经排好序了。接着是12,与67比较,比67小,67位置后移,或者说12往前移动位置,再继续与45比较,仍然比45小,继续前移,再与23比较,还是比23小,继续前移,已经是最后一个数了,所以12位置就定下来了,就是首位。接着是97...如此循环反复,最终形成有序数组。
比较过程:
23   45 67 12 97 4 32 55
23 45   67 12 97 4 32 55
23 45 67   12 97 4 32 55
12 23 45 67   97 4 32 55
12 23 45 67 97   4 32 55
 4 12 23 45 67 97   32 55
 4 12 23 32 45 67 97  55
 4 12 23 32 45 55 67 97
代码示例如下:
     /**
      * 直接插入排序
      * @param nums
      */
     public static void insertSort(int[] nums){
           for(int i = 1;i<nums.length;i++){
                int tmp = nums[i];
                int j = i - 1;
                for(;j >= 0 && tmp < nums[j];j--){
                     nums[j+1] = nums[j];
                }
                nums[j+1] = tmp;
           }
     }
希尔排序(最小增量排序)
先将待排序数组按某个增量(一般为n/2,n是数组个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
比如对于23,45,67,12,97,4,32,55,n=8 d=8/2=4,具体步骤如下:
23 45 67 12 97 4 32 55      (同颜色为一组,同组的进行比较并排序)
23 4 32 12 97 45 67 55     (这时d = d/2 = 2)
23 4 32 12 97 45 67 55      (同颜色为一组,同组的进行比较并排序)
23 4 32 12 67 45 97 55     (这时d = d/2 = 1)
4 23 12 32 45 67 55 97     (此时只需要按正常排序进行,但是由于先前的排序已经排好了大部分,基本就是左右进行比较就可以了)
代码示例如下:
 /**
      * 希尔排序
      * @param nums
      */
     public static void shellSort(int[] nums){
           int d = nums.length;
           while(d > 1){
                d = (d+1)/2;
                for(int z = 0;z < d;z++){
                     for(int i = z + d;i < nums.length; i+=d ){
                           int tmp = nums[i];
                           int j = i -d;
                           for(; j >= 0 && tmp < nums[j];j-=d){
                                nums[j+d] = nums[j];
                           }
                           nums[j+d] = tmp;
                     }
                }
           }
     }
选择排序
待排序的数组里,选择第一个数,之后依次与后面的数进行比较,如果比这个数小,就与其交换,当与后面的数比较完后,第一个数就会是整个数组最小的。接着选择数组第二个数,依次与后面进行比较,比它小的就继续交换位置,最后第二个数会是数组第二小的数。如此循环反复,最后形成有序数组。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
23 45 67 12 97 4 32 55
4 45 67 23 97 12 32 55   (23 先与12交换数值,得到12,再与4交换数值 得到目前这个排序 下面类似)
4 12 67 45 97 23 32 55
4 12 23 45 97 67 32 55
4 12 23 45 32 67 97 55
4 12 23 45 32 55 97 55
4 12 23 45 32 55 67 97  
4 12 23 45 32 55 67 97  (最后剩一个数就不用比较了)
代码示例如下:
/**
      * 选择排序(升序)
      * @param nums
      */
     public static void selectSort(int[] nums){
           for(int i = 0;i < nums.length;i++){
                for(int j = i+1; j < nums.length;j++){
                     if(nums[i] > nums[j]){
                           int tmp = nums[i];
                           nums[i] = nums[j];
                           nums[j] = tmp;
                     }
                }
           }

     }
堆排序
堆排序是基于树形结构的选择排序算法。是对直接选择排序的有效改进 。下面这段摘自网络:

堆的定义如下:具有n个元素的序列(h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
例如对于数组23,45,67,12,97,4,32,55,可以构建的树如下:

按照大顶堆进行排序后如下:

排序的思路就是:将最大的数(这里指升序)与堆最后一个节点交换位置,然后最后一个节点就不再参与下次的排序,相当于不在堆里。剩余的数经过调整重新成为大顶堆。

如此循环反复,可以得到升序排序的数组。
示例代码如下(该算法应该还可以优化,有待改进):
/**
      * 堆排序(升序)
      * 下面三个方法
      * @param nums
      */
     public static void heapSort(int[] nums){
           int size = nums.length;
           for(int i = 0;i < size-1;i++){
                buildMaxHeap(nums,size-1-i); //建立大顶堆
                Swap(nums, 0, size-i-1); //交换第一个即最大的和堆的最后一个数
           }
     }

     public static void buildMaxHeap(int[] nums,int last){
           //注意从最后一个节点的父节点开始建立堆
           for(int  i = (last-1)/2;i >= 0;i--){
                int k = i;
                //while(k*2+1 <= last){
                     //数组 起始点为0 这里bigger为子节点的左节点
                     int bigger = 2*k+1;
                     if (bigger <= last) { //左节点存在
                           if (bigger+1 <= last) { //右节点存在
                                if (nums[bigger] < nums[bigger+1]) {
                                     bigger++; //bigger指向子节点较大的那个索引
                                }
                           }
                     }
                     //当前节点即上面说的子节点的父节点与子节点中最大的进行比较
                     if (nums[k] < nums[bigger]) {
                           //如果小 就交换
                           Swap(nums,k,bigger);
                     }
                     //break; //while其实这里可不写 相应的break可以去掉
                //}
           }
     }
冒泡排序
对未排好序的数组中的数,相邻两个依次进行比较,较大的数后移,如此每执行一轮比较,最大的(就参与比较的而言)数就移到了最后。这样经过n-1轮比较后,数组就排好序了。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
23 45 67 12 97 4 32 55   第一次:23与45比较,不改变,45与67比较,67与12比较,由于12比较小,交换,67与97比较,97与4比较,交换,97与32比             较,交换,97与55比较,交换。最后97移到了最后一个位置。
23 45 67 12 4 32 55 97 第二次97不参与排序。依次类推
23 45 12 4 32 55 67 97
23 45 12 4 32 55 67 97
23 12 4 32 45 55 67 97
23 12 4 32 45 55 67 97
12 4 23 32 45 55 67 97
4 12 23 32 45 55 67 97
4 12 23 32 45 55 67 97
代码示例如下:
/**
      * 冒泡排序(升序)
      * @param nums
      */
     public static void bubbleSort(int[] nums){

           for(int i = 0;i<nums.length;i++)
                for(int j = 0;j<nums.length-1;j++){
                     if(nums[j]>nums[j+1]){
                           int tmp = nums[j];
                           nums[j] = nums[j+1];
                           nums[j+1] = tmp;
                     }
                }

     }
快速排序
从待排序的数组里选取一个作为基准数,一般选第1个或者最后一个(当然也有选中间一个的)。然后扫描数组,对数组进行第一轮排序,将比基准数小的移到左边,比基准数大的移到右边。然后数组就按基准数分成了两个数组,分别对左右数组递归的进行相同的处理,最后得到的就是排好序的了。
比如对于23,45,67,12,97,4,32,55,选取23做基准数,45比23大,移到23后面,67比23小,移到后面(实际代码可以保持原位,索引加1即可),接着12比23小,移到23前面,,,如此下去,示意图如下(递归部分比较麻烦,就不一步一步分析了,加粗的为下一个待比较的数):
23 45 67 12 97 4 32 55
23 45 67 12 97 4 32 55
23 45 67 12 97 4 32 55
12 23 45 67 97 4 32 55
12 23 45 67 97 4 32 55
12 4 23 45 67 97 32 55
12 4 23 45 67 97 32 55
12 4 23 45 67 97 32 55
代码示例如下:
 /**
      * 以下三个方法用于快速排序
      * @param nums
      */
     public static void quickSort(int[] nums){
           _quickSort(nums, 0, nums.length-1);
     }

     public static void _quickSort(int[] nums,int low,int high){
           //这步判断必须加
           if(low < high){
                int middle = getMiddle(nums,low,high);
                _quickSort(nums, low, middle-1);
                _quickSort(nums, middle+1, high);
           }
     }
     public static int getMiddle(int[] nums,int low,int high){
           int tmp = nums[low];
           while(low < high){
                if (nums[high] >= tmp && low < high) {
                     high--;
                }
                nums[low] = nums[high];
                if (nums[low] <= tmp && low < high) {
                     low++;
                }
                nums[high] = nums[low];
           }
           nums[low] = tmp;
           return low;
     }
归并排序
先将待排序数组分成若干个数组,直到不可再分(剩2个就可以开始排序了),再将分成的数组内进行排序,然后将排好序的数组再两两进行合并成有序数组,最后形成排好序的数组。归并也用到了递归的思想。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
分组阶段:     23 45 67 12 97 4 32 55
                     23 45 67 12     97 4 32 55
                     23 45      67 12     97 4    32 55
合并阶段:     23 45      12 67    4 97     32 55
                    12 23 45 67   4 32 55 97
                     45 12 4 23 67 97 32 55
代码示例如下:
/**
      * 归并排序(升序)
      * @param nums 数组
      * @param low
      * @param high
      */
     public static void mergeSort(int[] nums,int low,int high){
           if(low < high){
                int middle = (low + high)/2; //中间点
                mergeSort(nums, low, middle); //对左边数组递归
                mergeSort(nums, middle + 1, high); //对右边数组递归
                Merge(nums, low, middle, high); //将左右已排好序的数组合并成有序数组
           }
     }
     public static void Merge(int[] nums,int low,int middle,int high){
           int[] temp = new int[nums.length];
           int mid = middle + 1; //右边数组第一个数的索引
           int k = low;
           int t = low; //用于最后复制临时数组的值覆盖原数组的索引
           while(low <= middle && mid <= high){ //有序合并两个数组
                if(nums[low] <= nums[mid]){
                     temp[k++] = nums[low++];
                }else{
                     temp[k++] = nums[mid++]; //注意这里是mid不是high
                }
           }
           //剩余部分依次进入临时数组
           while(low <= middle){
                temp[k++] = nums[low++];
           }
           while(mid <= high){
                temp[k++] = nums[mid++];
           }

           //复制回原数组 覆盖原来的顺序
           while(t <= high){
                nums[t] = temp[t++];
           }
     }
基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
23
45
67
12
97
4
32
55

按个位:

0
1
2
3
4
5
6
7
8
9


12
23
4
45

67




32


55

97



按顺序收集:12 32 23 4 45 55 67 97
(其实这里已经排好序了 但是有些情况没这么顺利,还要继续按十位 百位等的收集)
按十位:

0
1
2
3
4
5
6
7
8
9


12
23
4
45

67




32


55

97



按顺序收集:12 32 23 4 45 55 67 97
代码示例如下:
/**
      * 基数排序
      * @param nums
      */
     public static void radixSort(int[] nums){
           //获取数组中最大的数
           int max = nums[0];
           for(int i = 0;i < nums.length;i++){
                if(max < nums[i]){
                     max = nums[i];
                }
           }
           //通过最大的数得到最大位数,确定排序次数
           int t = 0;
           while(max > 0){
                max/=10;
                t++;
           }
           //初始化链表 每一个数又可能有多个值 所以list里的元素仍然为链表这里即数组集合
           List<ArrayList<Integer>> list = new ArrayList<>();
           for(int i = 0; i < 10;i++){
                ArrayList<Integer> alist = new ArrayList<>();
                list.add(alist);
           }
           //开始排序
           for(int i = 0; i < t;i++){
                for(int j = 0;j < nums.length;j++){
                     //得到第i位数
                     int n = nums[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
                     ArrayList<Integer> arrayList = list.get(n);
                     arrayList.add(nums[j]);
                     list.set(n, arrayList);
                }
                //按第i位数的大小顺序收集元素 如果有相同的 按在数组里的排序
                int count = 0; //数组索引
                for(int y = 0; y < 10;y++){
                     while(list.get(y).size() > 0){
                           ArrayList<Integer> alist = list.get(y);
                           nums[count] = alist.get(0);
                           //为保证取出数的顺序以及判断是否取完 这里remove掉取出的数据 同时为下一趟排序清空数据
                           alist.remove(0);
                           count++;
                     }
                }
           }

     }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值