一.冒泡排序
(来自<java语言程序设计(进阶篇)> --梁勇)
1.排序思想及特点
多次遍历数组,在每次比较中,比较连续相邻的元素,如果某一对元素是降序,则交换他们的顺序,否则保持不变,直到所有的元素排序完毕
最好时间复杂度:O(n)
最坏时间复杂度:O(n²)
空间复杂度:O(1)
稳定性:稳定
2.优缺点
优点:简单,空间复杂度优秀
缺点:速度慢
3.适用场景
元素较少且数组基本有序的场景
4.示例代码
public class BubbleSort {
public static void main(String[] args) {
int[] array = CreatRandomArray.getRandomIntArray(Numbers.length, Numbers.maxIntNumber);
System.out.println("原数组: ");
for (int i = Numbers.beginIndex; i < array.length; i++) {
System.out.print(array[i] + “,”);
}
System.out.println();
// 调用方法对数组进行冒泡排序
bubbleSort(array);
System.out.println("使用冒泡排序后数组: ");
for (int i = Numbers.beginIndex; i < array.length; i++) {
System.out.print(array[i] + “,”);
}
}
public static void bubbleSort(int[] array) {
// 如果有一轮没有发生元素位置交换,则说明已排序成功,不需要再进行后面的比较
boolean needNextPass = true;
for (int i = Numbers.beginIndex + 1; i < array.length && needNextPass; i++) {
needNextPass = false;
for (int j = 0; j < array.length - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
needNextPass = true;
}
}
}
}
}
二.插入排序
(来自<java语言程序设计(进阶篇)> --梁勇)
1.排序思想及特点
重复地将新的元素插入到一个排好序的子线性表中,直到整个线性表排好序
最好时间复杂度:O(n)
最坏时间复杂度:O(n²)
空间复杂度:O(1)
稳定性:稳定
2.优缺点
优点:稳定,速度快
缺点:比较次数不一定,插入点越后的数据移动次数越多,当数据量大的时候极为明显
3.适用场景
数据量不大,对稳定性有要求,且基本有序的情况
4.示例代码
public class InsertionSort {
public static void main(String[] args) {
int[] list = CreatRandomArray.getRandomIntArray(Numbers.length, Numbers.maxIntNumber);
System.out.println("原数组: ");
for (int value : list) {
System.out.print(value + “,”);
}
System.out.println();
// 调用方法对数组进行排序
insertionSort(list);
System.out.println("插入排序后数组: ");
for (int value : list) {
System.out.print(value + “,”);
}
}
public static void insertionSort(int[] list) {
for (int i = Numbers.beginIndex; i < list.length; i++) {
// 获取当前需要插入的元素
int currentElement = list[i];
int j; // 已插入的元素个数
// 如果当前元素比已排好序的元素小,则往前继续移动
for (j = i - 1; j >= 0 && list[j] > currentElement; j–) {
// 大于当前元素的数后移
list[j + 1] = list[j];
}
// 将当前元素插入到正确位置
list[j + 1] = currentElement;
}
}
}
二.选择排序
(来自<java语言程序设计(基础篇)> --梁勇)
1.排序思想及特点
先找到最小的元素,将它和第一个元素交换位置,接下来,在剩下的数中再找到最小的元素,将它和第二个元素交换位置,直到只剩下一个元素为止
最好时间复杂度:O(n²)
最坏时间复杂度:O(n²)
空间复杂度:O(1)
稳定性:不稳定
2.优缺点
优点:相对冒泡排序减少了交换次数(n-1),效率大幅提升
缺点:比较次数较多,不稳定
3.适用场景
数据量不大,对稳定性没有要求的时候
4.示例代码
public class SelectionSort {
public static void main(String[] args) {
// 调用方法获取int型数组
int[] array = CreatRandomArray.getRandomIntArray(Numbers.length, Numbers.maxIntNumber);
// 输出原数组
System.out.println("原数组为: ");
for (int i = Numbers.beginIndex; i < array.length; i++) {
System.out.print(array[i] + “,”);
}
System.out.println();
// 调用方法对数组进行选择排序
selectionSort(array);
// 输出排序后的数组
System.out.println("使用选择排序后的数组: ");
for (int i = Numbers.beginIndex; i < array.length; i++) {
System.out.print(array[i] + “,”);
}
}
public static void selectionSort(int[] array) {
// 遍历数组
for (int i = Numbers.beginIndex; i < array.length - 1; i++) {
// 设下标为i的这个元素为最小数
int currentMin = array[i];
// 获取最小数的下标
int currentMinIndex = i;
// 获取最小数及最小数下标
for (int j = i + 1; j < array.length; j++) {
// 如果此数比上一个最小数小,则记该数为最小数,并记下其下标
if (currentMin > array[j]) {
currentMin = array[j];
currentMinIndex = j;
}
}
// 如果最小数不是本身,则交换两数位置
if (currentMinIndex != i) {
array[currentMinIndex] = array[i];
array[i] = currentMin;
}
}
}
}
四.快速排序
(来自<java语言程序设计(进阶篇)> --梁勇)
1.排序思想及特点
在数组中选择一个称为主元的元素,将数组分为两部分,使第一部分的所有元素都小于或等于主元,第二部分中的元素都大于或等于主元,
此时主元刚好处于排好序的正确位置,对第一部分递归地应用快速排序算法,然后对第二部分递归地应用快速排序算法
最好时间复杂度:O(nlog₂n)
最坏时间复杂度:O(n²)
空间复杂度:O(n)
稳定性:不稳定
2.优缺点
优点:极快,数据移动少
缺点:不稳定,需要很大的辅助空间
3.适用场景
数据量大,查找一组数中前n个数
4.示例代码
public class QuickSort {
public static void main(String[] args) {
int[] array = CreatRandomArray.getRandomIntArray(Numbers.length, Numbers.maxIntNumber);
System.out.println("排序前的数组: ");
for (int i = Numbers.beginIndex; i < array.length; i++) {
System.out.print(array[i] + “,”);
}
System.out.println();
// 调用方法对数组进行快速排序
quickSort(array);
System.out.println("快速排序后的数组: ");
for (int i = Numbers.beginIndex; i < array.length; i++) {
System.out.print(array[i] + “,”);
}
}
/**
* 对数组进行排序
/
private static void quickSort(int[] array) {
quickSort(array, 0, array.length - 1);
}
/*
* 辅助方法,用于对特定范围内的子数组进行排序
* @param array 要排序的数组
* @param first 首位下标
* @param last 末位下标
/
private static void quickSort(int[] array, int first, int last) {
if (last > first) {
// 调用方法选择主元并根据主元将数组分为两个部分且返回新的主元下标
int pivotIndex = partition(array, first, last);
// 对第一部分递归执行快速排序算法
quickSort(array, first, pivotIndex - 1);
// 对第二部分递归执行快速排序算法
quickSort(array, pivotIndex + 1, last);
}
}
/*
* 排序方法,将数组按主元分为两部分并返回新的主元
* @param array 要进行快速排序的数组
* @param first 除主元外的最低位数下标
* @param last 除主元外的最高位数下标
* @return 返回新的主元下标
*/
private static int partition(int[] array, int first, int last) {
int pivot = array[first]; // 设置主元(主元的设置影响性能)
int low = first + 1; // 除主元外的最低位数下标
int high = last; // 最高位数下标
while (low < high) {
// 从数组左侧查找第一个大于主元的元素
while (low <= high && array[low] <= pivot)
low++;
// 从数组右侧查找第一个小于主元的元素
while (low <= high && array[high] > pivot)
high–;
// 交换以上两个元素
if (high > low) {
int temp = array[high];
array[high] = array[low];
array[low] = temp;
}
}
// 当高位数大于等于主元,下标减1,直到小于主元为止
while (high > first && array[high] >= pivot)
high–;
// 若主元大于最低高位,则交换这两个元素位置,并返回将数组分为两部分的主元的新下标,否则返回主元的原始下标
if (pivot > array[high]) {
array[first] = array[high];
array[high] = pivot;
return high;
} else {
return first;
}
}
}
五.堆排序
(来自<java语言程序设计(进阶篇)> --梁勇)
1.排序思想及特点
使用二叉堆,首先将所有的元素添加到一个堆上,然后不断移除最大的元素以获得一个排好序的线性表
最好时间复杂度:O(nlog₂n)
最坏时间复杂度:O(nlog₂n)
空间复杂度:O(1)
稳定性:不稳定
2.优缺点
优点:效率极高,在高效率的同时只需要O(1)的辅助空间,集高效与节省空间于一身,排序效率稳定
缺点:不易维护,由于实际场景的数据经常变动导致每次更新排序序列,都要重新对堆进行维护来保证其特性
3.适用场景
数据更新不频繁,数据规模大的情况
4.相关知识
1.二叉堆
二叉堆(binary heap)是一棵具有以下属性的二叉树:
1)形状属性:二叉堆是一棵完全二叉树
2)堆属性:每个节点大于或等于它的任意一个孩子
2.二叉树
二叉树是一种层次体系结构.它可能是空的,也可能包含一个称为根(root)的元素以及称为左子树(left subtree)和右子树(left subtree)的两棵不同的二叉树.
一条路径的长度(length)是指这条路径上的边数,一个结点的深度(depth)是指从根结点到该结点的路径的长度.一个二叉堆是一种特殊的完全二叉树
3.完全二叉树
如果一棵二叉树的每一层都是满的,或者最后一层可以不填满并且最后一层的叶子都是靠左放置的,那么这棵二叉树就是完全的(complete).
例如,在下图中,a)和b)都是完全的,但是c)和d)都不是完全的,而且,a中的二叉树是一个堆,但是b中的二叉树不是堆,因为根(39)小于它的右孩子
5.堆排序使用流程
1.建立一个二叉堆
1)设置存储堆的ArrayList或数组
2)添加一个新结点
将最后一个结点作为当前结点;
while(当前结点大于它的父结点){
将当前结点和它的父结点交换;
现在当前结点往上面进了一个层次;
}
3)删除根结点(删除之后要重建这棵树以保持堆的属性)
用最后一个结点替换根结点
让根结点称为当前结点
while(当前结点具有子结点并且当前结点小于它的子结点){
将当前结点和它的较大子结点交换;
现在当前结点往下面退了一个层次;
}
2.创建堆对象并将要排序的数据存储到堆中,通过移除根结点获取最大值
6.示例代码
/**
-
建立二叉堆
/
public class Heap<E extends Comparable> {
// 将堆存储在ArrayList中
private ArrayList list = new ArrayList<>();
/*
*功能描述:创建一个空堆- @Params []
- @return
/
Heap() {
}
/*
*功能描述:创建堆的同时初始化堆 - @Params [objects]
- @return
/
Heap(E[] objects) {
for (E object : objects) {
add(object);
}
}
/*
*功能描述: 添加新结点 - @Params [newObject]
- @return void
/
void add(E newObject) {
// 将一个对象追加到树中
list.add(newObject);
// 将最后一个结点作为当前结点
int currentIndex = list.size() - 1;
while (currentIndex > 0) {
// 获取它的父结点
int parentIndex = (currentIndex - 1) / 2;
// 如果当前结点大于它的父结点,则将它与它的父结点交换
if (list.get(currentIndex).compareTo(list.get(parentIndex)) > 0) {
E temp = list.get(currentIndex);
list.set(currentIndex, list.get(parentIndex));
list.set(parentIndex, temp);
} else break;
// 当前结点往上面进了一个层次
currentIndex = parentIndex;
}
}
/*
*功能描述: 移除并返回根结点 - @Params []
- @return E
/
E remove() {
// 若树的深度为0,则返回null
if (list.size() == 0) return null;
// 获取根结点
E removedObject = list.get(0);
// 将最后一个结点设为新的根结点
list.set(0, list.get(list.size() - 1));
// 移除最后一个结点
list.remove(list.size() - 1);
// 当前的结点
int currentIndex = 0;
// 重新建树(为了让树保持堆的属性,即根结点大于或等于它的子结点)
while (currentIndex < list.size()) {
// 左子结点
int leftChildIndex = 2 * currentIndex + 1;
// 右子结点
int rightChildIndex = 2 * currentIndex + 2;
// 若左子结点大于堆的大小,说明该堆已经没有更深的层次了,结束对树的重建
if (leftChildIndex >= list.size()) break;
// 设左子结点为较大的结点
int maxIndex = leftChildIndex;
// 若右子结点小于堆的大小,则执行比较工作,为了获取其中较大的一个结点
if (rightChildIndex < list.size()) {
// 比较左右子结点,获取较大的一个结点
if (list.get(maxIndex).compareTo(list.get(rightChildIndex)) < 0) {
maxIndex = rightChildIndex;
}
}
// 将较大的结点与当前结点进行比较,若比当前结点大,则交换两个结点
if (list.get(currentIndex).compareTo(list.get(maxIndex)) < 0) {
E temp = list.get(maxIndex);
list.set(maxIndex, list.get(currentIndex));
list.set(currentIndex, temp);
currentIndex = maxIndex;
} else {
break;
}
}
// 返回移除的根结点
return removedObject;
}
/*
*功能描述: 获取堆的大小 - @Params []
- @return int
*/
public int getSize() {
return list.size();
}
}
public class HeapSort {
public static void main(String[] args) {
Integer[] list = CreatRandomArray.getRandomIntegerArray(Numbers.length, Numbers.maxIntNumber);
System.out.println("原数组: ");
for (Integer integer : list) {
System.out.print(integer + “,”);
}
System.out.println();
long beginTime = System.currentTimeMillis();
// 调用方法进行堆排序
heapSort(list);
long endTime = System.currentTimeMillis();
System.out.println("归并排序后的数组: “);
for (Integer integer : list) {
System.out.print(integer + “,”);
}
System.out.println();
System.out.println(Numbers.length + " 个数使用归并排序耗时 " + (endTime - beginTime) + " 毫秒”);
}
/**- 堆排序方法
- @param list 要排序的数组
*/
private static <E extends Comparable> void heapSort(E[] list) {
Heap heap = new Heap<>();
// 将要排序的元素添加到堆上
for (E e : list) {
heap.add(e);
}
// 通过移除堆上的最大元素来获取一个排好序的线性表
for (int i = list.length - 1; i >= 0; i–) {
list[i] = heap.remove();
}
}
}
六.归并排序
(来自<java语言程序设计(进阶篇)> --梁勇)
1.排序思想及特点
将数组分为两半,对每一部分递归地应用归并排序,在两部分都排好序后,对它们进行归并(分而治之)
最好时间复杂度:O(nlog₂n)
最坏时间复杂度:O(nlog₂n)
空间复杂度:O(n)
稳定性:稳定
2.优缺点
优点:速度极端快(基于比较的排序算法能达到的最高速度),稳定
缺点:需要很大的辅助空间
3.适用场景
不用考虑内存的情况
4.示例代码
public class MergeSort {
public static void main(String[] args) {
int[] list = CreatRandomArray.getRandomIntArray(Numbers.length, Numbers.maxIntNumber);
System.out.println("原数组: ");
for (int i = Numbers.beginIndex; i < list.length; i++) {
System.out.print(list[i] + “,”);
}
System.out.println();
// 调用方法对数组进行归并排序
mergeSort(list);
System.out.println("归并排序后的数组: ");
for (int i = Numbers.beginIndex; i < list.length; i++) {
System.out.print(list[i] + “,”);
}
}
/**
* 递归将数组分为两半
* @param list 要进行排序的数组
/
private static void mergeSort(int[] list) {
if (list.length > 1) {
// 将list前半部分分给新数组
int[] firstHalf = new int[list.length / 2];
System.arraycopy(list, 0, firstHalf, 0, list.length / 2);
// 递归调用mergeSort方法对数组进行对分
mergeSort(firstHalf);
// 将list后半部分分给新数组
int secondHalfLength = list.length - list.length / 2;
int[] secondHalf = new int[secondHalfLength];
System.arraycopy(list, list.length / 2, secondHalf, 0, secondHalfLength);
// 递归调用mergeSort方法对数组进行对分
mergeSort(secondHalf);
// 调用方法将对分的数组进行归并
merge(firstHalf, secondHalf, list);
}
}
/*
* 归并两个有序数组为一个临时数组list
* @param firstHalf 第一部分数组
* @param secondHalf 第二部分数组
* @param list 归并的数组
*/
private static void merge(int[] firstHalf, int[] secondHalf, int[] list) {
int current1 = 0; // 第一部分当前元素下标
int current2 = 0; // 第二部分当前元素下标
int current3 = 0; // 归并数组当前元素下标
// 重复比较两个数组中的当前元素,将小的数移动到list中
while (current1 < firstHalf.length && current2 < secondHalf.length) {
if (firstHalf[current1] < secondHalf[current2]) {
list[current3++] = firstHalf[current1++];
} else {
list[current3++] = secondHalf[current2++];
}
}
// 将第一部分剩余的元素全部移动到list中
while (current1 < firstHalf.length) {
list[current3++] = firstHalf[current1++];
}
// 将第二部分剩余的元素全部移动到list中
while (current2 < secondHalf.length) {
list[current3++] = secondHalf[current2++];
}
}
}
七.希尔排序
1.排序思想及特点
2.优缺点
3.适用场景
4.示例代码
八.桶排序
1.排序思想及特点
2.优缺点
3.适用场景
4.示例代码
九.基数排序
1.排序思想及特点
2.优缺点
3.适用场景
4.示例代码
十.外部排序
十一.附录
1.常数类
/**
-
常数类
/
public class Numbers {
/*- 数组循环起始下标,值为0
*/
public static final int beginIndex = 0;
/**
- 数组长度,值为50000
*/
public static final int length = 500000;
/**
- int型数组中最大数,值为100
*/
public static final int maxIntNumber = 10;
/**
- double型数组中最小数,值为100.0
*/
public static final double maxDoubleNumber = 100.0;
}
2.生成随机数组类
import java.util.Random;
- 数组循环起始下标,值为0
/**
-
随机生成一组数组
*/
public class CreatRandomArray {/**
- 随机生成长度为arrayLength的int型数组
- @param arrayLength 要生成数组的长度
- @param maxNumber 数组的最大值
- @return int
/
public static int[] getRandomIntArray(int arrayLength, int maxNumber) {
Random random = new Random();
int[] arrayInt = new int[arrayLength];
for (int i = Numbers.beginIndex; i < arrayInt.length; i++) {
arrayInt[i] = random.nextInt(maxNumber);
}
return arrayInt;
}
/*
*功能描述: 随机生成Integer型数组 - @Params [arrayLength, maxNumber]
- @return java.lang.Integer[]
/
public static Integer[] getRandomIntegerArray(int arrayLength, int maxNumber) {
Random random = new Random();
Integer[] arrayInt = new Integer[arrayLength];
for (int i = Numbers.beginIndex; i < arrayInt.length; i++) {
arrayInt[i] = random.nextInt(maxNumber);
}
return arrayInt;
}
/* - 随机生成长度为arrayLength的double型数组
- @param arrayLength 要生成的数组的长度
- @param maxNumber 数组的最大值
- @return double
*/
public static double[] getRandomDoubleArray(int arrayLength, double maxNumber) {
Random random = new Random();
double[] arrayInt = new double[arrayLength];
for (int i = Numbers.beginIndex; i < arrayInt.length; i++) {
arrayInt[i] = ((double)(int)(random.nextDouble() * maxNumber * 100)) / 100;
}
return arrayInt;
}
}