热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java排序算法面试_面试中常用排序算法实现(Java)

当我们进行数据处理的时候,往往需要对数据进行查找操作,一个有序的数据集往往能够在高效的查找算法下快速得到结果。所以排序的效率就会显的十分重要࿰

当我们进行数据处理的时候,往往需要对数据进行查找操作,一个有序的数据集往往能够在高效的查找算法下快速得到结果。所以排序的效率就会显的十分重要,本篇我们将着重的介绍几个常见的排序算法,涉及如下内容:

排序相关的概念

插入类排序

交换类排序

选择类排序

归并排序算法实现

一、排序相关的基本概念

排序其实是一个相当大的概念,主要分为两类:内部排序和外部排序。而我们通常所说的各种排序算法其实指的是内部排序算法。内部排序是基于内存的,整个排序过程都是在内存中完成的,而外部排序指的是由于数据量太大,内存不能完全容纳,排序的时候需要借助外存才能完成(常常是算计着某一部分已经计算过的数据移出内存让另一部分未被计算的数据进入内存)。而我们本篇文章将主要介绍内排序中的几种常用排序算法:

aae6cef097094078bed9fbc987323869.png

还有一个概念问题,排序的稳定性问题。如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的,否则说明该算法不稳定。

二、插入类排序算法

插入类排序算法的核心思想是,在一个有序的集合中,我们将当前值插入到适合位置上,使得插入结束之后整个集合依然是有序的。那我们接下来就学习下这几种同一类别的不同实现。

1、直接插入排序

直接插入排序算法的核心思想是,将第 i 个记录插入到前面 i-1 个已经有序的集合中。下图是一个完整的直接插入排序过程:

25d1206241f7246857aae6abf3247625.png

因为一个元素肯定是有序的,i 等于 2 的时候,将第二个元素插入到前 i-1个有序集合中,当 i 等于3的时候,将第三个元素插入到前 i-1(2)集合中,等等。直到我们去插入最后一个元素的时候,前面的 i-1 个元素构成的集合已经是有序的了,于是我们找到第 i 个元素的合适位置插入即可,整个插入排序完成。下面是具体的实现代码:

public static void InsertSort(int[] array){

int i=0,j=0,key;

for (i&#61;1;i<10;i&#43;&#43;){

key &#61; array[i];

j &#61; i-1;

while(j>&#61;0&&key

//需要移动位置,将较大的值array[j]向后移动一个位置

array[j&#43;1] &#61; array[j];

j--;

}

//循环结束说明找到适当的位置了,是时候插入值了

array[j&#43;1] &#61; key;

}

//输出排序后的数组内容

for (int value : array){

System.out.print(value&#43;",");

}

}

//主函数中对其进行调用

int[] array &#61; {1,13,72,9,22,4,6,781,29,2};

InsertSort(array);

输出结果如下&#xff1a;

3b60807c5ef29d2c2701538d03db89dd.png

整个程序的逻辑是从数组的第二个元素开始&#xff0c;每个元素都以其前面所有的元素为基本&#xff0c;找到合适的位置进行插入。对于这种按照从小到大的排序原则&#xff0c;程序使用一个临时变量temp保存当前需要插入的元素的值&#xff0c;从前面的子序列的最后一个元素开始&#xff0c;循环的与temp进行比较&#xff0c;一旦发现有大于temp的元素&#xff0c;让它顺序的往后移动一个位置&#xff0c;直到找到一个元素小于temp&#xff0c;那么就找到合适的插入位置了。

因为我们使用的判断条件是&#xff0c;key>array[j]。所以来说&#xff0c;插入排序算法也是稳定的算法。对于值相同的元素并不会更改他们原来的位置顺序。至于该算法的效率&#xff0c;最好的情况是所有元素都已有序&#xff0c;比较次数为n-1&#xff0c;最坏的情况是所有元素都是逆序的&#xff0c;比较次数为(n&#43;2)(n-1)/2&#xff0c;所以该算法的时间复杂度为O(n*n)。

2、二分折半插入排序

既然我们每次要插入的序列是有序的&#xff0c;我们完全可以使用二分查找到合适位置再进行插入&#xff0c;这显然要比直接插入的效率要高一些。代码比较类似&#xff0c;不再做解释。

public static void halfInsertSort(int[] array){

for(int k&#61;1;k

int key &#61; array[k];

//找到合适的位置

int low,high,mid;

low &#61; 0;high &#61; k-1;

while(low <&#61; high){

mid &#61; (low&#43;high)/2;

if(key &#61;&#61; array[mid])break;

else if(key > array[mid]){

low &#61; mid&#43;1;

}else{

high &#61; mid-1;

}

}

//low的索引位置就是即将插入的位置

//移动low索引位置后面的所有元素

for(int x&#61;k-1;x>&#61;low;x--){

array[x&#43;1] &#61; array[x];

}

array[low] &#61; key;

}

//遍历输出有序队列内容

for(int key:array){

System.out.print(key &#43; ",");

}

}

虽然&#xff0c;折半插入改善了查找插入位置的比较次数&#xff0c;但是移动的时间耗费并没有得到改善&#xff0c;所以效率上优秀的量可观&#xff0c;时间复杂度仍然为O(n*n)。

2、希尔排序

直接插入排序在整个待排序序列基本有序的情况下&#xff0c;效率最佳&#xff0c;但我们往往不能保证每次待排序的序列都是基本有序的。希尔排序就是基于这样的情形&#xff0c;它将待排序序列拆分成多个子序列&#xff0c;保证每个子序列的组成元素相对较少&#xff0c;然后通过对子序列使用直接排序。对于本就容量不大的子序列来说&#xff0c;直接排序的效率是相当优秀的。

希尔排序算法使用一个距离增量来切分子序列&#xff0c;例如&#xff1a;

685a62f30f3c61f1084b0b2b3759c856.png

如图&#xff0c;我们初始有一个序列&#xff0c;按照距离增量为4来拆分的话&#xff0c;可以将整个序列拆分成四个子序列&#xff0c;我们对四个子序列内部进行直接插入排序得到结果如下&#xff1a;

5a733b751000a3f95cf313f334a3169c.png

修改距离增量重新划分子序列&#xff1a;

709ac7936d058ddaac99130986b1d8f8.png

很显然&#xff0c;当距离增量变小的时候&#xff0c;序列的个数也会变少&#xff0c;但是这些子序列的内部都基本有序&#xff0c;当对他们进行直接插入排序的时候会使得效率变高。一旦距离增量减少为1&#xff0c;那么子序列的个数也将减少为1&#xff0c;也就是我们的原序列&#xff0c;而此时的序列内部基本有序&#xff0c;最后执行一次直接插入排序完成整个排序操作。

下面我们看算法是的具体实现&#xff1a;

/*渐减delete的值*/

public static void ShellSort(){

int[] array &#61; {46,55,13,42,94,17,5,70};

int[] delets &#61; {4,2,1};

for (int i&#61;0;i

oneShellSort(array,delets[i]);

}

//遍历输出数组内容

for(int value : array){

System.out.print(value &#43; ",");

}

}

/*根据距离增量的值划分子序列并对子序列内部进行直接插入排序*/

public static void oneShellSort(int[] array,int delet){

int temp;

for(int i&#61;delet;i

//从第二个子序列开始交替进行直接的插入排序

//将当前元素插入到前面的有序队列中

if(array[i-delet] > array[i]){

temp &#61; array[i];

int j&#61;i-delet;

while(j>&#61;0 && array[j] > temp){

array[j&#43;delet] &#61; array[j];

j -&#61; delet;

}

array[j &#43; delet] &#61; temp;

}

}

}

方法比较简单&#xff0c;具体的实现和直接插入排序算法相近&#xff0c;此处不再做解释。

三、交换类排序

交换类的排序算法一般是利用两个元素之间的值的大小进行比较运算&#xff0c;然后移动外置实现的&#xff0c;这类排序算法主要有两种&#xff1a;

1、冒泡排序

冒泡排序通过两两比较&#xff0c;每次将最大或者最小的元素移动到整个序列的一端。这种排序相当常见&#xff0c;也比较简单&#xff0c;直接上代码&#xff1a;

public static void bubbleSort(int[] array){

int temp &#61; 0;

for(int i&#61;0;i

for(int j &#61;0;j

if(array[j]>array[j&#43;1]){

//交换两个数组元素的值

temp &#61; array[j];

array[j] &#61; array[j&#43;1];

array[j&#43;1] &#61; temp;

}

}

}

//遍历输出数组元素

for(int value : array){

System.out.print(value &#43; ",");

}

}

2、快速排序

快速排序的基本思想是&#xff0c;从序列中任选一个元素&#xff0c;但通常会直接选择序列的第一个元素作为一个标准&#xff0c;所有比该元素值小的元素全部移动到他的左边&#xff0c;比他大的都移动到他的右边。我们称这叫做一趟快速排序&#xff0c;位于该元素两边的子表继续进行快速排序算法直到整个序列都有序。该排序算法是目前为止&#xff0c;内部排序中效率最高的排序算法。具体它是怎么做的呢&#xff1f;

e0761da88c594f736e8c58706246bc7a.png

首先他定义了两个头尾指针&#xff0c;分别指向序列的头部和尾部。从high指针位置开始扫描整个序列&#xff0c;如果high指针所指向的元素值大于等于临界值&#xff0c;指针前移。如果high指针所指向的元素的值小于临界值的话&#xff1a;

24e08cc8d50217d6191d599803a10279.png

将high指针所指向的较小的值交换给low指针所指向的元素值&#xff0c;然后low指针前移。

271002015aa533cdd63e630941fc1ef9.png

然后从low指针开始&#xff0c;逐个与临界值进行比较&#xff0c;如果low指向的元素的值小于临界值&#xff0c;那么low指针前移&#xff0c;否则将low指针所指向的当前元素的值交换给high指针所指向的当前元素的值&#xff0c;然后把high指针前移。

50b9a4900db3daf99aa0e25cf8214880.png

按照这样的算法&#xff0c;

5e366e639896a7f3cbcef72b9781b5f7.png

当low和high会和的时候&#xff0c;就代表着本次快速排序结束&#xff0c;临界值的左边和右边都已归类。这样的话&#xff0c;我们再使用递归去快速排序其两边的子表&#xff0c;直到最后&#xff0c;整张表必然是有序的。下面我们看代码实现&#xff1a;

/*快速排序的递归定义*/

public static void FastSort(int[] array,int low,int high){

if(low

int pos &#61; OneFastSort(array,low,high);

FastSort(array,low,pos-1);

FastSort(array,pos&#43;1,high);

}

}

public static int OneFastSort(int[] array,int low,int high){

//实现一次快速排序

int key &#61; array[low];

int flag &#61; 0;

while (low !&#61; high) {

if (flag &#61;&#61; 0) {

//flag为0表示指针从high的一端开始移动

if (array[high]

array[low] &#61; array[high];

low&#43;&#43;;

flag &#61; 1;

} else {

high--;

}

} else {

//指针从low的一端开始移动

if (array[low] > key) {

array[high] &#61; array[low];

high--;

flag &#61; 0;

} else {

low&#43;&#43;;

}

}

}

array[low] &#61; key;

return low;

}

如果上述介绍的快速排序的算法核心思想理解的话&#xff0c;这段代码的实现也就比较容易理解了。

四、选择类排序

选择类排序的基本思想是&#xff0c;每一趟会在n个元素中比较n-1次&#xff0c;选择出最大或者最小的一个元素放在整个序列的端点处。选择类排序有基于树的也有基于线性表的&#xff0c;有关树结构的各种排序算法&#xff0c;我们将在后续文章中进行描述&#xff0c;此处我们实现简单的选择排序算法。

public static void ChooseSort(int[] array){

for (int i&#61;0;i

for (int j&#61;i&#43;1;j

if(array[i]>array[j]){

//发现比自己小的元素&#xff0c;则交换位置

int temp &#61; array[j];

array[j]&#61;array[i];

array[i] &#61; temp;

}

}

}

//输出排序后的数组内容

for (int key : array){

System.out.print(key&#43;",");

}

}

代码堪比冒泡排序的算法实现&#xff0c;比较简单直接&#xff0c;此处不再赘述。

五、归并类排序算法

这里的归并类排序算法指的就是归并排序。归并排序的核心思想是&#xff0c;对于一个初始的序列不断递归&#xff0c;直到子序列中的元素足够少时&#xff0c;对他们进行直接排序。然后递归返回继续对两个分别有序的序列进行直接排序&#xff0c;最终递归结束的时候&#xff0c;整个序列必然是有序的。

b553ab1ed29573adfaf8f91013a25db3.png

对于一个初始序列&#xff0c;我们递归拆分的结果如上图。最小的子序列只有两个元素&#xff0c;我们可以轻易的对他们进行直接的排序。简单的排序结果如下&#xff1a;

f62ec3537ab1eca5d88b9b2874489806.png

然后我们递归返回&#xff1a;

14540f8780e85896643646e77bc7021c.png

初看起来和我们的希尔排序的基本思想有点像&#xff0c;希尔排序通过对初始序列的稀疏化&#xff0c;使得每个子序列在内部上都是有序的&#xff0c;最终在对整个序列进行排序的时候&#xff0c;序列的内部基本有序&#xff0c;总体上能提高效率。但是我们的归并排序的和核心思想是&#xff0c;通过不断的递归&#xff0c;直到子序列元素足够少&#xff0c;在内部对他们进行直接的排序操作&#xff0c;当递归返回的时候&#xff0c;对返回的两个子表再次进行归并排序&#xff0c;使得合成的新序列是有序的&#xff0c;一直到递归返回调用结束时候&#xff0c;整个序列就是有序的。

/*归并排序的递归调用*/

public static void MergeSort(int[] array,int low,int high){

if(low &#61;&#61; high){

//说明子数组长度为1&#xff0c;无需分解&#xff0c;直接返回即可

}else{

int p &#61; (low&#43;high)/2;

MergeSort(array,low,p);

MergeSort(array,p&#43;1,high);

//完成相邻两个子集合的归并

MergeTwoData(array,low,high);

}

}

/*用于排序两个子序列的归并排序算法实现*/

public static void MergeTwoData(int[] array,int low,int high){

int[] arrCopy &#61; new int[high-low&#43;1];

int i,j;

i &#61; low;j&#61; (low&#43;high)/2&#43;1;

for (int key&#61;0;key<&#61;high-low;key&#43;&#43;){

//如果左边子数组长度小于右边数组长度&#xff0c;当左数组全部入库之后&#xff0c;右侧数组不用做比较直接入库

if(i&#61;&#61;(low&#43;high)/2&#43;1){

arrCopy[key] &#61; array[j];

j&#43;&#43;;

}

//如果右侧数组长度小于左侧数组长度&#xff0c;当右侧数组全部入库之后&#xff0c;左侧数组不用做比较直接入库

else if(j&#61;&#61;high&#43;1){

arrCopy[key]&#61;array[i];

i&#43;&#43;;

}else if(array[i]

arrCopy[key]&#61;array[i];

i&#43;&#43;;

}else{

arrCopy[key] &#61; array[j];

j&#43;&#43;;

}

}

j &#61; 0;

//按顺序写回原数组

for(int x&#61;low;x<&#61;high;x&#43;&#43;) {

array[x] &#61; arrCopy[j];

j&#43;&#43;;

}

}

我们的递归调用方法还是比较简单的&#xff0c;首先从一个序列的中间位置开始递归分成两个子序列并传入该序列的头尾索引&#xff0c;当头尾索引相等的时候&#xff0c;说明序列中只有一个元素&#xff0c;于是无需调用归并排序&#xff0c;直接返回。等待两边的子序列都返回的时候&#xff0c;我们调用归并排序对这两个子序列进行排序&#xff0c;继续向上返回直到递归结束&#xff0c;最终的序列必然是有序的。我们主要看看用于归并两个子序列的排序算法的实现。

假设我们递归返回了这样的两个子序列&#xff0c;因为这些子序列都是递归返回的&#xff0c;所以他们在内部都是有序的&#xff0c;于是我们需要对两个子序列排序和并成新序列并向上递归返回。

45f3354fb98017cb27680a0a299ff567.png

排序的算法如下&#xff1a;

分别取出两个序列的栈顶元素&#xff0c;进行比较&#xff0c;把小的一方的元素出栈并存入新序列中&#xff0c;值较大的元素依然位于栈顶。这样&#xff0c;当有一方的元素全部出栈之后&#xff0c;另一方的元素顺序填入新序列中即可。具体的算法实现已经在上述代码中给出了。

至此&#xff0c;线性的基本排序算法都已经介绍完成了&#xff0c;有些算法介绍的比较粗糙&#xff0c;待后续深入理解后再回来补充&#xff0c;总结不到之处&#xff0c;望指出&#xff01;



推荐阅读
  • 入门指南:使用FastRPC技术连接Qualcomm Hexagon DSP
    本文旨在为初学者提供关于如何使用FastRPC技术连接Qualcomm Hexagon DSP的基础知识。FastRPC技术允许开发者在本地客户端实现远程调用,从而简化Hexagon DSP的开发和调试过程。 ... [详细]
  • 本文详细介绍了 Redis 中的主要数据类型,包括 String、Hash、List、Set、ZSet、Geo 和 HyperLogLog,并提供了每种类型的基本操作命令和应用场景。 ... [详细]
  • 尽管在WPF中工作了一段时间,但在菜单控件的样式设置上遇到了一些基础问题,特别是关于如何正确配置前景色和背景色。 ... [详细]
  • Android与JUnit集成测试实践
    本文探讨了如何在Android项目中集成JUnit进行单元测试,并详细介绍了修改AndroidManifest.xml文件以支持测试的方法。 ... [详细]
  • 本文详细介绍了二叉堆的概念及其在Java中的实现方法。二叉堆是一种特殊的完全二叉树,具有堆性质,常用于实现优先队列。 ... [详细]
  • 电商高并发解决方案详解
    本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 前言:由于Android系统本身决定了其自身的单线程模型结构。在日常的开发过程中,我们又不能把所有的工作都交给主线程去处理(会造成UI卡顿现象)。因此,适当的创建子线程去处理一些耗 ... [详细]
  • 解决Win10 1709版本文件共享安全警告问题
    每当Windows 10发布新版本时,由于兼容性问题往往会出现各种故障。近期,一些用户在升级至1709版本后遇到了无法访问共享文件夹的问题,系统提示‘文件共享不安全,无法连接’。本文将提供多种解决方案,帮助您轻松解决这一难题。 ... [详细]
  • 本文深入探讨了WPF框架下的数据验证机制,包括内置验证规则的使用、自定义验证规则的实现方法、错误信息的有效展示策略以及验证时机的选择,旨在帮助开发者构建更加健壮和用户友好的应用程序。 ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本文详细介绍了如何搭建一个高可用的MongoDB集群,包括环境准备、用户配置、目录创建、MongoDB安装、配置文件设置、集群组件部署等步骤。特别关注分片、读写分离及负载均衡的实现。 ... [详细]
  • 流处理中的计数挑战与解决方案
    本文探讨了在流处理中进行计数的各种技术和挑战,并基于作者在2016年圣何塞举行的Hadoop World大会上的演讲进行了深入分析。文章不仅介绍了传统批处理和Lambda架构的局限性,还详细探讨了流处理架构的优势及其在现代大数据应用中的重要作用。 ... [详细]
  • JUC并发编程——线程的基本方法使用
    目录一、线程名称设置和获取二、线程的sleep()三、线程的interrupt四、join()五、yield()六、wait(),notify(),notifyAll( ... [详细]
author-avatar
嘉娜杰_877
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有