5归并排序
思路就是无限递归对半分一直到1,1可以看成排序好的(也无所谓排序),然后回退到上层时候排序好的左半部分和右半部分依次比大小拷贝到新申请的数组中,然后再用对应的的下标拷贝回原来的位置,复杂度为o(nlogn)。缺点就是空间复杂度为o(n),所以相比快速排序弱爆了。但在外部文件排序中貌似有应用。
代码如下:
- <span style="font-size:12px;"><span style="font-size:14px;">//归并排序
- public void mergeSort(T[] t,Comparator<T> c){
- mergeSort(t,0,t.length-1,c);
- }
- private void mergeSort(T[] t,int low,int high,Comparator<T> c){
- if(low<high){//分割到一为止,
- int middle = (low+high)/2;
- mergeSort(t,low,middle,c);
- mergeSort(t,middle+1,high,c);
- //单个元素不需要排序,因此排序过程也在if语句里面
- T[] temp = (T[])new Object[high-low+1];
- int a = 0;//temp 的下标
- int lowIndex = low;//low到middle的索引,
- int highStart = middle+1;
- while((lowIndex<=middle)&&(high>=highStart)){//排序好的分割2列都有值
- if(c.compare(t[lowIndex], t[highStart])<=0){
- temp[a++] = t[lowIndex];
- lowIndex++;
- }else{
- temp[a++] = t[highStart];
- highStart++;
- }
- }
- while(lowIndex<=middle){//索引低的部分还有剩余项
- temp[a++] = t[lowIndex];
- lowIndex++;
- }
- while(high>=highStart){//索引高的部分还有剩余项
- temp[a++] = t[highStart];
- highStart++;
- }
- //重置temp的索引并拷贝回原数组
- for(a=0;a<temp.length;a++){
- t[low++] = temp[a];
- }
- </span>
- }</span>
思路是:找到一个支点,然后根据支点把比支点大的元素放在右边,比支点小的元素放在左边,将支点左右两个子数组递归进入下一层一直到子数组长度为1为止。复杂度也是o(nlogn)
对于支点的选择,最普通的情况为选第一个元素,暂时没有证明怎样选取支点复杂度最小,改进一下是比较第一个、中间一个、最后一个元素取中间大小的作为支点。复杂度比直接选取第一个稍好。当支点选取第一个元素时,如果数组为已经排序好的,快速排序会退化到平方阶的复杂度。
代码中的一些小优化:
1 将比较的3个元素最小的放在0处(这个本来就该在左边),最大的放中间,支点放在最右边(支点不用在中间位置比较自己)。
2 支点左右分割数组的代码中,与支点相等的数一定也要停下来,这样在递归过程中不会越界,带来的副产品是迭代变量自增自减一定也要写在循环中,不然就是死循环,具体见代码。
代码如下:
- <span style="font-size:12px;"><span style="font-size:12px;">//快速排序</span>
- public void quickSort(T[] t,Comparator<T> c){
- quickSort(t,0,t.length-1,c);
- }
- private void quickSort(T[] t,int low,int high,Comparator<T> c){
- if(low<high){//一个数则不用排序
- int middle = (low+high)/2;
- T temp;//交换值的临时变量
- //三个if确定分割点并将分割点放在最后
- if(c.compare(t[low], t[middle])>0){
- temp = t[low];t[low] = t[middle];t[middle] = temp;
- }
- if(c.compare(t[low], t[high])>0){
- temp = t[low];t[low] = t[high];t[high] = temp;
- }
- if(c.compare(t[middle], t[high])<0){
- temp = t[middle];t[middle] = t[high];t[high] = temp;
- }
- int lowIndex = low;
- int highIndex = high;
- /**
- * 循环内只能放小于大于不能放等于,这样2个下标移动的时候才肯定会
- * 在最小和最大处停下来,而带来的副产品是下标的自增自减一定要放
- * 在循环内,不然会造成循环。
- */
- while(true){//等也要停下来防止越界
- while(c.compare(t[++lowIndex], t[high])<0)//lowIndex先开始因此
- //循环break后互换用lowIndex
- ;
- while(c.compare(t[--highIndex], t[high])>0)//自增自减放下面是死循环
- ; //加等号相等依然执行会越界
- if(lowIndex>=highIndex)
- break;
- temp=t[lowIndex];t[lowIndex]=t[highIndex];t[highIndex]=temp;
- }
- temp=t[lowIndex];t[lowIndex]=t[high];t[high]=temp;
- quickSort(t,low,lowIndex-1,c);
- quickSort(t,lowIndex+1,high,c);
- }
- }</span>
快速排序与归并排序在时间复杂度上,虽然减少了比较的次数,但实际上由于递归的影响,对于少量数据的排序不占任何优势,只有在大数据量的下,递归的影响才会被减少的大量比较所消除。
因此实际上在对于归并和快速排序,递归可以增加个返回条件,在数据量小于个较小数的时候,直接使用冒泡等排序算法优化。