一.介绍
基于交换排序的算法有两种:冒泡排序和快速排序,冒泡排序其实是比较简单的排序算法,而快速排序则是在冒泡排序上面更高一层的思想体现.
二.冒泡排序
1.基本思想: 比较相邻两个元素的关键字值,如果反序,则交换.若按升序排序,每一趟将被扫描的数据序列中的最大元素交换到最后位置,就像气泡从水里冒出来一样.同样的,冒泡排序每一趟也是要遍历n-1次,比较n-1次.从第一个元素和第二个元素开始比较,如果第一个元素大于第二个元素,则将第一个和第二个元素的位置交换了,这时就移动了3次.空间缓存为1.接下来就是第二个元素和第三个元素的比较了.以此类推,到最后一个元素比较完后,第一趟的冒泡排序算是完成.第二趟的比较就可以忽略掉最后一个,最后一个是最大的了.直到所有的元素都冒泡,需要n-1趟排序.
2.代码
public static void bubbleSort(int[] table){ //点睛之笔,是否交换的标记 boolean exchange = true; for(int i = 0 ;i<table.length-1 && exchange ; i++){ exchange = false; for(int j = 0; j < table.length-1-i; j ++){ if(table[j]>table[j+1]){ int temp = table[j]; table[j] = table[j+1]; table[j+1] = temp; exchange = true; } } } }
3.评价
我在自己写这个冒泡排序的时候,以为很简单,但是仔细看了课本的写法,却有一个我没有想到的.就是exchange这个标记符的出现,它能在某些情况下减少冒泡排序的时间复杂度,避免没有必要的比较和移动.exchange的初始值是true,但是到了第一个for循环里面就变成了false,当然如果能到if(table[j]>table[j+1])的语句里面则说明了在这个序列中有不规律,没排序号的部分存在的.如果没有进入到if语句里面,说明了这个序列是排序好的,有规律的.所以我们可以放弃继续for循环.
冒泡排序算法的最好情况是数据的初始序列已排序号,只需一趟扫描,比较次数为n-1次,没有数据移动.(想象一下如果没有exchange的话,第一个for循环还有执行多n-2次,里面的的元素还要比较多(n-2)^2/2次),时间复杂度为O(n).最坏情况是数据元素反序排序,需要n-1趟扫描,比较次数和移动次数都是O(n^2).一般情况下,冒泡排序的时间复杂度为O(n^2)之间.
冒泡排序需要一个辅助空间用于交换两个元素,空间复杂度为O(1).
同时,冒泡排序算法是稳定的.
三.快速排序
1.由来介绍
首先,我们要知道快速排序是冒泡排序的升级版.冒泡排序中,在未排序好的序列中,目标元素(最大或最小元素)到达目的地,可能需要n-1移动.可能从下标1到下标n,要经历2(n-1)次移动,table[0]经过temp到达temp[3],再从table[3]经过temp到达table[4],这样一直进行下去,存在重复的数据移动.而快速排序算法希望尽可能地减少这样的重复的数据移动.
2.思想介绍
quick sort 的基本思想是:在数据序列中选择一个值作为比较的基准值,每趟从数据序列的两端开始交替进行,将小于基准值的元素交换到序列前端,将大于基准值的元素交换到序列后端,介于两者之间的位置则成为基准值最终位置.同时,序列将划分为两个子序列,再用同样的方法分别对两个子序列进行排序,直到子序列的长度为1,则完成排序.
基准值一般是选择到序列中的第一个元素,当然也可以选择序列的中间值,但由于序列的初始排序是随机的,无论如何选择基准值,都是无异的.
首先我们要解决的问题是当我们将基准值的位置选定后并且已经插入后,要如何将有基准值分开的两个子序列再进行同样的操作,不断循环,直到排序完成,直到子序列的长度为1.当然最好的办法就是递归算法的思想最好,所以我们至少有两个方法,一个为排序算法本身,一个是调用排序算法的方法.
第二个问题就是如何排序,首先选择一个基准值,一开始都是选择第一个元素,先和最后端的元素比较(注意此时第一个位置的值被作为标准值储存在另外一个空间,所以第一位置是没有意义的位置).如果比基准值大,不用换罗.此时就将下标值j-1,继续比较,如果比基准值小,好了,就让这个值去插入到第一个位置(此时插入值的原位置为无意义的空间).这时候,比较的端头从后端变到前端,让第二个元素(此时下标为1)和基准值比较,如果小,不用换位置,将i+1,让第三个元素(下标为2)和基准值比较,恰巧比基准值大,好,移动到上一次被移动的值的位置(上一次被移动值没有意义,插入后,这一次被移动的值的原位置也变得没有意义,为下一次移动的值提供位置).就这样i++,j--,一直到i和j的值相等,说明,比基准值小的值都在下标为i(j)之前,比基准值大的值都在下标为i(j)的值后面,然后把基准值放入到下标为i的位置(注意,此时下标为i的值已经被移动到别处,此时下标为i的值没有意义)
3.代码
//调用排序的方法 public static void quickSortBook(int[] table){ quickSortBook(table,0,table.length-1); } //排序方法 //方法的重置 public static void quickSortBook(int[] table ,int begin,int end){ if(begin<end){//这个判断语句就是来确定table是否符合quickSort的要求 int i = begin, j = end; int vot = table[i];//第一个值作为基准值 while(i!=j){//一趟排序,来确定一趟排序的结束 while(i<j && vot <= table[j]){ j--; } if(i<j){ table[i++] = table[j]; } while(i<j && table[i] <= vot){ i++; } if(i<j){ table[j--] = table[i]; } } table[i] = vot; //这里是递归调用的表现 quickSortBook(table,begin,j-1); quickSortBook(table,i+1,end); } }
4.分析
快速排序的执行时间和数据序列的初始排序及基准值的选择有关.最好情况是,每趟排序将序列分成长度相近的两个子序列,比较次数为O(n*log2n),时间复杂度为O(n*log2n),每趟都要比较n次,一共大概要log2n趟.最坏的情况是,每趟将序列分成长度差异很大的两个子序列,时间复杂度则飙升为O(x^2).例如,当数据序列已排序是,若选择序列的第一个值作为基准值,则每趟得到的两个子序列长度分别为0和划分前的长度减1,比如{1,2,3,4,5,6},需要比较n-1趟才能完成排序.时间复杂度为O(n^2).
总之,当n较大且数据序列随机排列时,快速排序是"quick"的,但是当n很小或基准值选择不合适时,quick sort则很慢.
快速排序算法在递归调用过程中,需要使用栈保存参数,栈所占用的空间与递归调用的次数有关,最好的情况下空间复杂度为O(log2n),最坏情况下空间复杂度为O(n),平均空间复杂度为O(log2n).
快速排序算法是不稳定的.