前言
为了巩固自己的算法能力,重学算法,一个一个自己写,敲。本文主要介绍Java八大排序经典算法。
1. 冒泡排序(BubbleSort)
1.1 基本思想
两个数比较大小,较大的数下沉,较小的数冒起来。
1.2 过程
1.3 算法实现
/**
* @Description: 冒泡排序,优化版 平均时间复杂度:O(n^2)
* @Date: 21:11 2019/8/7
* @Param: [arr]
* @return: int[]
*/
static int[] BubbleSort(int[] arr) {
boolean flag = false;
for (int j = 1; j < arr.length; j++) {
for (int i = 0; i < arr.length - j; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
flag = true;
}
}
if (!flag) {
break;
}
}
return arr;
}
2. 选择排序(SelctionSort)
2.1 基本思想
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
2.2 过程
2.3 算法实现
/**
* @Description: 选择排序 平均时间复杂度:O(n^2)
* @Date: 21:31 2019/8/7
* @Param: [arr]
* @return: int[]
*/
static int[] SelctionSort(int[] arr){
for (int i=0;i<arr.length-2;i++){
for (int j = i+1;j<arr.length-1;j++){
if (arr[i]>arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
3. 插入排序(Insertion Sort)
3.1 基本思想
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
如果数据序列基本有序,使用插入排序会更加高效。
3.2 过程
类似于打牌:
3.3 算法实现
/**
* @Description: 插入排序 平均时间复杂度:O(n^2)
* @Date: 10:36 2019/8/8
* @Param: [arr]
* @return: int[]
*/
static int[] insert_sort(int[] arr){
for (int n = 1;n<arr.length;n++){
for (int m =n;n>0;n--){
if (arr[m]<arr[m-1]){
int temp = arr[m];
arr[m] = arr[m-1];
arr[m-1] = temp;
}else {
break;
}
}
}
return arr;
}
4. 希尔排序(Shell Sort)
4.1 基本思想
该方法实质上是一种分组插入方法
希尔排序属于插入类排序,是将整个有序序列分割成若干小的子序列分别进行插入排序。
在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。
4.2 过程
希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。
4.3 算法实现
/**
* @Description: 希尔排序 平均时间复杂度O(n^1.5)
* @Date: 11:53 2019/8/8
* @Param: [arr]
* @return: int[]
*/
static int[] shell_sort(int[] arr) {
int gap = arr.length;
while (true) {
gap = gap / 2;
for (int k = 0; k < gap; k++) { //划分序列
for (int n = k + gap; n < arr.length; n += gap) {
for (int m = n; m < k; m -= gap) {
if (arr[m] < arr[m - gap]) {
int temp = arr[m];
arr[m] = arr[m - gap];
arr[m - gap] = temp;
} else {
break;
}
}
}
}
if (gap == 1) {
break;
}
}
return arr;
}
5. 快速排序(Quicksort)
5.1 基本思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
5.2 过程
5.3 算法实现
/**
* @Description: 快速排序 平均时间复杂度:O(N*logN)
* @Date: 14:13 2019/8/8
* @Param: [arr]
* @return: int[]
*/
static int[] quickSort(int a[], int l, int r) {
if (l >= r) {
return a;
}
int i = l;
int j = r;
int key = a[l];//选择第一个数为key
while (i < j) {
while (i < j && a[j] >= key)//从右向左找第一个小于key的值
j--;
if (i < j) {
a[i] = a[j];
i++;
}
while (i < j && a[i] < key)//从左向右找第一个大于key的值
i++;
if (i < j) {
a[j] = a[i];
j--;
}
}
//i == j
a[i] = key;
quickSort(a, l, i - 1);//递归调用
quickSort(a, i + 1, r);//递归调用
return a;
}
6. 归并排序(Merge Sort)
6.1 基本思想
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
首先将数组分为两组A,B,再将A,B组各自再分成2组。依次类推,当分出来的小组只有1个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的2个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
6.2 过程
6.3 算法实现
/**
* @Description: 合并数列 时间复杂度O(n) 用于归并排序
* @Date: 9:23 2019/8/9
* @Param: [a, b]
* @return: int[]
*/
private static int[] mergeArray(int[] arrays, int L, int M, int R) {
//左边的数组的大小
int[] leftArray = new int[M - L];
//右边的数组大小
int[] rightArray = new int[R - M + 1];
//往这两个数组填充数据
if (M - L >= 0) System.arraycopy(arrays, L, leftArray, 0, M - L);
if (R + 1 - M >= 0) System.arraycopy(arrays, M, rightArray, 0, R + 1 - M);
int i = 0, j = 0;
// arrays数组的第一个元素
int k = L;
//比较这两个数组的值,哪个小,就往数组上放
while (i < leftArray.length && j < rightArray.length) {
//谁比较小,谁将元素放入大数组中,移动指针,继续比较下一个
// 等于的情况是保证“稳定”
if (leftArray[i] <= rightArray[j]) {
arrays[k++] = leftArray[i++];
//相当于:
// arrays[k] = leftArray[i];
// i++;
// k++;
} else {
arrays[k++] = rightArray[j++];
}
}
//如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中(剩下的都是大数字)
while (i < leftArray.length) {
arrays[k++] = leftArray[i++];
}
//如果右边的数组还没比较完,左边的数都已经完了,那么将右边的数抄到大数组中(剩下的都是大数字)
while (j < rightArray.length) {
arrays[k++] = rightArray[j++];
}
return arrays;
}
/**
* @Description: 归并排序 平均时间复杂度:O(N*logN)
* @Date: 11:08 2019/8/9
* @Param: [arrays, L, R]
* @return: int[]
*/
static int[] merge_sort(int[] arrays, int L, int R) {
//如果只有一个元素,那就不用排序了
if (L == R) {
return arrays;
} else {
//取中间的数,进行拆分
int M = (L + R) / 2;
//左边的数不断进行拆分
merge_sort(arrays, L, M);
//右边的数不断进行拆分
merge_sort(arrays, M + 1, R);
//合并
return mergeArray(arrays, L, M + 1, R);
}
}
要想深入了解归并排序的小伙伴,可以参考:归并排序及优化(Java实现)
7. 堆排序(HeapSort)
7.1 基本思想
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。
7.2 过程
(88,85,83,73,72,60,57,48,42,6)
7.3 算法实现
/**
* @Description: 堆排序 平均时间复杂度:O(N*logN)
* @Date: 12:00 2019/8/9
* @Param: [arr]
* @return: int[]
*/
static int[] heapSort(int[] arr) {
// 将待排序的序列构建成一个大顶堆
for (int i = arr.length / 2; i >= 0; i--) {
heapAdjust(arr, i, arr.length);
}
// 逐步将每个最大值的根节点与末尾元素交换,并且再调整二叉树,使其成为大顶堆
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i); // 将堆顶记录和当前未经排序子序列的最后一个记录交换
heapAdjust(arr, 0, i); // 交换之后,需要重新检查堆是否符合大顶堆,不符合则要调整
}
return arr;
}
/**
* 构建堆的过程
*
* @param arr 需要排序的数组
* @param i 需要构建堆的根节点的序号
* @param n 数组的长度
*/
private static void heapAdjust(int[] arr, int i, int n) {
int child;
int father;
for (father = arr[i]; leftChild(i) < n; i = child) {
child = leftChild(i);
// 如果左子树小于右子树,则需要比较右子树和父节点
if (child != n - 1 && arr[child] < arr[child + 1]) {
child++; // 序号增1,指向右子树
}
// 如果父节点小于孩子结点,则需要交换
if (father < arr[child]) {
arr[i] = arr[child];
} else {
break; // 大顶堆结构未被破坏,不需要调整
}
}
arr[i] = father;
}
// 获取到左孩子结点
private static int leftChild(int i) {
return 2 * i + 1;
}
// 交换元素位置
private static void swap(int[] arr, int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
8. 基数排序(RadixSort)
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort.
8.1 基本思想
BinSort
BinSort想法非常简单,首先创建数组A[MaxValue];然后将每个数放到相应的位置上(例如17放在下标17的数组位置);最后遍历数组,即为排序后的结果。
问题: 当序列中存在较大值时,BinSort 的排序方法会浪费大量的空间开销。
基数排序是在BinSort的基础上,通过基数的限制来减少空间的开销。
8.2 过程
(1)首先确定基数为10,数组的长度也就是10.每个数34都会在这10个数中寻找自己的位置。
(2)不同于BinSort会直接将数34放在数组的下标34处,基数排序是将34分开为3和4,第一轮排序根据最末位放在数组的下标4处,第二轮排序根据倒数第二位放在数组的下标3处,然后遍历数组即可。
8.3 算法实现
/**
* @Description: 基数排序 平均时间复杂度:O(d(n+r))
* @Date: 13:53 2019/8/9
* @Param: [arrays]
* @return: int[]
*/
static int[] radixSort(int[] arrays) {
int max = findMax(arrays, 0, arrays.length - 1);
//需要遍历的次数由数组最大值的位数来决定
for (int i = 1; max / i > 0; i = i * 10) {
int[][] buckets = new int[arrays.length][10];
//获取每一位数字(个、十、百、千位...分配到桶子里)
for (int j = 0; j < arrays.length; j++) {
int num = (arrays[j] / i) % 10;
//将其放入桶子里
buckets[j][num] = arrays[j];
}
//回收桶子里的元素
int k = 0;
//有10个桶子
for (int j = 0; j < 10; j++) {
//对每个桶子里的元素进行回收
for (int l = 0; l < arrays.length; l++) {
//如果桶子里面有元素就回收(数据初始化会为0)
if (buckets[l][j] != 0) {
arrays[k++] = buckets[l][j];
}
}
}
}
return arrays;
}
private static int findMax(int[] arrays, int L, int R) {
//如果该数组只有一个数,那么最大的就是该数组第一个值了
if (L == R) {
return arrays[L];
} else {
int a = arrays[L];
int b = findMax(arrays, L + 1, R);//找出整体的最大值
if (a > b) {
return a;
} else {
return b;
}
}
}
9. 测试代码
/**
* @author :米奇罗
* @className :sortTest
* @date :Created in 2019/8/7 20:24
* @description: 排序算法测试
* @version: 1.0
*/
public class sortTest {
public static void main(String[] args) {
//int[] arr = {1};
int[] arr = {3, 5, 2, 7, 5,10,4,3,11,10,22,4,8};
//int[] arr = {3, 5, 2, 7,10,4,11,22,4,8};
System.out.println("冒泡排序:");
for (int i : sort.BubbleSort(arr)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("选择排序:");
for (int i : sort.SelctionSort(arr)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("插入排序:");
for (int i : sort.insert_sort(arr)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("希尔排序:");
for (int i : sort.shell_sort(arr)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("快速排序:");
for (int i : sort.quickSort(arr,0,arr.length-1)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("归并排序:");
for (int i : sort.merge_sort(arr,0,arr.length-1)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("堆排序:");
for (int i : sort.heapSort(arr)) {
System.out.print(i + " ");
}
System.out.println();
System.out.println("基数排序:");
for (int i : sort.radixSort(arr)) {
System.out.print(i + " ");
}
}
}
10. 排序算法复杂度总结
排序算法 | 平均时间复杂度 |
---|---|
冒泡排序 | O(n^2) |
选择排序 | O(n^2) |
插入排序 | O(n^2) |
希尔排序 | O(n^1.5) |
快速排序 | O(N*logN) |
归并排序 | O(N*logN) |
堆排序 | O(N*logN) |
基数排序 | O(d(n+r)) |
11. 小结&参考资料
小结
算法对于Java开发人员来说,真的很重要,对于我这种当时上课没有好好的人来说,只能事后诸葛亮,自己重新学习算法了。加油!!!
此文是Java算法基础系列博客的第一篇,接下来,将会介绍贪心算法、
动态规划、分治算法、回溯算法等。敬请期待。。。