冒泡排序
冒泡排序的基本思想是从后往前或者从前往后,进行两两相邻比较元素的值,如果是所排序的逆序,那么就进行交换。这种排序的效果就像水中的气泡从在较深处由于压强大气泡较小,在气泡上浮的过程中,压强逐渐减少,气泡逐渐增大的过程。
也就是说冒泡排序的过程会有较大或者较小的元素在序列的一端进行堆积,也就是形成最后的排序位置。
下面的图展示了冒泡排序的前两轮排序的过程:
可以发现这里的过程与插入排序的过程有些类似,因为每一趟的排序结果都是一个有序序列和一个无序序列。
但是要注意的是,与插入排序的区分在于:插入排序是将某一个元素进行有序序列的插入,而冒泡排序是对无序序列的顺序比较大小,交换位置,并不是限制于某一个元素,而是在比较过程中将大的元素进行上浮,最后插入到有序序列当中。
下面是冒泡排序实现的java代码:
//冒泡排序 public static void BubbleSort1(int[] arr) { for (int i = arr.length-1; i > 0; i--) { for (int j = 0; j < i; j++) { if(arr[j]>arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } }
对于冒泡排序来说,每一趟只要进行了交换,说明该序列不是有序的;而一趟结束之后都没有进行交换,则说明此时序列已经有序,那么此时就不需要再进行比较,直接结束退出循环即可。那么下面就是通过标志位进行优化的java代码:
//冒泡排序标志位优化 public static void BubbleSort2(int[] arr) { //该标志表示冒泡排序的一轮中是否进行了交换 boolean flag = false; for (int i = arr.length-1; i > 0; i--) { flag = false; for (int j = 0; j < i; j++) { if(arr[j]>arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; flag = true; } } if (!flag) { break; //如果一趟排序没有进行交换,说明已经有序,直接退出循环 } } }
时间复杂度分析:该算法在最坏的情况下,比较次数是(1+2+3+…+n-1)=(n-1)*n/2,而交换操作中需要三次操作,那么移动的次数就是:3*(n-1)*n/2。其时间复杂度就为O(n^2),最好的情况下,排序元素有序,在改进后的冒泡排序下是n-1次移动。
空间复杂度分析:没有用到多于的空间,则空间复杂度为O(1)。
容易分析冒泡排序是稳定的。
快速排序
快速排序的思想是基于分治思想的,分治思想是算法中较为常见也重要的一个思想。那么对于分治的思想通常是:为了解决整体问题,将其分割为多个子部份,并解决子问题,然后再对子问题进行划分,直到最后划分的问题可以解决。那么这个过程通常使用递归的方式去做。
快速排序的基本思想:任意选取一个元素作为枢轴,通过整理,将小于枢轴元素的值放到枢轴元素的左边,大于枢轴元素的值放到枢轴元素的右边。之后对枢轴的左边序列和右边序列分别做以上的操作,最后完成算法。
算法思想非常简单,接下来就需要算法实现的具体过程。
在算法的实现中,需要定义low和high变量,使得在low左边的元素小于枢轴元素,high右边的元素大于枢轴元素。
该算法的核心部分就是找到枢轴元素所应该在的位置
首先选取枢轴元素,并且确定low和high的位置。
第一次从high所指向的元素与枢轴元素进行比较,对于high指向的元素必须要比枢轴元素大,那么就需要交换位置,因此形成上图的过程。交换之后,就要切换遍历low指针。
low对应的3要小于枢轴元素,因此满足条件,low向右移动,检查下一个元素,而下一个元素5比4大,不满足low左边都是小于枢轴元素的条件,就与枢轴元素进行交换。
此时low与high交换位置,那么当前一轮对于high和low指针的处理就结束了,也就是一轮循环中各处理一次high和一次low,但是并没有结束,要进行下一次循环,直到low和high指向同一个元素,也就是low左边是小于枢轴元素,high右边是大于枢轴元素,那么此位置就是枢轴元素所应该在的位置。
可以看到,low和high指向了同一个位置,枢轴元素左边小于枢轴元素,右边大于枢轴元素。第一轮快速排序完成。分别对枢轴元素的左边和右边进行上述的操作即可完成排序,一般使用递归进行处理。
对上述过程中,可以分析出,在每一轮排序中,使用了大量的交换,这会大大损失掉算法的性能,因此,在这个过程中,需要将枢轴元素记录下来,并将相应需要交换的元素进行赋值即可,也就是说,将正在移动的指针的值,赋值到不动的指针的值上,直到最后找到枢轴位置,进行枢轴元素赋值,省去了大量的交换操作。
快速排序的java代码如下,其中partition为找到枢轴,排序中使用了递归:
//快速排序 public static int partition(int[] arr,int low,int high) { int pivot = arr[low]; //选取轴值 while(low < high) { while (pivot<=arr[high] && low < high) high--; arr[low] = arr[high]; while (pivot>=arr[low] && low < high) low++; arr[high] = arr[low]; } arr[low] = pivot; return low; } public static void QuickSort1(int[] arr,int low,int high) { if(low < high) { int pivotPos = partition(arr, low, high); sort1(arr, low, pivotPos-1); sort1(arr, pivotPos+1, high); } }
时间复杂度分析:
快速排序的每一轮中的多次partition的过程相当于对整个序列做了遍历,当然随着递归的加深,所扫描的元素要小于序列长度n,对于整个快速排序的过程可以抽象为一个二叉搜索树,每一层的结点都是一次或多次的partition,其时间复杂度不会超过O(n),而根据该二叉搜索树的高度为logn,就是n*logn的操作时间,那么其复杂度为O(n*logn)。
现在考虑这么一种最坏情况,所排序的元素有序:
此时的二叉搜索树的高度达到了n,根据上面的分析,那么此时的操作需要n*n次,时间复杂度为O(n*n)。
空间复杂度分析:
与时间复杂度的分析相同,在快速排序中由于使用了递归,就必然会用到栈空间,而栈调用的空间可以分析出来的是与二叉搜索树的高度有关,也就是空间复杂度为O(logn);对于最坏复杂度来说,二叉搜索树的高度有n,那么空间复杂度为O(n)。
稳定性分析:在快速排序交换过程中,很有可能将相同元素值的前后关系颠倒,因此是不稳定的排序。
标签:high,arr,元素,常见,交换,枢轴,low,排序 From: https://www.cnblogs.com/secuy/p/16785939.html