1、简介

排序是将元素按着指定关键字的大小递增或递减进行数据的排列,排序可以提高查找的效率

2、排序算法的分类

排序算法可大致分为四类七种,具体分类如下:

插入排序:直接插入排序、希尔排序

交换排序:冒泡排序、快速排序

选择排序:直接选择排序、堆排序

归并排序

3、插入排序

算法思想:每次将一个元素按着关键字大小插入到他前面已经排好序的子序列中,重复进行这个操作,直至插入所有数据。

3、1 直接插入排序

算法描述:假设前i个元素构成的子序列是有序的,然后根据关键字大小顺序将第i+1个元素a[i] 添加到已经排好序的子序列中,使得插入a[i]后的序列仍是一个有序序列,这样当元素都插入序列时,则排序完成。

算法实现:

   /*** 直接插入排序* @author chaizepeng*/private static void directInsertSort1(int[] array) {for (int i = 1; i < array.length; i++) {int temp = array[i];//拿到下一个需要插入的数值for (int j = i-1; j >= 0; j--) {//遍历已经排好序的集合//将想要插入的元素和已经排好序的集合中的每一个进行比较if (temp < array[j]) {array[j+1] = array[j];}else {array[j+1] = temp;break;}}}}/*** 直接插入排序* @author chaizepeng*/private static void directInsertSort2(int[] array) {for (int i = 1; i < array.length; i++) {int temp = array[i];//拿到下一个需要插入的数值int j ;for (j = i-1; j >= 0 && temp < array[j]; j--) {//遍历已经排好序的集合//将想要插入的元素和已经排好序的集合中的每一个进行比较array[j+1] = array[j];}array[j+1] = temp;}}

算法分析:

假设一个序列{1,2,3,4,5},使用直接插入排序时,每个个元素需要比较1次,移动两次(现将第i个给temp,再将第temp给第i个)即可,则完成排序总需比较n-1次,移动2(n-1)次,时间复杂度是O(n)

再假设一个序列{5,4,3,2,1},使用直接插入排序时,第i个元素准备插入时,需要比较i-1次,移动2+(i-1)次(前一个元素往后移动一次,第i元素先给temp,然后temp再给第1个元素)也就是i+1次,所以n个元素总共许比较n(n-1)/2次,移动(n-1)(n+4)/2次,所以时间复杂度为O(n^2)

所以插入排序算法的时间复杂度在O(n)到O(n^2)之间,其排序效率与比较次数和元素长度直接相关。

3、2 希尔排序

算法描述:

代码实现:

   /*** 希尔排序* @author chaizepeng*/private static void shellSort1(int[] array) {int len = array.length;int i,j,gap;//步长每次除以2,使用步长来对数组进行分组//步长的直接意思就是,每隔len个元素则为同一组元素,步长在数值上等于组数for (gap = len / 2 ; gap > 0 ; gap /= 2){//因为每次都是除以2,所以当步长越小时,每组中的元素越多,越接近于有序//这里是遍历根据步长分割的每一组for (i = 0 ; i < gap ; i++){//将每组中的元素进行排序,j是在i+gap开始的,因为每隔gap个数便是同一组数据//这是遍历每组中的每一个数据,组内数据进行比较,直接插入排序//i+gap正好是获取到数组中的每一个数据for (j = i + gap ; j < len ; j += gap){if(array[j] < array[j - gap]){int temp = array[j];int k = j - gap;while (k >= 0 && array[k] > temp){array[k + gap] = array[k];k -= gap;}array[k + gap] = temp;}}}}}/*** 希尔排序* @param array*/private  static  void shellSort2(int[] array){int len = array.length;int j , gap;for (gap = len / 2 ; gap > 0 ; gap /= 2){for (j = gap ; j < len ; j++){if (array[j] < array[j - gap]){int temp = array[j];int k = j - gap;while (k >= 0 && array[k] > temp){array[k + gap] = array[k];k -= gap;}array[k + gap] = temp;}}}}/*** 希尔排序* @param array*/private static void shellSort3(int[] array) {int i,j,gap;int len = array.length;for (gap = len / 2 ; gap > 0 ; gap /= 2){for (i = gap ; i < len ; i++){for (j = i - gap ; j>= 0 && array[j] > array[j+gap] ; j -= gap){int temp = array[j];array[j] = array[j+gap];array[j+gap] = temp;}}}}

算法分析:

因为根据之前对直接插入排序的分析可以知道直接插入排序算法的效率与比较次数和元素长度直接相关,可以看出希尔排序是对直接插入排序算法做了优化,针对的就是比较次数和元素的长度,希尔排序讲究先分组排序,这样就见效了移动的次数,随着元素长度的增加,序列趋于有序,减少了比较的次数,降低了时间复杂度。另外,希尔排序的时间复杂度是O(n*(logn)^2)。

排序算法的稳定性:是对关键字相同的元素排序性能的描述,当两个元素A,B相等,排序之前A在B前边,如果排完排完序之后,A仍然在B前边,则说明排序算法是稳定的。自我感觉分析一个算法稳定还是不稳定是可行的,但是如果说一个算法是稳定还是不稳定的应该不准确吧?对于稳定性而言是不是就是对临界元素的一种处理方式而已呢?不必纠结。

4、交换排序

4、1 冒泡排序

算法描述:假设按着升序排序,就是从第一个元素开始比较相邻两个元素的值,如果前边的比后边的大,则交换两个值得位置,然后继续往后进行比较交换操作,一趟下来使得序列中最大的数在最后边,假设长度是n,则第一趟将最大的数放在第n个位置上,然后再进行第二趟,第二趟下来之后保证除第n个数之外最大数在第n-1的位置,然后继续下一趟,重复上边操作,直到不需要继续元素交换了便排序结束了。

算法实现:

/*** 冒泡排序* @author chaizepeng*/
private static void bubbleSort1(int[] array) {int len = array.length;//控制比较的长度,最长len-1for (int i = len-1; i > 0; i--) {//第二层循环控制比较大小和交换位置for (int j = 0; j < i; j++) {if (array[j] > array[j+1]) {//如果前一个数比后一个数大,则交换位置int temp = array[j];array[j] = array[j+1];array[j+1] = temp;}}System.out.println("共有"+len+"个元素,这是第"+(len - i)+"趟");}
}/*** 冒泡排序* @author chaizepeng*/
private static void bubbleSort2(int[] array) {int len = array.length;//外层循环控制遍历趟数,最多循环len-1次for (int i = 0; i < len-1; i++) {for (int j = 0; j < len - 1 - i; j++) {//第二层循环控制比较大小和交换位置if (array[j] > array[j+1]) {//如果前一个数比后一个数大,则交换位置int temp = array[j];array[j] = array[j+1];array[j+1] = temp;}}System.out.println("共有"+len+"个元素,这是第"+(i+1)+"趟");}
}/*** 冒泡排序使用添加标记的方式进行优化,序列越接近有序,效率越高* @author chaizepeng*/
private static void bubbleSort3(int[] array) {int len = array.length;//标记boolean flag;//外层循环控制遍历趟数,最多循环len-1次for (int i = 0; i < len-1; i++) {flag = true;for (int j = 0; j < len - 1 - i; j++) {//第二层循环控制比较大小和交换位置if (array[j] > array[j+1]) {//如果前一个数比后一个数大,则交换位置int temp = array[j];array[j] = array[j+1];array[j+1] = temp;flag = false;}}System.out.println("共有"+len+"个元素,这是第"+(i+1)+"趟");//如果没有进行数据交换,也就是flag==trueif (flag) {break;//不再继续下一趟}}
}

算法分析:冒泡排序记住一点,只要没有元素交换了,则排序完成。

例如序列{1,2,3,4,5},第一趟比较4次,没有元素移动,时间复杂度O(n)

再例如序列{5,4,3,2,1},第一趟比较4次,移动12次,时间复杂度为O(n^2)

所以冒泡排序算法的时间复杂度在O(n)到O(n^2)之间,其时间复杂度与序列是否有序有直接关系,并且算法是稳定的。此算法每次都借助了一个临时的中间元素,用来交换两个数。

4、2 快速排序

算法描述:快速排序是对冒泡排序的优化,快速排序第一趟就根据某个元素将序列分成两部分,一部分中所有数据比此元素小,另一部分的所有数比此元素大或等于此元素;然后再对这两部分分别进行分割,整个过程可以递归进行,直到序列有序。

算法实现:

/*** 看图理解代码,一下子就全明白了* @author chaizepeng*/
private static void quickSort1(int[] array) {int i = 0;int j = array.length - 1;quickSort(i,j,array);
}/*** 递归实现快排算法* @author chaizepeng*/
private static void quickSort(int i, int j, int[] array) {int left = i;int right = j;int key = array[i];while (i < j) {//只要不相等就循环比较,交换操作//从右往左遍历while(i < j && array[j] >= key) {j--;}if (array[j] < key) {//每次交换的都是一个小于key的元素和key所在位置上的值,也就是key值int temp = array[i];array[i] = array[j];array[j] = temp;}//从左往右遍历while(i < j && array[i] <= key) {i++;}//交换i处和j处的数据if (array[i] > key) {//每次交换的都是一个大于key的元素和key所在位置上的值,也就是key值int temp = array[i];array[i] = array[j];array[j] = temp;}}//递归使用if (i > left) {quickSort(0, i, array);}if (j < right) {quickSort(j + 1, array.length-1, array);}
}

算法分析:

这里写的快排是不稳定,快速排序算法效率受标记值(基准值)的影响,假如每次选择的基准值都是最值的话,那么就会导致被分割的子序列是不平衡的(一个里边就一个元素,另一个里边是其余的n-1个元素),则需要比较的趟数就会增加,导致算法效率低下,快排的时间复杂度在O(nlogn)到O(n^2)之间。所以想要提高快排的效率就要保证每次找的基准值是当前序列的中间值。

快排的空间复杂度在O(logn)到O(n)之间

5、选择排序

5、1 直接选择排序

算法描述:以升序为例:从第一个元素开始,依次比较序列中的元素,将最小的元素放在第一个位置;接着在第二个元素开始,依次比较序列中的值,将最小的元素放在第二个位置,依次类推,直到排序结束。

算法实现:

/*** 以升序为例:从第一个元素开始,依次比较序列中的元素,将最小的元素放在第一个位置;接着在第二个元素开始,依次比较序列中的值,将最小的元素放在第二个位置,依次类推,直到排序结束* 直接选择排序* @author chaizepeng*/
private static void straightSelectSort(int[] array) {//控制比较的趟数for (int i = 0; i < array.length - 1; i++) {int minFlag = i;//记录一下最小值所在的下标for (int j = i+1; j < array.length; j++) {//控制从何处开始比较,比较到何处结束if (array[j] < array[minFlag]) {//比较当前值和之前记录的最小下标对应的值minFlag = j;}}//将最小值放在最前边if (minFlag != i) {int temp = array[minFlag];//最小值array[minFlag] = array[i];//将当前值赋给最小值所在的位置array[i] = temp;//当前位置放最小值}}
}

算法分析:

直接选择排序,最多需要n-1趟,并且每一趟都需进行n-i此的比较,所以时间复杂度是O(n^2),并且直接选择排序是不稳定的算法。自我感觉这是最容易理解和实现的排序算法。

5、2 堆排序

算法描述:堆排序是对直接选择排序的一种优化,直接选择排序算法在每一趟比较两个数大小时, 只是比较大小没有做任何操作, 而堆排序针对此处做了优化,他在比较的同时也将其他的元素(不是最小的元素)做了相应调整。堆排序是先使用待排序的序列构建一个堆,这里用大顶堆来实现,然后根据大顶堆的特点,将根结点元素放到最后(这里实现升序排序),然后将剩下的元素再构成一个大顶堆,依次进行,直到堆的长度为1结束排序。

说一下什么是堆,堆是一种数据结构,首先是一个完全二叉树(有右子树必有左子树的二叉树),另外,这个完全二叉树的各个结点上的数值从根结点到每一个叶子结点都会有一种规律:
     父结点一定大于或者等于其孩子结点,这样的称为大顶堆
     父结点一定小于或者等于其孩子结点,这样的称为小顶堆

算法实现:

/*** * 堆排序:是对直接选择排序的一种优化,直接选择排序算法在每一趟比较两个数大小时, 只是比较大小没有做任何操作,*       而堆排序针对此处做了优化,他在比较的同时也将其他的元素(不是最小的元素)做了相应调整。堆排序是先使用待排序的序列构建一个堆,这里用大顶堆来实现,*        然后根据大顶堆的特点,将根结点元素放到最后(这里实现升序排序),然后将剩下的元素再构成一个大顶堆,依次进行,直到堆的长度为1结束排序。*  存储堆时的数据下标在1开始,而不是0,因为可以直接使用二叉树的性质进行堆的构建   * @author chaizepeng*/
private static void heapSort(int[] array) {//这里已经-1了int len = array.length-1;//用待排序的序列构建一个大顶堆,因为排序是借助堆结构进行的,这里相当于初始化堆//存储序列的数组下标必须在1开始,根据平衡二叉树的顺序存储特性可以知道,len/2之后的是叶子节点,而len/2以及它前边的结点是存在子结点的//依次遍历每一个存在子结点的结点,然后进行判断、交换结点,使得构建一个堆//1、初始化最大堆for (int i = len/2; i > 0; i--) {heapAdjust(array, i, len);}for (int i = 1; i < array.length; i++) {System.out.print(array[i]+" ");}System.out.println("--------------------------");//2、交换根结点和最后一个结点位置,将剩下的重新构建一个堆for (int i = len; i > 1; i--) {//交换元素int temp = array[i];array[i] = array[1];array[1] = temp;heapAdjust(array, 1, i-1);}
}/*** 构建大顶堆* 什么是堆:*  堆是一种数据结构,首先是一个完全二叉树(有右子树必有左子树的二叉树),另外,这个完全二叉树的各个结点上的数值从根结点到每一个叶子结点都会有一种规律:*      父结点一定大于或者等于其孩子结点,这样的称为大顶堆*      父结点一定小于或者等于其孩子结点,这样的称为小顶堆* @author chaizepeng*/
private static void heapAdjust(int[] array, int i, int len) {//遍历当前操作结点对应的子结点int j ;for (j = i * 2; j <= len ; j *= 2) {//记录一下当前操作的结点int temp = array[i];//子结点的左孩子和右孩子进行比较//这里为什么要比较一下呢?假设子结点比父节点大,则需要上浮子结点,但是有可能有左右两个结点,这里比较一下,确定哪一个结点上浮//此处j < len 必须要加if (j < len && array[j] < array[j+1]) {++j;}//如果当前操作的结点 > 子结点 ,不操作if (temp >= array[j]) {break;}//交换父子结点的值array[i] = array[j];array[j] = temp;//将需要判断的元素下标改成下降的元素下标,用于与其子节点进行判断i = j;}
}

算法分析:

堆排序的时间复杂度是O(n㏒n),算法是不稳定的。堆排序算法充分利用了完全二叉树的性质,效率高,比较复杂,不容易理解,比较适合元素多的序列排序使用。

6、归并排序

算法描述:归并排序使用的是算法设计中分治的思想,分而治之,然后合并,将小的子序列进行排序,然后慢慢的将有序的子序列进行合并,最终使得整个序列有序,这里介绍的是二路归并算法,也就是每次只将两个子序列进行归并。具体操作是这样的,每次是将两个序列A、B进行合并,这里假设这两个序列是有序的,首先初始一个长度为两个序列长度之和的容器,然后声明两个标记位i,j,i指向序列A的第一个元素,j指向序列B的第一个元素,然后比较两个数组中标记位上数的大小,哪一个小就将标记位上的数放到初始的容器中,然后将标记位指向下一个元素,然后直至其中一个序列中的元素已被移动完毕,则将另一个序列中的元素复制到容器中,排序完毕,这只是核心的两个序列归并逻辑。

算法实现:

public static void main(String[] args) {int []array = {24, 27 ,41, 44, 19, 47 ,50 ,5,65, 93 ,94 };for (int i = 0; i < array.length; i++) {System.out.print(array[i]+" ");}System.out.println();System.out.println("-----------------------------");//从第一个元素开始,第一次归并时,每一个归并的序列长度是1(默认每一个序列长度为1的序列是有序的)mergeSort(array,1);for (int i = 0; i < array.length; i++) {System.out.print(array[i]+" ");}
}/*** * @author chaizepeng** @param array 排序总序列* @param len 要归并的序列的长度*/
private static void mergeSort(int[] array ,int len) {//序列{6,5,8,4,7,9,2,1,4}要进行归并,第一次归并时需要将相邻的两个元素进行排序归并,分组如下//6,5,8,4,7,9,2,1,4//此时,需要比较两个相邻的元素,所以,就需要进行分组和排序//6,5  8,4  7,9  2,1  4//需要先判断一下要分几个组int count = array.length / (len * 2);//判断一下count的值,如果=0的话,则说明len*2已经大于序列总长度,这时序列已经排序完成,结束即可if (count == 0) {return;}//然后依次归并for (int i = 0; i < count; i++) {//i*len 第一个序列的第一个元素位置//len 有序序列长度//(2+i)*len 第二个序列的最后一个元素位置(不包括)merge(array,i*len*2,(i+1)*len*2,len);}//在这里判断一下是否归并正好两两对应,如果有剩下的,则也需要归并一下(这里是拿剩下的序列组和它的前一组进行归并操作)int rem = array.length % (len * 2);if (rem != 0) {merge(array, array.length-rem-len, array.length,len);}//进行完一次归并后,继续下一次操作//子序列长度为len的已经归并完成,下一次使用len*2作为长度继续归并mergeSort(array,len * 2);
}/*** 一次归并过程* @author chaizepeng** @param array* @param leftBegin* @param rightEnd* @param len*/
private static void merge(int[] array, int leftBegin, int rightEnd,int len) {//标记位,用于复制数组用int flag = leftBegin;//用一个数组来存一下需要合并的序列 int []temp = new int[rightEnd - leftBegin];int leftEnd = leftBegin + len;int rightBegin =leftEnd;//右边序列开始的位置//标记temp的下标int j = 0;//比较两个序列中的元素大小,进行填充while (leftBegin < leftEnd && rightBegin < rightEnd) {if (array[leftBegin] > array[rightBegin]) {temp[j++] = array[rightBegin++];}else {temp[j++] = array[leftBegin++];}}//判断一下前后两个被比较的序列那个还有元素剩余,则直接复制到temp中while(leftBegin < leftEnd) {temp[j++] = array[leftBegin++];}while(rightBegin < rightEnd) {temp[j++] = array[rightBegin++];}//将temp中的数据填到array中for (int k = 0; k < temp.length; k++) {array[flag+k] = temp[k];}
}

算法分析:

归并算法的时间复杂度是O(n㏒n),算法是稳定的,效率较高。

7、性能比较

没有绝对好的算法,要根据具体的情况来分析那个算法更好,平均情况下快排(个人比较喜欢快排)、堆排序和归并排序效率高;如果排序的序列基本有序,那么冒泡排序和直接插入排序效率比较高;如果序列基本逆序,则堆排序和归并排序效率高;在空间复杂度上看,堆排序是最好的。但是快排是最常用的。

附加一张算法指标对比表:

6、java中的排序算法相关推荐

  1. android studio插入数据表中没有_学Java能拿高薪吗 Java中常见排序算法有哪些

    学Java能拿高薪吗?Java中常见排序算法有哪些?作为老牌编程语言,Java拥有广阔的市场占有率,几乎90%以上的大中型互联网应用系统在服务端开发都会首选Java.为了加入到Java这一高薪行业,很 ...

  2. Java中各个排序算法比较

    之前我在javase基础中初步了解了一下冒泡排序,现在想根据创建的排序算法总结出它们在各方面的优劣性. 下图为java常见的几种排序: 之前我们在刘老师的数据结构课程上也有学过时间复杂度和空间复杂度的 ...

  3. java中的排序算法——归并排序

    为什么使用归并排序? java中的Arrays.sort(Object[] o)是对数组进行排序,它使用的是归并排序的方式,  快速排序要比归并排序更快一些,但为什么使用归并排序了?原因是归并排序是一 ...

  4. java中的排序算法——插入排序详解

    package com.algorithm; /**  * 插入排序及其变体  *   * List可转化为数组进行排序  * Object数组中的元素必须实现Comparable接口,即元素必须是可 ...

  5. java中的排序算法——简单选择排序,树形选择排序与堆排序(一)

    package com.sort; /**  * 选择排序:  * 简单选择排序,树形选择排序与堆排序  *   */ public class SelecSortDemo { /** * ----- ...

  6. java中queue排序_Java中常见的排序算法有哪些?---选择排序

    排序相关的的基本概念 排序: 将一组杂乱无章的数据按一定的规律顺次排列起来. 数据表( data list): 它是待排序数据对象的有限集合. 排序码(key):通常数据对象有多个属性域, 即多个数据 ...

  7. java中自然排序和比较器排序

    这里所说到的Java中的排序并不是指插入排序.希尔排序.归并排序等具体的排序算法.而是指执行这些排序算法时,比较两个对象"大小"的比较操作.我们很容易理解整型的 i>j 这样 ...

  8. 排序算法 - 面试中的排序算法总结

    排序算法总结 查找和排序算法是算法的入门知识,其经典思想可以用于很多算法当中.因为其实现代码较短,应用较常见.所以在面试中经常会问到排序算法及其相关的问题.但万变不离其宗,只要熟悉了思想,灵活运用也不 ...

  9. java中的排序方法,Java中的排序比较方式:自然排序和比较器排序

    这里所说到的Java中的排序并不是指插入排序.希尔排序.归并排序等具体的排序算法.而是指执行这些排序算法时,比较两个对象"大小"的比较操作.我们很容易理解整型的 i>j 这样 ...

最新文章

  1. python读取文件一行-Python逐行读取文件内容
  2. 如何让LINUX程序运行在多CPU?
  3. 将decimal类型的数据转成2.12这样价钱的显示方式
  4. Codeforces 997E Good Subsegments (线段树)
  5. cad布局教程_CAD制图初学入门之常见的CAD打印设置汇总
  6. This will have no impact if delete.topic.enable is not set to true以及删除kafka中的topic
  7. jsr250-api_JSON处理的Java API(JSR-353)–流API
  8. 【转】精华!图解局域网共享设置步骤 - 李小虎
  9. Java集合框架源码解读(2)——HashMap
  10. java 二叉树排序算法
  11. (一)SLAM拓扑地图(地图的生成和显示)
  12. win10下安装Elasticsearch和kibana教程
  13. 足疗小张和面向对象的7个设计原则
  14. 区块链交易正规平台有哪些?哪个区块链交易平台正规?
  15. 【机器学习】【特征选择】4.嵌入法Embedded
  16. vue插件瀑布流vue-masonry(带源码)
  17. MYSQL优化自学笔记
  18. 'CALayer position contains NaN: [nan nan]'异常
  19. voip是利用计算机网络,计算机三级网络技术VoIP概述
  20. 一起刷 leetcode 之旋转矩阵(头条/华为/陌陌真题)

热门文章

  1. ncl 添加点shp文件_气象编程 | NCL高效快速精准提取不规则区域内的格点数据
  2. c程序设计语言选修难吗,欢迎大家选修C语言程序设计这门课,本帖解释一下一些常见的问题...
  3. python opencv 图像添加噪声_opencv+python同时加椒盐噪声和随机杂点噪声
  4. activemq和mysql_activeMQ 填坑记
  5. 数据结构 快速排序(详解)
  6. C++ struct结构体 实现搜索二叉树(BST)
  7. Redis高级数据结构原理解析-bitmap,hyperloglog
  8. python加载模型包占用内存多大_加载pickle python对象会占用大量内存
  9. [蓝桥杯2019初赛]迷宫-DFS、BFS两种方法
  10. A Story of One Country (Hard)(中途相遇法/启发式分裂)