Java数据结构第一讲-排序算法(下)

简介: Java数据结构第一讲-排序算法(下)
4.1.7、基数排序
  • O(n) 空间复杂度O(rd) 稳定(基数排序必须依赖于另外的排序方法 实质是多关键字排序)
    是通过比较数字将其分配到不同的“桶里”来排序元素的。他是线性排序算法之一。
  • 解决方案:1、最高位优先法MSD 2、最低位优先法LSD
  • 算法步骤
  • 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零
  • 从最低位开始,依次进行一次排序
  • 从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列
  • 基数排序图解如下

参考代码

public class RadixSort implements IArraySort {
     @Override
     public int[] sort(int[] sourceArray) throws Exception {
         // 对 arr 进行拷贝,不改变参数内容
         int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
         int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }
    /**
     * 获取最高位数
     */
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }
    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }
    protected int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }
    private 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;
    }
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}
4.1.8、桶排序
  • O(n) 将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序
  • 适用场景:外部排序中(磁盘中,内存有限,无法将数据全部加载到内存中)
  • 算法步骤
  • 设置固定数量的空桶。
  • 把数据放到对应的桶中。
  • 对每个不为空的桶中数据进行排序。
  • 拼接不为空的桶中数据,得到结果
  • 动画演示

4.1.9、计数排序
  • 桶排序的一种特殊形式:每个桶中的数据相同
  • 算法步骤
  • 设置固定数量的空桶。
  • 把数据放到对应的桶中。
  • 对每个不为空的桶中数据进行排序。
  • 拼接不为空的桶中数据,得到结果
  • 基数排序图解如下

4.1.10、排序方法的选择?
  • n代表数据量
  • 1、n较小,可以采用直接插入或直接选择排序
  • 2、若文件初始状态基本有序,应选用直接插入、冒泡或随机的快速排序
  • 3、n较大,采用复杂度为O(nlgn)的方法:快排/堆排/归并
  • 4、在实际的软件开发中,为什么我们更倾向于使用插入排序而不是冒泡排序算法?
  • 从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个,所以在对相同数组进行排序时,冒泡排序的运行时间理论上要长于插入排序。

如果数据存储在链表中,这三种排序算法还能工作吗?

一般而言,考虑只能改变节点位置,冒泡排序相比于数组实现,比较次数一致,但交换时操作更复杂;

插入排序,比较次数一致,不需要再有后移操作,找到位置后可以直接插入,但排序完毕后可能需要倒置链表;

选择排序比较次数一致,交换操作同样比较麻烦。综上,时间复杂度和空间复杂度并无明显变化,若追求极致性能,冒泡排序的时间复杂度系数会变大,插入排序系数会减小,选择排序无明显变化。

4.2、排序工具类Arrays?如何实现一个通用的、高性能的排序函数?(Java语言采用堆排序实现排序函数,C语言使用快速排序实现排序函数)
  • Arrays拥有一组static方法(equals():比较两个array是否相等/fill():将值填入array中/sort():用来对array进行排序/binarySearch():在排好序的array中寻找元素/system.arraycopy():array的复制)
  • Jdk7中Arrays.sort()和Collections.sort()排序方法使用注意: jdk1.6中的arrays.sort()和 collections.sort()使用的是MergeSort; jdk1.7中内部实现转换成了TimSort方法,
  • 对对象间比较的实现
    1、有两个参数,第一个是比较的数据,第二个是比较的规则,如果comparator为空,则使用comparableTimSort的sort实现
    2、传入的待排序数组若小于MIN_MERGE(Java实现中为32)则从数组开始处找到一组连接升序或严格降序(找到后翻转)的数
    BinarySort:使用二分查找的方法将后续的数插入之前的已排序数组
    3、开始真正的TimSort过程(选取minRun大小,之后待排序数组将被分成以minRun大小为区块的一块块子数组)
    Timsort的思想:找到已经排好序的数据子序列,然后对剩余部分排序,最后合并起来
  • java提供的默认排序算法
    1、对于基础数据类型,目前使用的是所谓双轴快速排序(Dual-Pivot QuickSort),是一种改进的快速排序算法,早期版本是相对传统的快速排序;
    2、而对于对象数据类型,目前则是使用TimSort,思想上也是一种归并(Merge)和二分插入排序(binary Sort)结合的优化排序算法。
    思路是查找数据集中已经排好序的分区(这里叫run 连续升或降的序列),然后合并这些分区来达到排序的目的。
  • Java8引入了并行排序算法(直接使用parallelSort方法),这是为了充分利用现代多核处理器的计算能力,底层实现基于fork-join框架,当处理的数据集比较小的时候,差距不明显,甚至还表现差一点;但是,当数据集增长到数万或百万以上时,提高就非常大了,具体还是取决于处理器和系统环境.
  • fork-join框架的适用场景:计算密集型,而非IO密集型,踩过坑

4.3、常见的查找算法?
  • 1、二分查找法(考虑好边界条件,不要被面试官抓住漏洞)(使用Arrays工具类的binarySearch方法)
    思路:先确定数组的中间位置,然后将要查询的值与数组中间位置的值进行比较,若小于数组中间值,则要查找的值应位于该中间值之前,依次类推;
    算法: 1、如果关键字小于中央元素,只需继续在数组的前半部分进行搜索;2、如果关键字与中央元素相等,则搜索结束,找到匹配元素;3、如果关键字大于中央元素,只需继续在数组的后半部分进行搜索
    限制:用于顺序链表或排序后的链表
    注意事项:1、循环退出条件low<=high;2、mid的取值(low+(high-low)>>1)因为相比除法运算来说,计算机处理位运算要快得多;3、low和high的更新low=mid+1,high=mid-1
    时间复杂度:O(lgn)
public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = (low + high) / 2;//或者int mid = low+((high-low)>>1);
    if (a[mid] == value) {
      return mid;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }
  return -1;
}
  • 2、4种常见的二分查找变形问题
    第一种:查找第一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == 0) || (a[mid - 1] != value)) 
        return mid;  //mid不是第一个数或mid左边的数不是
      else high = mid - 1;
    }
  }
  return -1;
}

第二种:查找最后一个值等于给定值的元素

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] != value))
        return mid;
      else
        low = mid + 1;
    }
  }
  return -1;
}

第三种:查找第一个大于等于给定值的元素

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + ((high - low) >> 1);
    if (a[mid] >= value) {
      if ((mid == 0) || (a[mid - 1] < value))
        return mid;
      else
        high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  return -1;
}

第四种:查找最后一个小于等于给定值的元素

public int bsearch7(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] > value))
        return mid;
      else
        low = mid + 1;
    }
  }
  return -1;
}
  • 3、如果有序数组是一个循环有序数组,比如4,5,6,1,2,3。针对这种情况,如何实现一个求“值等于给定值”的二分查找算法呢?
public int search(int[] nums, int target) {
  if (nums.length == 1 && nums[0] == target)
    return 0;
  int left = 0;
  int right = nums.length - 1;
  int mid = 0;
  while (left < right) {
    mid = (left + right) >> 1;
    if (nums[left] == target)
      return left;
    if (nums[right] == target)
      return right;
    if (nums[mid] == target)
      return mid;
    if (nums[mid] > nums[left]) { // 第一种情况
      if (target > nums[mid]) {
        left = mid + 1;   //在mid到左侧最大值区间
      } else {//target小于中间值
        if (target > nums[left]) {
          right = mid - 1;
        } else {
          left = mid + 1;  //在右侧区间
        }
      }
    } else { // 第二种情况   mid小于最左值
      if (target > nums[mid]) {//两种情况:1、在mid右侧  2、在左侧
        if (target < nums[right]) {
          left = mid + 1; //1、在mid右侧 
        } else {
          right = mid - 1; //2、在左侧
        }
      } else {  //在右侧的左边区域
        right = mid - 1;
      }
    }
  }
  return -1;
}
  • 4、x的平方根 LeetCode69 实现int sqrt(int x)函数。计算并返回x的平方根,其中x是非负整数,由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去
  • 方法1:java自带API
public int mySqrt(int x) {
  return (int)Math.sqrt(x);
}
  • 方法2:二分搜索
int mySqrt(int x) {
  //注:在中间过程计算平方的时候可能出现溢出,所以用long long。
  long i=0;
    long j=x/2+1;//对于一个非负数n,它的平方根不会大于(n/2+1)
  while(i<=j)
  {
    long mid=(i+j)/2;
      long res=mid*mid;
    if(res==x) return mid;
    else if(res<x) i=mid+1;
    else j=mid-1;
  }
  return j;
}

方法3:牛顿迭代法 求c的算术平方根就是求f(x)=x^2-c的正根 迭代公式:xn+1=1/2(xn+c/xn)

int mySqrt(int x) {
  if (x == 0) return 0;
  double last=0;
  double res=1;
  while(res!=last)
  {
    last=res;
    res=(res+x/res)/2;
  }
  return int(res);
}

4.4、复杂度分析

常见的时间复杂度?表示的是一个算法执行效率与数据规模增长的变化趋势

时间复杂度 概念
  1. O(1) 常数阶| 常量级别的时间复杂度:只要代码的执行时间不随n的增大而增长,这样代码的时间复杂度我们都记作O(1)。
    2、O(logn)对数阶、O(nlogn)线性对数阶| 代码循环执行的次数呈现对数关系
    3、O(m+n)、O(m*n) | 代码的复杂度由两个数据的规模来决定

空间复杂度:(表示算法的存储空间与数据规模之间的增长关系)

常见的空间复杂度就是O(1)、O(n)、O(n2)

  • 平均时间复杂度(加权平均时间复杂度):加了概率
  • 均摊时间复杂度:对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。
  • 算法的最好情况和最坏情况?
    最好情况:算法执行最佳的数据排列。如:二分搜索时,目标值正好位于搜索的数据中心,时间复杂度为0;
    最差情况:给定算法的最差输入。如:快速排序中,如果选择的关键值是列表中最大或最小值,最差情况就会发生,时间复杂度会变成O(n^2)

4.5、如何高效地判断无序数组中是否包含某特定值?
// 方法1:使用list  (**最常使用**)
public static boolean useList(String[] arr, String targetValue) {
   return Arrays.asList(arr).contains(targetValue);
}
// 方法2:使用Set  低效
>public static boolean useSet(String[] arr, String targetValue) {
  Set<String> set = new HashSet<String>(Arrays.asList(arr));
  return set.contains(targetValue);
}
// 方法3:使用一个简单循环  最高效
>public static boolean useLoop(String[] arr, String targetValue) {
  for(String s: arr){
    if(s.equals(targetValue))
      return true;
  }
  return false;
}
  • 方法4:Arrays.binarySearch()方法:数组必须是有序的(有序数组时,使用列表或树可达到O(lgn),使用hashset可达到O(1))

4.6、查找算法实战?
  • 1、我们要给电商交易系统中的“订单”排序。订单有两个属性(下单时间,订单金额) 需求是按金额从小到大对订单数据排序。对金额相等的订单,按下单时间从早到晚排序
    稳定性概念:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变
    思路:先按下单时间给订单排序,排完序之后,使用稳定排序算法,按订单金额重新排序(稳定排序算法可以保持金额相同的两个对象,在排序之后的前后顺序不变)
  • 2、O(n)时间复杂度内求无序数组中的第K大元素?(利用分区的思想) 代码放在eclipse中
    我们选择数组区间A[0…n-1]的最后一个元素A[n-1]作为pivot,对数组A[0…n-1]原地分区,这样数组就分成了三部分,A[0…p-1]、A[p]、A[p+1…n-1]。
    如果p+1=K,那A[p]就是要求解的元素;如果K>p+1, 说明第K大元素出现在A[p+1…n-1]区间,我们再按照上面的思路递归地在A[p+1…n-1]这个区间内查找。同理,如果K<p+1,那我们就在A[0…p-1]区间查找。
  • 3、现在你有10个接口访问日志文件,每个日志文件大小约300MB,每个文件里的日志都是按照时间戳从小到大排序的。你希望将这10个较小的日志文件,合并为1个日志文件,合并之后的日志仍然按照时间戳从小到大排列。如果处理上述排序任务的机器内存只有1GB,你有什么好的解决思路
    answer:先构建十条io流,分别指向十个文件,每条io流读取对应文件的第一条数据,然后比较时间戳,选择出时间戳最小的那条数据,将其写入一个新的文件,然后指向该时间戳的io流读取下一行数据,然后继续刚才的操作,比较选出最小的时间戳数据,写入新文件,io流读取下一行数据,以此类推,完成文件的合并, 这种处理方式,日志文件有n个数据就要比较n次,每次比较选出一条数据来写入,时间复杂度是O(n),空间复杂度是O(1),几乎不占用内存。

Action1:

Action2:

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
3月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
440 35
|
8月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
3月前
|
存储 算法 搜索推荐
《数据之美》:Java数据结构与算法精要
本系列深入探讨数据结构与算法的核心原理及Java实现,涵盖线性与非线性结构、常用算法分类、复杂度分析及集合框架应用,助你提升程序效率,掌握编程底层逻辑。
|
8月前
|
存储 缓存 监控
上网行为监控系统剖析:基于 Java LinkedHashMap 算法的时间序列追踪机制探究
数字化办公蓬勃发展的背景下,上网行为监控系统已成为企业维护信息安全、提升工作效能的关键手段。该系统需实时记录并深入分析员工的网络访问行为,如何高效存储和管理这些处于动态变化中的数据,便成为亟待解决的核心问题。Java 语言中的LinkedHashMap数据结构,凭借其独有的有序性特征以及可灵活配置的淘汰策略,为上网行为监控系统提供了一种兼顾性能与功能需求的数据管理方案。本文将对LinkedHashMap在上网行为监控系统中的应用原理、实现路径及其应用价值展开深入探究。
204 3
|
8月前
|
人工智能 算法 NoSQL
LRU算法的Java实现
LRU(Least Recently Used)算法用于淘汰最近最少使用的数据,常应用于内存管理策略中。在Redis中,通过`maxmemory-policy`配置实现不同淘汰策略,如`allkeys-lru`和`volatile-lru`等,采用采样方式近似LRU以优化性能。Java中可通过`LinkedHashMap`轻松实现LRUCache,利用其`accessOrder`特性和`removeEldestEntry`方法完成缓存淘汰逻辑,代码简洁高效。
330 0
|
9月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
292 1
|
3月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
7月前
|
存储 算法 安全
Java中的对称加密算法的原理与实现
本文详细解析了Java中三种常用对称加密算法(AES、DES、3DES)的实现原理及应用。对称加密使用相同密钥进行加解密,适合数据安全传输与存储。AES作为现代标准,支持128/192/256位密钥,安全性高;DES采用56位密钥,现已不够安全;3DES通过三重加密增强安全性,但性能较低。文章提供了各算法的具体Java代码示例,便于快速上手实现加密解密操作,帮助用户根据需求选择合适的加密方案保护数据安全。
481 58
|
6月前
|
机器学习/深度学习 算法 Java
Java实现林火蔓延路径算法
记录正在进行的森林防火项目中林火蔓延功能,本篇文章可以较好的实现森林防火蔓延,但还存在很多不足,如:很多参数只能使用默认值,所以蔓延范围仅供参考。(如果底层设备获取的数据充足,那当我没说)。注:因林火蔓延涉及因素太多,如静可燃物载量、矿质阻尼系数等存在估值,所以得出的结果仅供参考。
112 4
|
6月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
185 1

热门文章

最新文章