博客
关于我
9. 排序算法 - java实现
阅读量:390 次
发布时间:2019-03-05

本文共 15039 字,大约阅读时间需要 50 分钟。

文章目录

1. 基本概念说明

1.1 术语说明

  • 稳定:a=b, a在b前面,排序后a仍然在b前面。
  • 不稳定:a=b,a在b前面,排序后a可能在b前面,也可能在b后面。
  • 内排序:需要排序的数据全部放在内存中。
  • 外排序:数据太大,内存放不下,有一部分保存在外存中。

1.2 各种排序算法的复杂度

排序算法 平均情况 最好情况 最差情况 辅助空间 稳定性
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 稳定
快速排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( l o g n ) − O ( n ) O(logn)-O(n) O(logn)O(n) 不稳定
简单选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 稳定
堆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( 1 ) O(1) O(1) 不稳定
直接插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 稳定
希尔排序 O ( n l o g n ) − O ( n 2 ) O(nlogn)-O(n^2) O(nlogn)O(n2) O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1) 不稳定
归并排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n) 稳定

1.3 应用到的基本方法

/**	 * @Description: 交换下标i和j元素的值	 * @param array	 * @param i	 * @param j	 */	public static void swap(int[] array, int i, int j) {   		int temp = array[i];		array[i] = array[j];		array[j] = temp;	}	/**	 * @Description: 打印数组array	 * @param array	 */	public static void print(int[] array) {   		for (int i : array) {   			System.out.print(i + ",");		}		System.out.println(" ");	}

2. 冒泡排序

2.1 基本描述

属于比较交换排序,基本思想是将小数或大数从底部排到顶部,类似冒泡。

2.2 图解

在这里插入图片描述

2.3 实现代码

public static int[] bubbleSort(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		for (int i = 0; i < array.length; i++) {   			for (int j = 0; j < array.length - 1 - i; j++) {   				if (array[j] > array[j + 1]) {   					swap(array, j, j + 1);				}			}		}		return array;	}	public static int[] bubbleSort2(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		for (int i = 0; i < array.length; i++) {   			for (int j = array.length - 1; j > i; j--) {   				if (array[j] < array[j - 1]) {   					swap(array, j, j - 1);				}			}		}		return array;	}

2.4 改进方法

public static int[] bubbleSort1(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		boolean flag = true;		// 如果没有交换,则表明接下来的数据都是有序的,退出循环		for (int i = 0; i < array.length && flag; i++) {    			flag = false;			for (int j = 0; j < array.length - 1 - i; j++) {   				if (array[j] > array[j + 1]) {   					swap(array, j, j + 1);					flag = true;				}			}		}		return array;	}

3. 快速排序

3.1 基本描述

  • 对冒泡排序的改进,增大了数据比较和移动的距离,将较大的数直接移到后面,较小的数直接移到前面,从而减少总的比较和移动次数。
  • 运用分治思想,把一个串list变成两个子串sub-list,选定一个基准值pivot,所有比pivot小的数移到较小的列,所有比pivot大的数移到较大的列。

3.2 图解1

在这里插入图片描述

3.3 图解2

在这里插入图片描述

3.4 实现代码

public static int[] quickSort(int[] array, int low, int high) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		if (low < high) {   			int pivotIndex = partition(array, low, high);			quickSort(array, low, pivotIndex - 1);			quickSort(array, pivotIndex + 1, high);		}		return array;	}	public static int partition(int[] array, int low, int high) {   		int pivot = array[low];		while (low < high) {   			while (low < high && array[high] >= pivot) {   				high--;			}			swap(array, low, high);			while (low < high && array[low] <= pivot) {   				low++;			}			swap(array, low, high);		}		return low;	}

3.5 快速排序优化

3.5.1 基准值pivot的优化

固定取第一个作为基准值pivot(其实无论固定取哪一个都一样)是性能的瓶颈,因为这个数很有可能是比较大或者比较小的数,合理选择是pivot尽量靠近中间值。有人提出三数取中法:取数列左中右三个数(也可以随机取),然后排序,取排序后中间的数。

partition方法改造后的代码:

public static int partition(int[] array, int low, int high) {   		int mid = low + (high - low) / 2;		// 交换后左端比较小		if (array[low] > array[high]) {   			swap(array, low, high);		}		// 交换后保证最大值在右边		if (array[mid] > array[high]) {   			swap(array, mid, high);		}		// 交换后保证中间值在左边		if (array[mid] > array[low]) {   			swap(array, low, mid);		}		int pivot = array[low]; // 取第1位作为pivot,		while (low < high) {   			while (low < high && array[high] >= pivot) {   				high--;			}			swap(array, low, high);			while (low < high && array[low] <= pivot) {   				low++;			}			swap(array, low, high);		}		return low;	}

3.5.2 优化不必要的交换

观察50的下标变化,从0->9->1->8->5,最后的位置是5,可以采用替换。

public static int partition(int[] array, int low, int high) {   		int mid = low + (high - low) / 2;		// 交换后左端比较小		if (array[low] > array[high]) {   			swap(array, low, high);		}		// 交换后保证最大值在右边		if (array[mid] > array[high]) {   			swap(array, mid, high);		}		// 交换后保证中间值在左边		if (array[mid] > array[low]) {   			swap(array, low, mid);		}		int pivot = array[low]; // 取第1个位pivot		while (low < high) {   			while (low < high && array[high] >= pivot) {   				high--;			}			// swap(array, low, high);			array[low] = array[high];// 采用替换的操作,而不是交换			while (low < high && array[low] <= pivot) {   				low++;			}			// swap(array, low, high);			array[high] = array[low];// 采用替换的操作,而不是交换		}		array[low] = pivot;		return low;	}

3.5.3 优化小数组的方案

快速排序运用到递归,对于大数组而言,影响不大,但是对于小数组,简单插入排序性能更好。

public static int[] quickSort(int[] array, int low, int high) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		if (high - low > MAX_LENGTH_INSERTION_SORT) {    // 有人认为7,有人认为50			int pivotIndex = partition(array, low, high);			quickSort(array, low, pivotIndex - 1);			quickSort(array, pivotIndex + 1, high);		} else {   			insertionSort(array, low, high);		}		return array;	}

3.5.4 优化递归操作

递归耗用栈,quickSort尾部有两次递归,如果待排序的序列划分极端不平衡,递归深度趋近与n,而不是 log ⁡ 2 n \log_2{n} log2n

public static int[] quickSort1(int[] array, int low, int high) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		if (high - low > MAX_LENGTH_INSERTION_SORT) {    // 有人认为7,有人认为50			while (low < high) {   				int pivotIndex = partition(array, low, high);				quickSort1(array, low, pivotIndex - 1);				low = pivotIndex + 1;			}		} else {   			insertionSort(array, low, high);		}		return array;	}

第一次递归后,low就没用,此时low = pivot + 1,再来一次循环做partition,效果等同于quickSort(array,pivot +1 ,high)。

3.7 最终代码

public class QuickSort {   	// 有人认为7比较合适,也有人认为50比较合适	private static final int MAX_LENGTH_INSERTION_SORT = 7;	public static void main(String[] args) {   		int[] array = {    9, 5, 1, 4, 3, 6, 8, 7, 0, 2, 55, 86, 5 };		quickSort1(array, 0, array.length - 1);		print(array);	}	public static int[] quickSort(int[] array, int low, int high) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		if (high - low > MAX_LENGTH_INSERTION_SORT) {    // 有人认为7,有人认为50			int pivotIndex = partition(array, low, high);			quickSort(array, low, pivotIndex - 1);			quickSort(array, pivotIndex + 1, high);		} else {   			insertionSort(array, low, high);		}		return array;	}	public static int[] quickSort1(int[] array, int low, int high) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		if (high - low > MAX_LENGTH_INSERTION_SORT) {    // 有人认为7,有人认为50			while (low < high) {   				int pivotIndex = partition(array, low, high);				quickSort1(array, low, pivotIndex - 1);				low = pivotIndex + 1;			}		} else {   			insertionSort(array, low, high);		}		return array;	}	public static int partition(int[] array, int low, int high) {   		int mid = low + (high - low) / 2;		// 交换后左端比较小		if (array[low] > array[high]) {   			swap(array, low, high);		}		// 交换后保证最大值在右边		if (array[mid] > array[high]) {   			swap(array, mid, high);		}		// 交换后保证中间值在首位		if (array[mid] > array[low]) {   			swap(array, low, mid);		}		int pivot = array[low]; // 取第一个值,保存基准值		while (low < high) {   			while (low < high && array[high] >= pivot) {   				high--;			}			// swap(array, low, high);			array[low] = array[high];// 采用替换的操作,而不是交换			while (low < high && array[low] <= pivot) {   				low++;			}			// swap(array, low, high);			array[high] = array[low];// 采用替换的操作,而不是交换		}		array[low] = pivot;		return low;	}	public static int[] insertionSort(int[] array, int low, int high) {   		// 第1个元素默认为单个元素的有序数组		int length = high + 1;		for (int i = low + 1; i < length; i++) {   			for (int j = i; j > low; j--) {   				if (array[j] < array[j - 1]) {   					swap(array, j, j - 1);				}			}		}		return array;	}	public static void swap(int[] array, int i, int j) {   		int temp = array[i];		array[i] = array[j];		array[j] = temp;	}	public static void print(int[] array) {   		for (int i : array) {   			System.err.print(i + ",");		}		System.out.println(" ");	}}

4. 简单选择排序

4.1 基本描述

从n个数中,选出最小的值放在第1个位置;在n-1个数中,选出最小的值放在第2个位置,以此类推。

4.2 图解

在这里插入图片描述

4.3 实现代码

public static int[] selectionSort(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		int minIndex ;		for (int i = 0; i < array.length; i++) {   			minIndex = i;			for (int j = i+1; j < array.length; j++) {   				if (array[j] < array[minIndex]) {   					minIndex = j;				}			}			if (i != minIndex) {   				swap(array, minIndex, i);			}		}		return array;	}

5. 堆排序

5.1 什么是堆

  • 堆是****;
  • 每个节点都大于或等于其左右孩子的堆,称为大顶堆;
  • 每个节点都小于或等于其左右孩子的堆,称为小顶堆。
    下面左图为大顶堆,右图是小顶堆。
    在这里插入图片描述

5.2 涉及的完全二叉树的性质

在这里插入图片描述

设父节点的下标为i。

  • i=0时,则为根节点;
  • 父节点与左孩子的关系: 2 ∗ i + 1 2 * i+1 2i+1;
  • 父节点与右孩子的关系: 2 ∗ i + 2 2 * i+2 2i+2

5.3 基本思想

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

5.4 动图

在这里插入图片描述

5.5 实现代码

public class HeapSort {   	public static void main(String[] args) {   		int[] array = {    50, 10, 90, 30, 70, 40, 80, 60, 20 };		heapSort2(array);		print(array);	}	public static int[] heapSort(int[] array) {   		int length = array.length;		// 从下标最大的父节点开始做调整,直到形成大顶堆		for (int i = length / 2 - 1; i >= 0; i--) {   			heapAdjust(array, i, length);		}		for (int i = length - 1; i >= 0; i--) {   			swap(array, 0, i);			heapAdjust(array, 0, i);		}		return array;	}	/**	 * @Description: 调整堆,将较大的值调整到父节点	 * @param array 数组	 * @param s     开始下标	 * @param size  调整堆	 * @return	 */	public static void heapAdjust(int[] array, int s, int size) {   		int temp = array[s];		for (int j = 2 * s + 1; j < size; j = 2 * j + 1) {   			// 左孩子小于右孩子,j加1,此时j是右孩子			if (j < (size - 1) && array[j] < array[j + 1]) {   				++j;			}			// 如果父节点大于等于左右孩子,停止调整			if (temp >= array[j]) {   				break;			}			// 执行到这,说明父节点小于孩子,调整			array[s] = array[j];			// 开始下标的调整,继续下个循环			s = j;		}		array[s] = temp;	}	public static int[] heapSort2(int[] array) {   		int length = array.length;		// 从下标最大的父节点开始做调整,直到形成大顶堆		for (int i = length / 2 - 1; i >= 0; i--) {   			heapAdjust2(array, i, length);		}		for (int i = length - 1; i >= 0; i--) {   			swap(array, 0, i);			heapAdjust2(array, 0, i);		}		return array;	}	public static void heapAdjust2(int[] array, int s, int size) {   		int maxIndex = s;		// 如果有左孩子,左孩子大于父节点,将最大指针指向左孩子		if (s * 2 + 1 < size && array[s * 2 + 1] > array[maxIndex]) {   			maxIndex = s * 2 + 1;		}		// 如果有右孩子,右孩子大于父节点,将最大指针指向右孩子		if (s * 2 + 2 < size && array[s * 2 + 2] > array[maxIndex]) {   			maxIndex = s * 2 + 2;		}		// 如果父节点不是最大,交换父节点与最大值,并且递归		if (maxIndex != s) {   			swap(array, s, maxIndex);			heapAdjust2(array, maxIndex, size);		}	}	public static void swap(int[] array, int i, int j) {   		int temp = array[i];		array[i] = array[j];		array[j] = temp;	}	public static void print(int[] array) {   		for (int i : array) {   			System.out.print(i + ",");		}		System.out.println(" ");	}}

6. 直接插入排序

6.1 基本描述

稳定的算法,基本思想是将数列分为有序(可将首元素看做是一个有序的数列)和无序两个子列,将无序子列数据在有序子列找到相应位置,插入即可。比简单选择和冒泡的性能要好一些。

6.2 图解

在这里插入图片描述

6.3 实现代码

public static int[] insertionSort(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		// 第1个元素默认为单个元素的有序数组		for (int i = 1; i < array.length; i++) {   			for (int j = i; j > 0; j--) {   				if (array[j] < array[j - 1]) {   					swap(array, j, j - 1);				}			}		}		return array;	}	public static int[] insertionSort1(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		// 第1个元素默认为单个元素的有序数组		for (int i = 1; i < array.length; i++) {   			int temp = array[i];			int preIndex = i - 1;			while (preIndex >= 0 && array[preIndex] > temp) {   				array[preIndex + 1] = array[preIndex];				preIndex--;			}			array[preIndex + 1] = temp;		}		return array;	}

7. 希尔排序

7.1 基本描述

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的记录越来越多,当增量减至1时,整个数列恰被分成一组,算法便终止。

7.2 图解

在这里插入图片描述

在这里插入图片描述

7.3 实现代码

public static int[] shellSort(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		int gap = array.length / 2;		while (gap > 0) {   			for (int i = gap; i < array.length; i++) {   				for (int j = i; j > gap - 1; j -= gap) {   					if (array[j] < array[j - gap]) {   						swap(array, j, j - gap);					}				}			}			gap /= 2;		}		return array;	}	public static int[] shellSort1(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		int gap = array.length / 2;		while (gap > 0) {   			for (int i = gap; i < array.length; i++) {   				int temp = array[i];				int preIndex = i - gap;				while (preIndex >= 0 && array[preIndex] > temp) {   					array[preIndex + gap] = array[preIndex];					preIndex -= gap;				}				array[preIndex + gap] = temp;			}			gap /= 2;		}		return array;	}

7.4 优化点

增量gap的选取是个关键,目前没有找到最好的增量序列。

参考:https://blog.csdn.net/Foliciatarier/article/details/53891144

8. 归并排序

8.1 基本描述

性能不受输入数据的影响,始终是 O ( n l o g n ) O(nlogn) O(nlogn),代价是需要额外的空间。

归并排序采用分治的思想。
归并:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

8.2 图解

在这里插入图片描述

8.3 实现代码

public class MergeSort {   	public static void main(String[] args) {   		int[] array = {    9, 5, 1, 4, 3, 6, 8, 7, 0, 2 };		int[] result = mergeSort(array);		print(result);	}	public static int[] mergeSort(int[] array) {   		if (array == null) {   			throw new NullPointerException("array is null!");		}		if (array.length < 2) {   			return array;		}		int mid = array.length / 2;		int[] left = Arrays.copyOfRange(array, 0, mid);		int[] right = Arrays.copyOfRange(array, mid, array.length);		return merge(mergeSort(left), mergeSort(right));	}	public static int[] merge(int[] left, int[] right) {   		int leftLength = left.length;		int rightLength = right.length;		int[] result = new int[leftLength + rightLength];		int resultLength = result.length;		for (int index = 0, leftIndex = 0, rightIndex = 0; index < resultLength; index++) {   			if (leftIndex >= leftLength) {    // 说明左列已经合并完				result[index] = right[rightIndex++];			} else if (rightIndex >= rightLength) {    // 说明右列已经合并完				result[index] = left[leftIndex++];			} else if (left[leftIndex] > right[rightIndex]) {    // 说明左列的数值比右列的大,则合并右列的值				result[index] = right[rightIndex++];			} else {   				result[index] = left[leftIndex++];			}		}		return result;	}	public static void swap(int[] array, int i, int j) {   		int temp = array[i];		array[i] = array[j];		array[j] = temp;	}	public static void print(int[] array) {   		for (int i : array) {   			System.out.print(i + ",");		}		System.out.println(" ");	}}

参考:

  1. 大话数据结构
  2. https://www.cnblogs.com/guoyaohua/p/8600214.html
  3. https://www.runoob.com/w3cnote/bubble-sort.html
  4. https://juejin.im/post/5bf9f2285188256b0f5832a0
你可能感兴趣的文章
mysql 复杂查询_mysql中复杂查询
查看>>
mYSQL 外键约束
查看>>
mysql 多个表关联查询查询时间长的问题
查看>>
mySQL 多个表求多个count
查看>>
mysql 多字段删除重复数据,保留最小id数据
查看>>
MySQL 多表联合查询:UNION 和 JOIN 分析
查看>>
MySQL 大数据量快速插入方法和语句优化
查看>>
mysql 如何给SQL添加索引
查看>>
mysql 字段区分大小写
查看>>
mysql 字段合并问题(group_concat)
查看>>
mysql 字段类型类型
查看>>
MySQL 字符串截取函数,字段截取,字符串截取
查看>>
MySQL 存储引擎
查看>>
mysql 存储过程 注入_mysql 视图 事务 存储过程 SQL注入
查看>>
MySQL 存储过程参数:in、out、inout
查看>>
mysql 存储过程每隔一段时间执行一次
查看>>
mysql 存在update不存在insert
查看>>
Mysql 学习总结(86)—— Mysql 的 JSON 数据类型正确使用姿势
查看>>
Mysql 学习总结(87)—— Mysql 执行计划(Explain)再总结
查看>>
Mysql 学习总结(88)—— Mysql 官方为什么不推荐用雪花 id 和 uuid 做 MySQL 主键
查看>>