一、算法分类
常见排序算法可以分为两大类:
非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
相关概念:
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
1、计数排序 —— 基础桶排序(时间快,空间大)
计数排序(Counting Sort),工作的原理是将数组分到有限数量的桶子里。
这个算法就好比有11个桶,编号从0~10。每出现一个数,就将对应编号的桶中的放一个小旗子,最后只要数数每个桶中有几个小旗子就OK了。例如2号桶中有1个小旗子,表示2出现了一次;3号桶中有1个小旗子,表示3出现了一次;5号桶中有2个小旗子,表示5出现了两次;8号桶中有1个小旗子,表示8出现了一次。
代码实现如下:
/**
* 桶排序:浪费空间
*/
@Test
public void bucketSorting() {
int[] bucketNums = new int[10000];
int[] nums = new int[]{17, 200, 1, 5000, 700, 5000, 9999};
List resultNums = new ArrayList();
for (int num : nums) {
bucketNums[num] = bucketNums[num] + 1;
}
for (int i = 0; i < bucketNums.length; i++) {
if (bucketNums[i] > 0) {
while (--bucketNums[i] >= 0) {
resultNums.add(i);
}
}
}
System.out.println(resultNums);
}
根据代码,我们用大写字母O来表示时间复杂度,M为桶的个数,N为待排序数的个数,最终桶排序的时间复杂度为O(M+N)。
2、冒泡排序(节约空间,浪费时间)
冒泡排序(Bubble Sort),它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。
代码实现如下:
/**
* 冒泡排序:浪费时间
*/
@Test
public void bubbleSort() {
int[] nums = new int[]{17, 200, 1, 5000, 700, 5000, 9999};
int num;
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length - i; j++) {
if (nums[i] > nums[j]) {
num = nums[j];
nums[j] = nums[i];
nums[i] = num;
}
}
}
System.out.println(Arrays.toString(nums));
}
根据代码,我们用大写字母O来表示时间复杂度,N为待排序数的个数,最终冒泡排序的时间复杂度为O(N^2)。
3、快速排序(时间与空间平衡)
快速排序(Quicksort)是对冒泡排序的一种改进。
它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
代码实现如下:
/**
* @param arr 排序集合
* @param low 开始位
* @param high 结束位
*/
private void quickSort(int[] arr, int low, int high) {
int i = low;
int j = high;
int t;
if (low > high) {
return;
}
// temp就是基准位
int temp = arr[low];
while (i < j) {
// 先看右边,依次往左递减
while (temp <= arr[j] && i < j) {
j--;
}
// 再看左边,依次往右递增
while (temp >= arr[i] && i < j) {
i++;
}
// 如果满足条件则交换
if (i < j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
// 最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
// 递归调用左半数组
quickSort(arr, low, j - 1);
// 递归调用右半数组
quickSort(arr, j + 1, high);
}
/**
* 快速排序
*/
@Test
public void quickSort() {
int[] arr = {1, 7, 2, 4, 7, 62, 3, 4, 2, 10, 8, 9, 19};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
根据代码,我们用大写字母O来表示时间复杂度,N为待排序数的个数,最坏的情况就是冒泡排序的时间复杂度为O(N^2),每个都要判断一次,平均的时间复杂度为O(NlogN)。
4、选择排序(Selection Sort)
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
public static void main(String[] args) {
int[] arrs = {1, 7, 2, 4, 7, 62, 3, 4, 2, 10, 8, 9, 19};
for(int i = 0; i < arrs.length; i++) {
int min = arrs[i];
int minIndex = i;
for(int j = i; j < arrs.length; j++) {
if(min > arrs[j]) {
min = arrs[j];
minIndex = j;
}
}
if(minIndex != i) {
arrs[minIndex] = arrs[i];
arrs[i] = min;
}
}
System.out.println(Arrays.toString(arrs));
}
5、插入排序(Insertion Sort)
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
public static void main(String[] args) {
int[] arrs = {7, 2, 4, 7, 62, 3, 4, 2, 1, 10, 8, 9, 19};
for(int i = 0; i < arrs.length - 1; i++) {
int current = arrs[i+1];
int index = i;
while(index >= 0 && current < arrs[index]) {
arrs[index + 1] = arrs[index];
index--;
}
arrs[index+1] = current;
}
System.out.println(Arrays.toString(arrs));
}
6、归并排序(Merge Sort)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
7、桶排序(Bucket Sort)
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
8、基数排序(Radix Sort)
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
9、希尔排序(Shell Sort)
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
10、堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
图片文字引用博客:十大经典排序算法(动图演示)