【数据结构与算法】Java 常用几种排序算法

术语释义

  1. 稳定性
    稳定性是假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,则称这种排序算法是稳定的;否则称为不稳定的。

  2. 平均时间复杂度
    算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况。

  3. 空间复杂度
    一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。

插入排序(直接插入排序、二分法插入排序、希尔排序)

  1. 直接插入排序

基本思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排好序的序列的合适位置**(从后向前找)**,直到全部插入排序完为止。


public class InsertSort {
	
	public static void main(String args[]) {
		int[] a = {20,1,32,6,9,0,12,3,16};
		System.out.print("排序前: ");
		for(int i=0; i<a.length; i++) {
			System.out.print(a[i] + " ");
		}
		
		System.out.println();     //换行
		
		//直接插入排序
		for(int i=1; i<a.length; i++) {
			int temp = a[i];
			int j;
			for(j=i-1; j>=0; j--) {
				if(a[j] > temp) {
					a[j+1] = a[j];
				} else {
					break;
				}
			}
			a[j+1] = temp;
			}
		 
		    System.out.print("排序后: ");
		       for(int i=0; i<a.length; i++) {
			       System.out.print(a[i] + " ");
		    }
		
	}

}


结果为:
排序前: 20 1 32 6 9 0 12 3 16
排序后: 0 1 3 6 9 12 16 20 32

稳定性:稳定
平均时间复杂度:O(n2)
空间复杂度:O(1)
  1. 二分法插入排序

基本思想:二分法插入排序的思想和直接插入一样。但寻找合适位置插入的方式不同,二分法可减少比较次数。

public class BinaryInsertSort {
	
	public static void main(String args[]) {
		int[] a = {12,3,1,34,5,9,32,10};
		System.out.print("排序前: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
		   
		   System.out.println();   //换行
		   
		   //二分法插入排序
		   sort(a);
		   System.out.print("排序后: ");
		      for(int i=0; i<a.length; i++) {
		    	  System.out.print(a[i] + " ");
		      }
	}

	private static void sort(int[] a) {
		for(int i=0; i<a.length; i++) {
			
			int temp = a[i];
			int left = 0;
			int right = i-1;
			int mid = 0;
			
			while(left <= right) {
				mid = (left + right)/2;
				if(temp < a[mid]) {
					right = mid-1;
				}  else {
					left = mid+1;
				}
			}
			for(int j=i-1; j>=left; j--) {
				a[j+1] = a[j];
			}
			if(left != i) {
				a[left] = temp;
			}
		}
		
	}
}

结果为:
排序前: 12 3 1 34 5 9 32 10
排序后: 1 3 5 9 10 12 32 34

稳定性:稳定
平均时间复杂度:O(n2)
空间复杂度:O(1)
  1. 希尔排序

基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组,所有距离为d1的倍数的记录放在同一个组中。
实质:分组插入的方法


public class ShellSort {
	
	public static void main(String args[]) {
		int[] a = {2,12,3,45,30,99,1,29};
		System.out.print("排序前: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
		   
		   System.out.println();    //换行
		   
		   //希尔排序
		   int d = a.length;
		   while(true) {
			   d = d/2;
			   for(int x=0; x<d; x++) {
				   for(int i=x+d; i<a.length; i=i+d) {
					   int temp = a[i];
					   int j;
					   for(j=i-d; j>=0 && a[j]>temp; j=j-d) {
						   a[j+d] = a[j];
					   }
					   a[j+d] = temp;
				   }
			   }
			   
			   if(d == 1) {
				   break;
			   }
		   }
		   System.out.print("排序后: ");
		      for(int i=0; i<a.length; i++) {
		    	  System.out.print(a[i] + " ");
		      }
	}

}

结果为:
排序前: 2 12 3 45 30 99 1 29
排序后: 1 2 3 12 29 30 45 99

稳定性:不稳定
平均时间复杂度:O
空间复杂度:O(1)

选择排序(简单选择排序、堆排序)

  1. 简单选择排序

基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。


public class SelectionSort {
	
	public static void main(String args[]) {
		int[] a = {2,31,4,67,5,1,60,8,21};
		System.out.print("排序前: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
		   
		   System.out.println();  //换行
		   
		   //选择排序
		   for(int i=0; i<a.length; i++) {
			   int min = a[i];
			   int n = i;
			   for(int j=i+1; j<a.length; j++) {
				   if(a[j] < min) {
					   min = a[j];
					   n = j;
				   }
			   }
			   a[n] = a[i];
			   a[i] = min;
			   
		   }
		   System.out.print("排序后: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
	}

}

结果为:
排序前: 2 31 4 67 5 1 60 8 21
排序后: 1 2 4 5 8 21 31 60 67

稳定性:不稳定
平均时间复杂度:O(n2)
空间复杂度:O(1)
  1. 堆排序

基本思想:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个 堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对 它们作交换,最后得到有n个节点的有序序列。

import java.util.Arrays;

public class HeapSort {
	
	public static void main(String args[]) {
		int[] a = {1,23,4,7,2,34,56,65,43};
		int arrayLength = a.length;
		   //循环建堆
		   for(int i=0; i<arrayLength-1; i++) {
			   //建堆
			   buildMaxHeap(a,arrayLength-1-i);
			   //交换堆顶和最后一个元素
			   swap(a,0,arrayLength-1-i);
			   System.out.println(Arrays.toString(a));
		   }
	}

	//为data数组从0到lastIndex建大项堆
	private static void buildMaxHeap(int[] data, int lastIndex) {
				// TODO Auto-generated method stub
		         //从lastIndex处节点(最后一个节点)的父节点开始 
		         for(int i=(lastIndex-1)/2;i>=0;i--){
		             //k保存正在判断的节点 
		             int k=i;
		             //如果当前k节点的子节点存在  
		             while(k*2+1<=lastIndex){
		                 //k节点的左子节点的索引 
                         int biggerIndex=2*k+1;
						//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
		                 if(biggerIndex<lastIndex){  
		                     //若果右子节点的值较大  
		                     if(data[biggerIndex]<data[biggerIndex+1]){  
		                         //biggerIndex总是记录较大子节点的索引  
		                         biggerIndex++;  
		                     }  
		                }  
		                 //如果k节点的值小于其较大的子节点的值  
		                 if(data[k]<data[biggerIndex]){  
		                     //交换他们  
		                     swap(data,k,biggerIndex);  
		                     //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值  
		                     k=biggerIndex;  
		                 }else{  
		                     break;  
		                 }  
		             }
		         }
		     }
	
	//交换
	private static void swap(int[] data, int i, int j) {
		// TODO Auto-generated method stub
		int temp = data[i];
		data[i] = data[j];
		data[j] = temp;
		
	}
}

结果为:
[1, 43, 56, 23, 2, 34, 4, 7, 65]
[7, 43, 34, 23, 2, 1, 4, 56, 65]
[4, 23, 34, 7, 2, 1, 43, 56, 65]
[1, 23, 4, 7, 2, 34, 43, 56, 65]
[2, 7, 4, 1, 23, 34, 43, 56, 65]
[1, 2, 4, 7, 23, 34, 43, 56, 65]
[1, 2, 4, 7, 23, 34, 43, 56, 65]
[1, 2, 4, 7, 23, 34, 43, 56, 65]

稳定性:不稳定
平均时间复杂度:O(n*log2n)
空间复杂度:O(1)

交换排序(冒泡排序、快速排序)

  1. 冒泡排序

基本思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序完为止。

public class BubbleSort {
	public static void main(String args[]) {
		int[] a = {2,31,3,43,45,6,0,21,1};
		System.out.print("排序前: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
		   
		   System.out.println();   //换行
		   
		   //冒泡排序
		   for(int i=0; i<a.length; i++) {
			   for(int j=0; j<a.length-i-1; j++) {
				   if(a[j] > a[j+1]) {
					   int temp = a[j];
					   a[j] = a[j+1];
					   a[j+1] = temp;
				   }
			   }
		   }
		   System.out.print("排序后: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
	}

}

结果为:
排序前: 2 31 3 43 45 6 0 21 1
排序后: 0 1 2 3 6 21 31 43 45

稳定性:稳定
平均时间复杂度:O(n2)
空间复杂度:O(1)
  1. 快速排序

基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。


public class QuickSort {
	public static void main(String args[]) {
		int[] a = {2,10,8,5,3,91,20,35};
		System.out.print("排序前: ");
		   for(int i=0; i<a.length; i++) {
			   System.out.print(a[i] + " ");
		   }
		   
		   System.out.println();  //换行
		   
		   //快速排序
		   quick(a);
		   System.out.print("排序后:");
		      for(int i=0; i<a.length; i++) {
		    	  System.out.print(a[i] + " ");
		      }
	}

	private static void quick(int[] a) {
		if(a.length>0) {
			quickSort(a,0,a.length-1);
		}
	}

	private static void quickSort(int[] a, int low, int high) {
		
		//防止堆栈溢出异常
		if(low < high) {
			int middle = getMiddle(a,low,high);
			quickSort(a,0,middle-1);
			quickSort(a,middle+1,high);
		}
	}

	private static int getMiddle(int[] a, int low, int high) {
		// TODO Auto-generated method stub
		int temp = a[low];   //基准元素
		
		//找到比基准元素小的元素位置
		while(low < high) {
			while(low < high && a[high] >= temp) {
				high--;
			}
			a[low] = a[high];
			while(low < high && a[low] <= temp) {
				low++;
			}
			a[high] = a[low];
		}
		a[low] = temp;
		return low;
	}

}

结果为:
排序前: 2 10 8 5 3 91 20 35
排序后:2 3 5 8 10 20 35 91

稳定性:不稳定
平均时间复杂度:O(n*log2n)
空间复杂度:O(log2n)~O(n)

归并排序(归并排序)

基本思想:将两个或两个以上有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。


public class MergeSort {
	
	public static void main(String args[]) {
		int[] a = {2,12,3,29,43,67,8,0,1};
		System.out.print("排序前: ");
		for(int i=0; i<a.length; i++) {
			System.out.print(a[i] + " ");
		}
		
		System.out.println();  //换行
		
		//归并排序
		mergeSort(a,0,a.length-1);
		System.out.print("排序后: ");
		for(int i=0; i<a.length; i++) {
			System.out.print(a[i] + " ");
		}
		
	}

	private static void mergeSort(int[] a, int left, int right) {
		if(left < right) {
			int middle =(left + right)/2;
			//对左边递归
			mergeSort(a,left,middle);
			//对右边递归
			mergeSort(a,middle+1,right);
			//合并
			merge(a,left,middle,right);
		}
		
	}

	private static void merge(int[] a, int left, int middle, int right) {
		int[] temArr = new int[a.length];
		int mid = middle + 1;   //右边的起始位置
		int tmp = left;
		int third = tmp;
		while(left <= middle && mid <= right) {
			//从两个数组中选较小的数放入中间数组
			if(a[left] <= a[mid]) {
				temArr[third++] = a[left++];
			} else {
				temArr[third++] = a[mid++];
			}
		}
		//把剩余部分放入中间数组
		while(left <= middle) {
			temArr[third++] = a[left++];
		}
		while(mid <= right) {
			temArr[third++] = a[mid++];
		}
		//把中间数组复制回原数组
		while(tmp <= right) {
			a[tmp] = temArr[tmp++];
		}
	}

}

结果为:
排序前: 2 12 3 29 43 67 8 0 1
排序后: 0 1 2 3 8 12 29 43 67

稳定性:稳定
平均时间复杂度:O(nlogn)
空间复杂度:O(1)

猜你喜欢

转载自blog.csdn.net/weixin_39548940/article/details/100915146