常用排序算法总结

1. 准备工作

为方便排序算法工作,首先编写了一段代码,用于随机生成一个指定大小的数组。具体代码如下:

import java.util.Random;

public class BuildArray {
	
	/**
	 * 调用此方法以得到一个随机的整型数组
	 * 
	 * @param N 随机数组的大小
	 * @return 返回指定大小的随机数组
	 */
	public static int[] getArray(int N) {
		if(N < 1) {
			return null;
		}
		int[] array = new int[N];
		int bound = 10 * N; // 随机数组元素大小的上界
		for (int i = 0; i < array.length; i++) {
			array[i] = new Random().nextInt(bound);
		}
		return array;
	}
	
	/**
	 * 调用此方法以打印整型数组array
	 * 
	 * @param array 需要打印的数组
	 */
	public static void printArray(int[] array) {
		System.out.print("[");
		for (int i : array) {
			System.out.print(i + " ");
		}
		System.out.println("]");
	}

}

2. 排序算法

为方便描述,以下的排序算法均按照从小到大排序。

2.1 冒泡排序

算法描述:

冒泡排序需要逐个比较相邻的未排序的元素,如果顺序错误,则交换它们的位置。每次遍历可以得到一个已排序的元素,并将它放置在未排序数组末端,未排序数组元素减一,当不存在未排序元素时,排序结束,具体操作如下。

1.从头开始比较相邻的元素,如果前着大于后者,则交换它们的位置。
2.依次向后对每一组相邻的元素执行步骤1,执行结束后,最大的元素应该在数组的末尾,将其归为“已排序”元素。
3.针对剩下的未排序的元素,重复执行上述1~2的步骤,直至所有元素排序完毕。

代码实现:

public class BubbleSort {
	
	/**
	 * 冒泡排序算法
	 * 
	 * @param array 待排序数组
	 */
	public static void bubbleSort(int[] array) {
		if (null == array) {
			return;
		}
		int size = array.length;
		for (int i = 0; i < size-1; i++) { // 重复以完成所有元素的排序
			for (int j = 0; j < size-i-1; j++) { // 遍历所有未排序的元素对
				if(array[j] > array[j+1]) { // 如果前面的元素大于后面的元素,就交换它们
					int temp = array[j];
					array[j] = array[j+1];
					array[j+1] = temp;
				}
			}
		}
	}

	public static void main(String[] args) {
		int[] array = BuildArray.getArray(15);
		System.out.print("排序前数组:");
		BuildArray.printArray(array);
		bubbleSort(array);
		System.out.print("排序后数组:");
		BuildArray.printArray(array);
	}

}

执行结果:

排序前数组:[81 8 102 77 48 17 78 92 84 33 59 71 1 13 42 ]
排序后数组:[1 8 13 17 33 42 48 59 71 77 78 81 84 92 102 ]

2.2 选择排序

算法描述:

选择排序的方法比较直观,只要遍历一次未排序的数组,就能找出其中的最小值,未排序元素-1。重复此操作,就可以完成对数组的排序,具体操作如下。

1.遍历数组,找出其中的最小值,并将其与首元素交换位置,至此未排序元素-1。
2.对未排序的数组重复操作1,直至所有元素排序完毕。

代码实现:

public class SelectionSort {

	/**
	 * 选择排序算法
	 * 
	 * @param array 待排序数组
	 */
	public static void selectionSort(int[] array) {
		if (null == array) {
			return;
		}
		int size = array.length;
		for (int i = 0; i < size-1; i++) { // 重复以完成所有元素的排序
			int min = i; // 最小元素的下标
			for (int j = i+1; j < size; j++) { // 遍历以找到最大的元素
				if (array[min] > array[j]) {
					min = j;
				}
			}
			int temp = array[i];
			array[i] = array[min];
			array[min] = temp;
		}
	}

	public static void main(String[] args) {
		int[] array = BuildArray.getArray(15);
		System.out.print("排序前数组:");
		BuildArray.printArray(array);
		selectionSort(array);
		System.out.print("排序后数组:");
		BuildArray.printArray(array);
	}

}

执行结果:

排序前数组:[68 63 9 3 121 51 100 39 55 92 141 136 106 66 123 ]
排序后数组:[3 9 39 51 55 63 66 68 92 100 106 121 123 136 141 ]

2.3 插入排序

算法描述:

2.1和2.2中的两种方法都是基于未排序数组的比较进行排序的,而插入算法与它们略有不同。插入算法首先会选择一个未排序的元素,通过将其与已排序的元素对比,找到合适的位置将其插入,并重复此操作以完成排序,具体操作如下。

1.首先认为数组的首元素是已排序元素;
2.向后选择第一个未排序的元素(在这是指第二个元素);
3.记住此元素(记为A)的值
4.让其与前面的元素(记为B)进行比较,如果A较小,则将B后移一位,然后A继续向前比较;
5.直到比较结果为A较大,则已经找到合适的位置,比较结束,将A插入。
6.依此向后选择第一个未排序的元素,执行步骤3~5,直至所有元素排序完毕。

代码实现:

public class InsertionSort {
	
	/**
	 * 插入排序算法
	 * 
	 * @param array 待排序数组
	 */
	public static void insertionSort(int[] array) {
		if (null == array) {
			return;
		}
		int size = array.length;
		for (int i = 1; i < size; i++) { // 重复以完成所有元素的排序
			int temp = array[i];
			int j = i;
			while (j > 0 && temp < array[j-1]) { // 如果小于前面的元素,就将前面的元素后移一位
				array[j] = array[j-1];
				j--;
			}
			array[j] = temp;  // 正确的位置插入元素
		}
	}

	public static void main(String[] args) {
		int[] array = BuildArray.getArray(15);
		System.out.print("排序前数组:");
		BuildArray.printArray(array);
		insertionSort(array);
		System.out.print("排序后数组:");
		BuildArray.printArray(array);
	}
	
}

执行结果:

排序前数组:[95 24 3 118 2 62 81 110 51 146 82 2 25 63 108 ]
排序后数组:[2 2 3 24 25 51 62 63 81 82 95 108 110 118 146 ]

2.4 希尔排序

算法描述:

希尔排序是简单插入排序的改进算法,该算法是冲破二次时间屏障的第一批算法之一,它通过比较相距一定间隔的元素来工作,因此也被称作缩小增量排序。希尔排序算法的核心在于增量序列的选取,增量序列里的元素依次代表了每轮比较的数据之间的间隔,这里选择以gap = gap/2的方式(即增量序列为{n, n/2, n/4, …, 1})实现希尔排序,具体操作如下。

1.设定初始增量gap = array.length / 2;
2.以增量gap对整个数组进行插入排序;
3.修改增量gap = gap/2;
4.重复操作2~3,直至gap = 0;

代码实现:

public class ShellSort {
	
	/**
	 * 希尔排序算法
	 * 
	 * @param array 待排序数组
	 */
	public static void shellSort(int[] array) {
		if (null == array) {
			return;
		}
		int size = array.length;
		for (int gap = size/2; gap > 0; gap /= 2) { // 构建增量序列
			for (int i = gap; i < size; i++) { // 以增量gap对整个数组进行插入排序
				int temp = array[i];
				int j = i;
				while (j >= gap && temp < array[j-gap]) {
					array[j] = array[j-gap];
					j -= gap;
				}
				array[j] = temp;
			}
		}
	}

	public static void main(String[] args) {
		int[] array = BuildArray.getArray(15);
		System.out.print("排序前数组:");
		BuildArray.printArray(array);
		shellSort(array);
		System.out.print("排序后数组:");
		BuildArray.printArray(array);
	}
	
}

执行结果:

排序前数组:[121 141 81 141 46 56 142 105 68 145 72 81 48 94 13 ]
排序后数组:[13 46 48 56 68 72 81 81 94 105 121 141 141 142 145 ]

2.5 归并排序

算法描述:

在谈归并排序之前,先来了解一个概念——分治策略:为了解决一个规模较大的问题,我们可以将其分解为两个子问题,并借助递归分别得到它们的解,然后将子问题的解合并成原问题的解。

归并排序就采用了分治策略,它首先将需要排序的序列分割成两个子序列,并对它们分别进行归并排序,最后将得到的有序子序列进行合并,得到最终已排序的序列,具体操作如下。

1.把长度为n的原序列分成两个长度为n/2的子序列。
2.对两个子序列进行归并排序,得到已排序的两个子序列。
3.合并已排序的子序列。

代码实现:

import java.util.Arrays;

public class MergeSort {

	/**
	 * 归并排序算法
	 * 
	 * @param array 待排序数组
	 * @return 排序后的数组
	 */
	public static int[] mergeSort(int[] array) {
		if (null == array) {
			return null;
		}
		int size = array.length;
		if (1 == size) {
			return array;
		}
		// 分割数组
		int[] left = Arrays.copyOfRange(array, 0, size/2);
		int[] right = Arrays.copyOfRange(array, size/2, size);
		// 递归调用归并排序算法
		left = mergeSort(left);
		right = mergeSort(right);
		return merge(left, right); // 合并已排序的子数组
	}
	
	/**
	 * 合并已排序的两个子序列
	 * 
	 * @param left 左子序列
	 * @param right 右子序列
	 * @return 合并后的序列
	 */
	private static int[] merge(int[] left, int[] right) {
		int leftIndex = 0;
		int rightIndex = 0;
		int leftBound = left.length;
		int rightBound = right.length;
		int[] mergedArray = new int[leftBound+rightBound];
		
		for (int i = 0; i < mergedArray.length; i++) {
			if (leftIndex == leftBound) { // 左子数组的元素已经合并完毕
				while (rightIndex < rightBound) {
					mergedArray[i++] = right[rightIndex++];
				}
				break;
			}
			if (rightIndex == rightBound) { // 右子数组的元素已经合并完毕
				while (leftIndex < leftBound) {
					mergedArray[i++] = left[leftIndex++];
				}
				break;
			}
			if (left[leftIndex] < right[rightIndex]) { // 至此,左右子数组的元素均未合并完毕
				mergedArray[i] = left[leftIndex++];
			} else {
				mergedArray[i] = right[rightIndex++];
			}
		}
		
		return mergedArray;
	}

	public static void main(String[] args) {		
		int[] originalArray = BuildArray.getArray(15);
		System.out.print("排序前数组:");
		BuildArray.printArray(originalArray);
		int[] sortedArray = mergeSort(originalArray);
		System.out.print("排序后数组:");
		BuildArray.printArray(sortedArray);
	}
	
}

执行结果:

排序前数组:[96 122 108 92 28 26 51 91 57 57 80 73 124 119 66 ]
排序后数组:[26 28 51 57 57 66 73 80 91 92 96 108 119 122 124 ]

2.6 快速排序

算法描述:

快速排序是分治策略的又一典型应用,与归并排序不同的是,归并排序算法的主要计算量集中于有序子序列的归并,而快速排序算法正好相反,它的主要计算量集中于将原问题划分为两个子问题,具体操作如下。

1.选取数组的某一位置作为轴点。
2.调整数组,以使轴点左边的元素均小于轴点的元素,轴点右边的元素均大于轴点的元素。
3.递归的对轴点左右的数组进行快速排序。

轴点的选取对快速排序算法性能的影响较大,如果只是单纯的选择数组的第一个元素作为轴点,再输入非随机的情况下,可能很难获得较好的性能。轴点最好的选择是数组中值所在的位置,但不幸的是,这很难算出,且会拉低整个排序算法的性能基于此,我们一般会选取左端、右端、中心位置的元素,以它们的中值所在的位置作为轴点。在代码实现中,构造带有指定轴点的数组是关键,其具体操作如下。

1.选取轴点,将其放置在最右端;
2.设最左端下标为left,最右端为right;
3.令i=left,j=right-1;
4.如果array[1]大于轴点处的元素,交换i和j处的元素,j–;
5.如果array[1]小于轴点处的元素,i++;
6.重复操作5~6,直至i>j(同样的,j也可以按类似的规则向前扫描);
7.交换i和right处的元素。

代码实现:

public class QuickSort {
	
	/**
	 * 快速排序算法
	 * 
	 * @param array 待排序数组
	 * @param left 待排序元素的左边界
	 * @param right 待排序元素的右边界
	 */
	public static void quickSort(int[] array, int left, int right) {
		if (null == array || left >= right) {
			return;
		}
		median(array, left, right); // 至此,轴点元素已经被放置在末尾,且array[0]小于等于轴点的元素。
		int i = left + 1;
		int j = right -1;
		
		while (i <= j) {
			if (array[i] > array[right]) {
				swap(array, i, j);
				j--;
			} else {
				i++;
			}
		}
		
		swap(array, i, right); // 至此,数组被轴点分成左右两部分
		quickSort(array, left, i-1);
		quickSort(array, i+1, right);
	}
	
	/**
	 * 选取数组轴点的元素,并将其放置在数组的末端
	 * 
	 * @param array 待排序数组
	 * @param left 左边界
	 * @param right 右边界
	 */
	private static void median(int[] array, int left, int right) {
		int center = (left + right) / 2;
		if (array[center] < array[left]) {
			swap(array, center, left);
		} // 至此,array[left] <= array[center]
		if (array[left] > array[right]) {
			swap(array, right, left);
		} // 至此,array[right]为三者的中值,且array[left]小于等于此中值
	}
	
	/**
	 * 给定指定数组array和下标index0、index1, 交换给定下标处的数组元素
	 * 
	 * @param array 待交换的数组
	 * @param index0 数组下标1
	 * @param index1 数组下标2
	 */
	private static void swap(int[] array, int index0, int index1) {
		int temp = array[index0];
		array[index0] = array[index1];
		array[index1] = temp;
	}
	
	public static void main(String[] args) {
		int[] array = BuildArray.getArray(15);
		System.out.print("排序前数组:");
		BuildArray.printArray(array);
		quickSort(array, 0, array.length-1);
		System.out.print("排序后数组:");
		BuildArray.printArray(array);
	}

}

执行结果:

排序前数组:[44 139 15 12 4 146 37 9 27 51 53 27 100 5 140 ]
排序后数组:[4 5 9 12 15 27 27 37 44 51 53 100 139 140 146 ]

3. 复杂度分析

排序方法平均时间复杂度最坏时间复杂度空间复杂度
冒泡排序O(n2)O(n2)O(1)
选择排序O(n2)O(n2)O(1)
插入排序O(n2)O(n2)O(1)
希尔排序因增量序列不同而有所不同因增量序列不同而有所不同(如Hibbard增量为O(n3/2))O(1)
归并排序O(n*long2n)O(n*long2n)O(n)
快速排序O(n*long2n)O(n2)O(long2n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值