当前位置: 首页 > news >正文

排序算法(java版)

一直想理解一下基本的排序算法,最近正好在搞java所以就一并了(为了便于理解,这儿都是以从小到大排序为目的)

 

冒泡排序

 也就是比较连续的两个值,如果前面一个值大于后面一个值,则交换。

时间复杂度为O(n*n),是稳定排序(稳定性意思:当两个值相同时,排序前后的这两个值的相对位置是否有交换)

注意点:第二重循环参数

    // 冒泡排序,从小到大
    private static void BubbleSort(int[] arr, int n) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n - i - 1; ++j) {
                if (arr[j] > arr[j + 1]) 
                    Swap(arr, j, j + 1);
            }
        }
    }

 

 

选择排序

就是从头到尾选择每个位置放这个位置到最后一个位置的最小值,其中我们可以标记,减少交换次数

时间复杂度为O(n*n),不稳定的排序

注意点:判断后再进行异或的交换

    // 选择排序
    private static void SelectSort(int[] arr, int n) {
        int temp;
        for (int i = 0; i < n; ++i) {
            temp = i;
            for (int j = i + 1; j < n; ++j) {
                if (arr[temp] > arr[j]) {
                    temp = j;
                }
            }
            // 异或交换时必须判断
            if (i != temp)
                Swap(arr, i, temp);
        }
    }

 

 

直接插入排序

就是从头到尾遍历每个点,每个点再从尾到头找这个值放在数组的位置

时间复杂度为O(n*n),稳定的排序

注意点:temp与谁比较

    // 直接插入排序
    private static void InsertSort(int[] arr, int n) {
        // 哨兵
        int temp;
        for (int i = 1; i < n; ++i) {
            temp = arr[i];
            int k = i - 1;
            // 注意从后向前
            for (int j = k; j >= 0 && temp < arr[j]; --j, --k) {
                arr[j + 1] = arr[j];
            }
            arr[k + 1] = temp;
        }
    }

 

 

快速排序

其实就类似分治的方法,取一个中枢元素,然后找到一个位置放这个中枢元素,保证这个位置前面的值不大于它,后面的值不小于它,接着分别递归

 最坏时间复杂度为O(n*n),平均时间复杂度为O(n*log2n),所以可以先打乱顺序。是不稳定的排序

注意点:中间两个while的顺序

    // 模拟洗牌打乱顺序
    private static void Shuffle(int[] arr, int n) {
        Random random = new Random();
        for (int i = 0; i < n; ++i) {
            int x = random.nextInt(n - 1);
            int y = random.nextInt(n - 1);
            if (x != y)
                Swap(arr, x, y);
        }
        Print(arr, n);
    }
    // 快速排序(最坏时间是n*n,所以我们首先可以打乱顺序)
    private static void quickSort(int[] arr, int left, int right) {
        // 哨兵
        int temp = arr[left];
        int i = left, j = right;

        // 出口
        if (i >= j)
            return;

        // 保证左边一定不比temp大,右边一定不比temp小
        while (i < j) {
            while (i < j && arr[j] >= temp) {
                --j;
            }
            if (i < j) {
                arr[i] = arr[j];
                ++i;
            }
            while (i < j && arr[i] <= temp) {
                ++i;
            }
            if (i < j) {
                arr[j] = arr[i];
                --j;
            }
        }
        arr[i] = temp;

        // 左右递归
        quickSort(arr, left, i - 1);
        quickSort(arr, i + 1, right);
    }

 

 

二路归并排序

其实很简单,就是从中间分成左右两个数组分别递归,在最后返回时我们保证两个数组都是有序数组再合并,可以知道使用双指针的方法可以在O(n)的

时间内合并两个有序数组,然后知道这样分下去其实是几乎完全的二叉树,高度为log2n

 时间复杂度为O(n*log2n),是稳定的排序

注意点:指针向后移动

    // 二路归并排序
    private static void Marge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int leftpos = left, rightpos = mid + 1;
        // 双指针方法
        for (int i = 0; i < right - left + 1; ++i) {
            if (rightpos > right || leftpos <= mid && arr[leftpos] <= arr[rightpos]) {
                temp[i] = arr[leftpos++];
            } else {
                temp[i] = arr[rightpos++];
            }
        }
        for (int i = 0; i < right - left + 1; ++i) {
            arr[i + left] = temp[i];
        }
    }
    private static void MargeSort(int[] arr, int left, int right) {
        if (left >= right)
            return;
        // 分成两段递归
        int mid = (left + right >> 1);
        MargeSort(arr, left, mid);
        MargeSort(arr, mid + 1, right);
        // 关键:合并两端有序数组,只需用O(n)时间做到
        Marge(arr, left, mid, right);
    }

 

 

希尔排序

极其简单的算法,简答的方法是是设置步长并每次减一半,接着使用插入排序,排的是相隔步长的元素

 时间复杂度很麻烦,但是最坏也是O(n*n),平均大概是O(n^3/2),是不稳定排序

    // 希尔排序
    private static void HillSort(int[] arr, int n) {
        // 设置步长,即保证相隔步长个元素为有序数组
        for (int step = n / 2; step > 0; step /= 2) 
            // 修改的插入排序
            for (int i = step; i < n; ++i) 
                for (int j = i; j - step >= 0; j -= step) 
                    if (arr[j - step] > arr[j]) 
                        Swap(arr, j - step, j);
    }

 

 

堆排序

为了让额外的空间复杂度为O(1),可以首先倒着使用原数组下沉的思想(就是保证了此点的子树一定是堆)建立大顶堆,

接着依次将头放到新空出来的位置,再更新堆

时间复杂度是O(n*log2n),是不稳定排序

注意点:边界值得大小

    // 堆的下沉,保证此树为堆
    private static void DeleteEmp(int[] arr, int fat, int n) {
        int lefchild;
        int temp = arr[fat];
        for (; (fat << 1 | 1) < n; fat = lefchild) {
            lefchild = fat << 1 | 1;
            if (lefchild < n - 1 && arr[lefchild] < arr[lefchild + 1])
                lefchild++;
            if (arr[lefchild] > temp) {
                arr[fat] = arr[lefchild];
            } else {
                break;
            }
        }
        arr[fat] = temp;
    }
    // 堆排序(额外的辅助空间为O(1)),聪明的方法是利用原来的空间直接建树,
    // 但是从小到大时我们需要建立大顶堆,接着删除第一个点放到数组最后一个位置,倒着插入
    private static void HeapSort(int[] arr, int n) {
        // 建堆(注意普通建堆时间复杂度为O(n),因为上浮的平均时间复杂度为O(1))
        // 保证以个点为树的都是堆,倒着来就可以保证整棵树
        for (int i = n / 2; i >= 0; --i) {
            DeleteEmp(arr, i, n);
        }
        // 堆顶放入后面,再调整堆
        for (int i = n - 1; i > 0; --i) {
            Swap(arr, 0, i);
            DeleteEmp(arr, 0, i);
        }
    }

 

 

 

 

以上都是基于比较的排序方法,接下来介绍一些非基于比较的排序算法,而且都是线性的时间复杂度

 

计数排序

就是将数字变成数组下标,这种排序主要是保证数据范围比较小。

时间复杂度为O(k),是不稳定的排序

 

 

基数排序

首先从低到高枚举的是数字的每一位,每一位根据0-9的顺序放入桶中储存,再放回原数组,注意负数,还有就是非常耗费空间

时间复杂度为O(n),是稳定的排序

    // 基数排序,时间复杂度为O(n)的排序方式,m为最大数字的位数+1
    private static void BaseSort(int[] arr, int n, int m) {
        // 假设数字范围为(0,1000000000)
        int[] order = new int[10];
        int[][] temp = new int[10][n];
        int mm = 0, k = 1;
        while (mm < m) {
            // 从最低位开始放入10个桶中
            for (int i = 0; i < n; ++i) {
                int last = arr[i] / k % 10;
                temp[last][order[last]++] = arr[i];
            }
            // 10个桶中按顺序放回数组
            int coun = 0;
            for (int i = 0; i < 10; ++i) {
                if (order[i] > 0) {
                    for (int j = 0; j < order[i]; ++j)
                        arr[coun++] = temp[i][j];
                    order[i] = 0;
                }
            }
            k *= 10;
            ++mm;
        }
    }

 

 

桶排序

将数组分到有限数量的桶子里。每个桶子再个别排序

时间复杂度为O(n),是稳定的排序

 

接着还有一些比较奇特的排序方式:

鸡尾酒排序:双向冒泡排序

梳排序:在冒泡排序下让步长不为1,而且每次步长除以1.3

奇偶排序:多核处理有优势;先将待排序数组的所有奇数位与自己身后相邻的偶数位相比较,如果前者大于后者,则进行交换,直到这一趟结束。然后将偶数位与自己身后相邻的奇数位相比较,如果前者大于后者,则进行交换,直到这一趟结束。重复

外排序:以上都是在内存中排序的算法,即可以在内存中直接寻址,而外排序则可能是输入数据在磁带上,它使用的是归并排序,但是可以是多路归并排序

树形选择排序(锦标赛排序) :对N个数字,选出最小(大)的n/2个数,再进行新一轮的比较,直到选出最小(大)的。

1.把N个数放到完全二叉树的叶子节点,两两比较,选出最小的作为根节点,且保存到数组中

2.把最小的原始值设为无穷大,从那个地方开始新一轮比较

第一次需要比较n-1次,之后都是log2(n)次

 

小结

对于一般的内部排序,基本使用的是插入排序,希尔排序或者快速排序,主要是根据输入数据的大小来确定。

注意希尔排序可以使用Sedgewick增量运行,则预计运行时间就为O(N^7/6),而快速排序找枢纽可以使用三数中值分割法。

堆排序比希尔排序要慢,但是也可以根据Floyd提出的改进算法移动一次数据来优化。

转载于:https://www.cnblogs.com/zhuanzhuruyi/p/6569227.html

相关文章:

  • 初学ArcGIS API for JavaScript
  • 倒排列表求交集算法汇总
  • BZOJ 4195: [Noi2015]程序自动分析 [并查集 离散化 | 种类并查集WA]
  • UIButton的titleLabel不同状态字体判断
  • STM32 Flash Download failed
  • H5+css从入门到精通
  • xpath与css的区别
  • PHP类与对象
  • malloc函数及用法
  • SQL基础操作指令
  • IP首部格式[转载]
  • Cisco配置VLAN+DHCP中继代理+NAT转发上网
  • 让angular-cli工程基于ExpressJS服务,为对接后台API做准备
  • 面空间数据中网格索引和四叉树索引的结合及优化的一种方案
  • Python学习(20):Python函数(4):关于函数式编程的内建函数
  • __proto__ 和 prototype的关系
  • canvas 高仿 Apple Watch 表盘
  • IndexedDB
  • java第三方包学习之lombok
  • JS数组方法汇总
  • JS字符串转数字方法总结
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • tensorflow学习笔记3——MNIST应用篇
  • Vue 动态创建 component
  • Vue2.0 实现互斥
  • vue-router的history模式发布配置
  • Vue--数据传输
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 老板让我十分钟上手nx-admin
  • 扑朔迷离的属性和特性【彻底弄清】
  • 无服务器化是企业 IT 架构的未来吗?
  • ionic异常记录
  • 数据可视化之下发图实践
  • $jQuery 重写Alert样式方法
  • (1)SpringCloud 整合Python
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (MATLAB)第五章-矩阵运算
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (三)终结任务
  • (转)setTimeout 和 setInterval 的区别
  • (转)创业的注意事项
  • . NET自动找可写目录
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .NET CORE Aws S3 使用
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .net连接oracle数据库
  • /usr/lib/mysql/plugin权限_给数据库增加密码策略遇到的权限问题
  • @FeignClient注解,fallback和fallbackFactory
  • @transactional 方法执行完再commit_当@Transactional遇到@CacheEvict,你的代码是不是有bug!...
  • @WebServiceClient注解,wsdlLocation 可配置
  • @我的前任是个极品 微博分析
  • [1] 平面(Plane)图形的生成算法
  • [202209]mysql8.0 双主集群搭建 亲测可用