1. 直接插入排序
假设有一组待排序数据 {4, 8, 2, 1, 45, 13, 2}。
算法思路:直接插入排序就是像整理扑克牌一样,把待排序的元素插入到已经排好序的元素中。(每次拿出无序区间中的第一个数,插入到有序区间的合适的位置)
具体实现:
当插入第 i ( i>=1 ) 个元素时,前面的 array[0],arr[1],... arr[i-1] 已经排好序,此时 arr[i] 与 arr[i-1],arr[i-2],... arr[0]顺序进行比较,找到插入位置就将 arr[i] 插入,原来位置上的元素后移。
代码:
public static void insertSort(int[] arr){
int len = arr.length;
for(int i=1; i<len; i++){
int tmp = arr[i];//临时变量存放arr[i]的值
int j;
for(j=i-1; j>=0; j--){
if(tmp>=arr[j]){
//当前有序
break;
}else {
//开始移动赋值
arr[j+1] = arr[j];
}
}
arr[j+1] = tmp;
}
}
分析: 时间复杂度 O(n^2),空间复杂度 O(1),稳定。
2. 希尔排序(是对插入排序的优化)
算法思路:希尔排序又叫缩小增量排序。是对插入排序的优化,是将待排序序列先分成 drr 组,再分别对每组进行直接插入排序。一轮之后,减小 drr ,继续对每组进行直接插入排序,... 一直到 drr=1,进行最后一轮直接插入排序。这样做的目的是为了让序列趋近于有序,加快排序。
至于这里的 drr 的取法有多种,不同的取法算法的性能也会有所差异,这里主要选取一种:
第1轮:drr = (len/3向下取整)+1;
第2轮:drr = (drr/3向下取整)+1;
. . .
一直到 drr = 1
具体实现:
代码:
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = new int[]{5,4,9,2,1,7,1,3,2,10};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void shellSort(int[] arr){
int[] drr = new int[]{4,2,1};
for(int i=0; i<drr.length; i++){
shell(arr,drr[i]);
}
}
public static void shell(int[] arr, int gap){
for(int i=gap; i<arr.length; i++){
int tmp = arr[i];
int j = 0;
for(j=i-gap; j>=0; j-=gap){
if(tmp >= arr[j]){
break;
}else {
arr[j+gap] = arr[j];
}
}
arr[j+gap] = tmp;
}
}
}
分析: 最坏时间复杂度 O(n^2),最好时间复杂度 O(n^1.3),空间复杂度O(1),不稳定。
3. 选择排序
算法思路:选择排序是每一次从待排序元素中选出最小(或最大)的一个元素,存放在序列的起始位置。直到要排序的元素全部排完。
具体实现:
假设有一组待排序数据 {4, 8, 2, 1, 45, 13, 2}
定义变量 i 来遍历序列,j = i+1;
如果 arr[j] < arr[i] 就交换位置,否则继续 j++。
代码:
public static void selectSort(int[] arr){
for(int i=0; i<arr.length; i++){
for(int j=i+1; j<arr.length; j++){
if(arr[j] < arr[i]){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
分析:时间复杂度O(n^2),空间复杂度O(1),不稳定。
4. 冒泡排序
算法思路:对待排序元素相邻的元素两两比较,如果顺序错误,就进行交换。(把最大的数挤到了最后)
代码:
public static void bubbleSort(int[] arr){
for(int i=0; i<arr.length-1; i++){ //趟数
boolean flag = false;
for(int j=0; j<arr.length-1-i; j++){
if(arr[j+1] < arr[j]){
int tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
flag = true;
}
}
if(flag == false){
break;
}
}
}
分析:时间复杂度O(n^2),优化后(加了flag标记)时间复杂度为O(n),空间复杂度O(1),稳定。
5. 快速排序
算法思路:从待排序序列中任取一个元素作为基准值,按基准值将待排序序列分割成两个子序列。比基准值小的元素放到基准值左边,比基准值大的元素放到基准值右边。然后左右序列重复该过程,直到所有元素都排列在相应位置为止。
具体实现:下面方法的基准值总是取序列的第一个元素。实际上,基准值的取法有多种,取法不同,算法的性能也会有差异。
代码:
import java.util.Arrays;
import java.util.Random;
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[100];
Random random = new Random();
for(int i=0; i<arr.length; i++){
arr[i] = random.nextInt()/10000000;
}
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr, int start, int end){
int low = start;
int high = end;
int key = arr[low];
while(low<high){
while(low < high && arr[high] >= key){
high--;
}
if(low >= high){
break;
}else {
arr[low] = arr[high];
}
while(low < high && arr[low] <= key){
low++;
}
if(low >= high){
break;
}else {
arr[high] = arr[low];
}
}
arr[low] = key;
if(low>start+1) quickSort2(arr,start,low-1);//对左边继续排序
if(high<end-1) quickSort2(arr,high+1,end);//对右边继续排序
}
}
分析:时间复杂度O(N*logN),空间复杂度O(logN),不稳定。另外,采用快排,如果序列已经有序,此时的分割是一个非常不好的分割。因为每次划分只能使待排序序列减一,并没有将序列分成两部分。此时为最坏情况,快排沦为冒泡排序,时间复杂度为O(n^2)。为了避免这种情况,就要对快排进行优化,使得每次选择的基准值能较为均匀地把序列分成两部分。下面有两种方法:
方法1:随机选取基准(取待排序序列中任意一个元素作为基准)
这是一种相对安全的策略。由于基准值是随机从序列中选取的,那么产生的分割也不会总是出现劣质的分割。但当整个序列全相等时,仍然是最坏情况,时间复杂度是O(n^2)。但实际上,随机化快速排序得到理论最坏情况的可能性很低,仅为1/(2^n),所以随机化快速排序可以对于绝大多数数据达到O(N*logN)的期望时间复杂度。
方法2:三数取中法(也就是取左端、中间、右端三个数,进行排序,将中间数作为基准值)