排序方法总结
1.冒泡排序
2.选择排序
3.插入排序
4.快速排序
5.计数排序
6.归并排序
7.二分法排序
##1.冒泡排序##
1.1基本思想:
两个数比较大小,较大的数下沉,较小的数冒起来。
1.2过程:
1.比较相邻的两个数据,如果第二个数小,就交换位置。
2.从后向前两两比较,一直到比较最前两个数据。最终最小数被交换到起始的位置,这样第一个最小数的位置就排好了。
3.继续重复上述过程,依次将第2.3…n-1个最小数排好位置。
1.3代码实现(工具:Eclipse)
package sort;
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int []arr= {2,4,5,15,8,9,16};
int []res=BubbleSort(arr);
System.out.println(Arrays.toString(res));
}
public static int [] BubbleSort(int[]arr) {
int temp;//临时变量
boolean flag;//是否交换的标志
for(int i =0;i<arr.length;i++) {//表示趟数
flag=false;
for(int j=arr.length-1;j>i;j--) {
if(arr[j]<arr[j-1]) {
temp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=temp;
flag = true;
}
}
if(!flag)break;
}
return arr;
}
}
1.4冒泡排序复杂度分析
最好情况下 :数组本身有序,只需要进行n-1次比较,不需要交换,时间复杂度为O(n)
最坏情况下 :数组为逆序,此时需要比较n*(n-1)/2,并作等数量级的记录移动,时间复杂度为O(n^2)
##2.选择排序##
2.1基本思想:
在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换
第二次遍历n-2个数,找到最小的数值与第二个元素交换;
…
第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。
2.2代码实现:(工具:Eclipse)
package sort;
import java.util.Arrays;
public class SelctionSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int []arr= {2,4,5,15,8,9,16};
int []res=select_sort(arr);
System.out.println(Arrays.toString(res));
}
public static int [] select_sort(int arr[]) {
for(int i=0;i<arr.length-1;i++) {
int minIndex = i;
for(int j=i+1;j<arr.length;j++) {
if(arr[j]<arr[minIndex]) {
minIndex = j;
}
}
if(minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
return arr;
}
}
2.3简单选择排序复杂度分析
简单排序最大的特点:交换移动数据次数相当少,最好情况下交换0次,最差请求交换n-1次;适用于数组个数不多,但每个数组元素较大的情况。
时间复杂度:无论是最好最差情况,比较次数一样多,n(n-1)/2,总的时间复杂度O(n^2)。
简单选择排序性能上略优于冒泡排序。
##3.插入排序##
3.1基本思想:
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
3.2代码实现:(工具:Eclipse)
package sort;
import java.util.Arrays;
public class InsertionSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int []arr= {2,4,5,15,8,9,16};
int []res=insert_sort(arr);
System.out.println(Arrays.toString(res));
}
public static int[] insert_sort(int[] arr) {
int temp;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
} else {
break;
}
}
}
return arr;
}
}
3.3直接插入排序复杂度分析
最好情况下:在本身有序的情况下,共比较了n-1次,没有移动的记录,时间复杂度为O(n)。
最坏情况下:在逆序情况下:比较了 n(n-1)/2次,而移动次数为(n+2)(n-2)/2次。
平均比较 移动 次数:n^2/4, 故直接插入排序的时间复杂度为O(n^2)。
直接插入排序法比冒泡和简单选择排序的性能要好一些。
##4.快速排序##
4.1基本思想:
1.先从数列中取出一个数作为key值;
2.将这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
3.对左右两个小数列重复第二步,直至各区间只有1个数。
4.2代码实现:(工具:Eclipse)
package sort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr={2,7,1,2,8,1,3};
System.out.println(Arrays.toString(arr));
QuickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static int getIndex(int[] arr,int left,int right){
//找一个基准点 nlog(n) 冒泡排序:n^2
int key= arr[left];
//int left=0; //初始的左循环的下标
//int right=arr.length-1; //初始的右循环的下标
while(left<right){//循环嵌套 外层循环一次 内层循环所有
//从右向左循环 循环条件 大 不变 小 交换
while(arr[right]>=key&&left<right){
right--;
}
//出了循环 证明arr[right]<key 交换
arr[left]=arr[right];//arr[right]=key
//从左向右循环 小 大 交换
while(arr[left]<=key&& left<right){
left++;
}
//出了这个循环arr[left]>key
arr[right]=arr[left];
}
//出了外层循环 left=right
//给分界点位置赋值
arr[left]=key;
return left;
}
//排序过程
public static void QuickSort(int[] arr,int left,int right){
//出口
if(left>=right){
return;
}
//找分界点的 下标
int index=getIndex(arr, left, right);
//以下标为分界进行左侧 右侧分别排序 递归调用的过程
//先对左侧进行排序
QuickSort(arr, left, index-1);
//对右侧进行排序
QuickSort(arr, index+1, right);
}
}
4.3快速排序时间复杂度分析
最优与平均时间复杂度O(nlogn)
最坏时间复杂度O(n^2)
空间复杂度O(logn)
快速排序是一种不稳定的排序方法
##5.计数排序##
5.1基本思想:
对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数 。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。它创建一个长度为这个数据范围的数组C,C中每个元素记录要排序数组中对应记录的出现个数。
5.2代码实现:(工具:Eclipse)
package sort;
import java.util.Arrays;
public class JiShuPX {
public static void main(String[] args) {
int[] arr={1,4,7,3,2,4};
int[] res=jspx(arr);
System.out.println(Arrays.toString(res));
}
public static int[] jspx(int[] arr){
//求 最大值 和最小值
int max=arr[0];
int min=arr[0];
for(int a:arr){
if(max<a){
max=a;
}
if(min>a){
min=a;
}
}
int[] pxarr=new int[max-min+1];
//循环遍历 旧数组进行计数排序
for(int a:arr){
pxarr[a-min]+=1;
}
//遍历输出
//创建最终数组
int[] result=new int[arr.length];
//记录最终数组的下标
int index=0;
//先循环 每一个元素 计数排序器的下标中
for(int i=0;i<pxarr.length;i++){
//循环出现的次数
for(int j=0;j<pxarr[i];j++){
result[index++]=i+min;
}
}
return result;
}
}
5.3计数排序时间复杂度分析
采用计数排序就很好,总的时间复杂度为O(n)。
##6.归并排序##
6.1基本思想
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
6.2代码实现:(工具:Eclipse)
package sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr={2,3,1,2,4,2,3,1};
int[] newarr=new int[arr.length];
chaiSort(arr, 0, arr.length-1, newarr);
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.toString(newarr));
}
//拆
public static void chaiSort(int[] arr,int first,int last,int[] newarr){
if(first<last){
//找中间点
int mid=(first+last)/2;
//拆左侧的数据集 //拆右侧的数据集
chaiSort(arr, first, mid, newarr);
//拆右侧的
chaiSort(arr, mid+1, last, newarr);
//并
merge(arr, first, mid, last, newarr);
}
}
//并 参数
/**
* newarr 长度 === 原来的arr的长度
* @param arr
* @param first
* @param mid
* @param last
* @param newarr
*/
public static void merge(int[] arr,int first,int mid,int last,int[] newarr){
//第一个数据集的起始下标
int m=first;
int x=mid;//第一个数据集的终止的下标
//第二个数据集的起始下标
int n=mid+1;
int y=last;//第二个数据集的终止下标
//新数组的 起始下标
int index=0;
while(m<=x&&n<=y){
if(arr[m]<=arr[n]){//第一个数据集的数据小
newarr[index++]=arr[m++];
}else{
newarr[index++]=arr[n++];
}
}
//如果 第一个数据集
while(m<=x){
newarr[index++]=arr[m++];
}
while(n<=y){
newarr[index++]=arr[n++];
}
for(int i=0;i<index;i++){
arr[first+i]=newarr[i];
}
}
}
6.3归并排序复杂度分析
无论最好、最坏、平均来讲总的时间复杂度为O(nlogn)
对于递归归并:由于需要与原始记录序列同样数量的存储空间存放归并结果及递归时深度为logn的栈空间,空间复杂度O(n+logn)
归并排序为稳定的排序算法。
##7.二分法排序##(由于面试会经常出现,所以单独拿出)
7.1基本思想:
二分排序算思想是分治,将大的问题简化成为小的问题,所有小的问题全部解决了,整个问题就解决了。
二分法排序必须为有序数组,从中查找某一元素,如果此元素在这个数组中则返回下标,不在则返回-1;通过数组的长度取其中间值,然后与要查找的元素相比较,如果大于则下次循环应该从左边查找,小于则从右边;二分法排序快的原因是它并没有循环所有元素,而是折半查询,但是需要有序数组这一必要条件。
7.2代码实现:(工具:Eclipse)
package sort;
import java.util.Arrays;
public class BinaryInsertSort {
public static void main(String[] args) {
int []arr= {2,4,5,15,8,9,16};
int []res=BinaryInsertSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(res));
}
//待排数据存储在数组a中,以及待排序列的左右边界
public static int[] BinaryInsertSort(int[] arr, int left, int right) {
int low, middle, high;
int temp;
for (int i = left + 1; i <= right; i++) {
temp = arr[i];
low = left;
high = i - 1;
while (low <= high) {
middle = (low + high) / 2;
if (arr[i] < arr[middle])
high = middle - 1;
else
low = middle + 1;
}
for (int j = i - 1; j >= low; j--)
arr[j + 1] = arr[j];
arr[low] = temp;
}
return arr;
}
}
7.3二分法排序复杂度分析
二分排序的期望复杂度是O(nlgn),最差情况是O(n^2)
总结:
各种排序方法比较
简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
影响排序效果的因素
因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
①待排序的记录数目n;
②记录的大小(规模);
③关键字的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥存储结构;
⑦时间和辅助空间复杂度等。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;若要求排序稳定,则可选用归并排序。