zoukankan      html  css  js  c++  java
  • 排序总结[6]_快速排序

    快速排序是实践中已知最快的排序算法。
    基本思想:选取一个枢纽元进行一趟快排,将比起小的元素放在左边,比其大的元素放在右边,然后递归的对左右两部分分别进行快速排序即可。
    特点:

    • 平均时间复杂度:O(N*logN)
    • 最坏时间复杂度:O(N^2)---每次选择的枢纽元都是最小或最大元素
    • 最优时间复杂度:O(N*logN)
    • 空间复杂度:快速排序在系统内部需要一个栈来实现递归。若每次划分较为均匀,则其递归树的高度为 O(lgn), 故递归后所需栈空间为 O(lgn) 。最坏情况下,递归树的高度为 O(n), 所需的栈空间为 O(n)
    • 稳定性:不稳定

    注意几点:

    1.如何选取枢纽元来避免最坏情景?
    2.如何进行数据分割,就是根据枢纽元进行一趟快排?
    3.小数组的时候使用快排合适吗?

    一、如何选取枢纽元?

      很多非标准例程选择第一个元素作为枢纽,如果输入是随机的,那这样是可以接受的,但是如果输入是预排序或者反序的,那这样就会产生一个劣质的分割,因为所有元素都被分配到一部分中去了,感觉什么都没有干,这样就退化为冒泡算法了,时间复杂度是O(N^2)。
      这里介绍三分中值算法。我们知道一组N个数的中值第[N/2]个大的数,枢纽元的最好选择就是中值。不幸的是中值不好算,怎么办呢?
      一般采用左端、中间和右端三个元素的中值作为枢纽元,这样就消除了预排序输入的坏情况,提高了大约5%的运行时间。---资料来源《数据结构与算法分析_C语言描述》

    二、分割策略

      本文介绍一种比较实际应用比较好的分割策略。该法的第一步是通过将枢纽元与最后的元素交换使得枢纽元离开要分割的数据段。然后设定2个指针i、j左移和右移完成一趟快排。
      下图是交换过程:

    三、分割策略

      对于很小的数组,快速排序反而不如插入排序,不仅如此,因为快速排序是递归的,这种情况经常发生。通常的解决方法是对小的数组不递归的使用快速排序,而是使用插入排序。一般N<=10是一个比较好的截止范围。

    四、参考代码

    void quickSort(int[] arr){
    	if(arr==null)throw new NullPointerException();
    	if(arr.length<=1)return;
    	quickSortRec(arr,0,arr.length-1); 
    	//quickSortNoRec(arr,0,arr.length-1);
    }
    private static final int CUTOFF = 10;
    //递归版本
    void quickSortRec(int[] arr,int left,int right){
    	if(left+CUTOFF <= right){
    		int pos = partition(arr,left,right);
    		quickSortRec(arr,left,pos-1);
    		quickSortRec(arr,pos,right);
    	}else{
    		insertSort(arr,left,right);//小数组选择插入排序即可
    	}
    }
    //非递归版本
    void quickSortNoRec(int[] arr,int left,int right){
    	Stack<Integer> stack = new Stack<Integer>();
    	if(left<right){
    		int pos = partition(arr,left,right);
    		//如果考虑小数组,那每次入栈前判断一下元素个数
    		if(left<pos-1){ 
    			stack.push(left);
    			stack.push(pos-1);
    		}
    		if(pos<right){
    			stack.push(pos);
    			stack.push(right);
    		}
    		while(!stack.isEmpty()){//栈中存放需要排序的索引范围
    			int low = stack.pop();
    			int high= stack.pop();
    			pos = partition(arr,low,high);
    			if(low<pos-1){
    				stack.push(low);
    				stack.push(pos-1);
    			}
    			if(pos<right){
    				stack.push(pos);
    				stack.push(high);
    			}
    		}
    	}
    }
    
    //一趟快排
    int partition(int[] arr,int left,int right){
    	int privote = media3(arr,left,right);
    	int i=left,j=right-1;
    	//先移动i和j来完成交换过程
    	for(;;){
    		while(arr[++i]<privote){}
    		while(arr[--j]>privote){}
    		if(i<j){
    			swap(arr,i,j);//交换i和j
    	    }else{
    	    	break;//i和j交错之后就结束了
    	    } 
    	}
    	//再交换i和privot
    	swap(arr,i,right-1);
    	return i; //返回枢纽元的正确位置即可
    }
    //三分中值算法
    int media3(int[] arr,int left,int right){
    	int mid = left + ((right-left)>>>1);
    	if(arr[left]>arr[mid]){
    		swap(arr,left,mid);
    	}
    	if(arr[left]>arr[right]){
    		swap(arr,left,right);
    	}
    	if(arr[mid]>arr[right]){
    		swap(arr,mid,right);
    	}
    	swap(arr,mid,right-1); //hide the privote
    	return arr[right-1];//return the privote
    }
    
  • 相关阅读:
    mysql drop table & myisam
    python 发送 html email
    python mysqldb 查询返回字典结构
    shell 脚本 连接数据库
    python 中使用map 构建sql查询语句
    C#启动一个外部程序(1)WinExec
    知道在那里划这一条线吗[zt]
    C#启动一个外部程序(2)ShellExecute
    把FlashCom 帮助安装到Flash 8 中文版
    C#读写ini文件
  • 原文地址:https://www.cnblogs.com/lhyblog/p/5903902.html
Copyright © 2011-2022 走看看