为了简单,所有的例子默认都为正序,这里不考虑正序倒序的问题!!
准备
开始前,我这边做了以下准备:
- 一台通电的电脑
- JDK8 OR JDK11
- JUnit5
- Idea
- lombok(非必须,个人习惯用这个打印日志了)
一个抽象接口:
public interface Sort<E> {
E[] sort();
}
一个测试用例:
@Slf4j
class SortTest {
Integer[] integers;
Sort<Integer> sort;
@BeforeEach
void setUp() {
integers = new Integer[]{3, 7, 2, 1, 4, 8, 9, 8, 5, 6, 0};
log.info("排序前: {}", Arrays.toString(integers));
}
@AfterEach
void tearDown() {
assertSort(sort.sort());
}
@Test
void bubbleSortTest() {
sort = new BubbleSort(integers);
}
@Test
void insertSortTest() {
sort = new InsertSort(integers);
}
@Test
void selectSortTest() {
sort = new SelectSort(integers);
}
private void assertSort(Integer[] sort) {
log.info("排序后: {}", Arrays.toString(sort));
assertAll(
() -> assertEquals(integers.length, sort.length),
() -> assertEquals(0, sort[0]),
() -> assertEquals(9, sort[sort.length - 1]),
() -> assertEquals(1, sort[1]),
() -> assertEquals(2, sort[2]),
() -> assertEquals(3, sort[3]),
() -> assertEquals(4, sort[4]),
() -> assertEquals(5, sort[5]),
() -> assertEquals(6, sort[6]),
() -> assertEquals(7, sort[7]),
() -> assertEquals(8, sort[8]),
() -> assertEquals(8, sort[9])
);
}
}
冒泡排序
对于冒泡排序,一切描述都是苍白的,看看这个魔性的视频,你就会完全明白
https://www.bilibili.com/video/BV1xW411Y7VL
public class BubbleSort implements Sort<Integer> {
private final Integer[] elements;
public BubbleSort(Integer[] elements) {
this.elements = elements;
}
@Override
public Integer[] sort() {
int length = elements.length;
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - i - 1; j++) {
// 当前大于下一位,则互换位置,继续向后比较
if (elements[j] > elements[j + 1]) {
Integer temp = elements[j];
elements[j] = elements[j + 1];
elements[j + 1] = temp;
}
// 当前小于或等于下一位,从下一位开始往后比较
}
}
return elements;
}
}
插入排序
- 插入排序将数组划分为两个区域,已排序区,和未排序区
- 排序开始时,数组第一个元素默认在已排序区
- 每轮排序都是将未排序区的第一个元素,与已排序区的元素依次比较,如果未排序元素小于当前已排序元素,则此处为插入点
- 插入点之后的元素向后平移
- 将未排序元素插入此处
- 否则,此未排序元素插入到已排序区的最后,即数组不做任何移动,从未排序区的下一位进行新一轮比较
public class InsertSort implements Sort<Integer> {
private Integer[] elements;
public InsertSort(Integer[] elements) {
this.elements = elements;
}
@Override
public Integer[] sort() {
int sortedLength = 1;
int unSortedLength = elements.length - 1;
for (int i = 0; i < unSortedLength; i++) {
// 未排序区的第一个元素
Integer unSort = elements[sortedLength];
// 与已排序元素比较
for (int j = 0; j < sortedLength; j++) {
// 1. 已排序区:如果unSort比所有的都大,则排在最后一位,直接完成本轮比对即可
// 2. 已排序区:unSort小于此位数,则排在此位前
if (unSort < elements[j]) {
rightMoveOnePlace(sortedLength, j, elements);
elements[j] = unSort;
break;
}
// 3. 已排序区:unSort大于此位数,则进行下一位数对比
}
sortedLength++;
}
return elements;
}
/**
* 将数组从指定位置开始,向右移动1位
*
* @param endMoveElementIndex 结束移动的元素数量
* @param startMoveElementIndex 开始移动的元素位置
* @param elements 待操作的数组
*/
private void rightMoveOnePlace(int endMoveElementIndex, int startMoveElementIndex, Integer[] elements) {
for (int k = 0; k < endMoveElementIndex - startMoveElementIndex; k++) {
elements[endMoveElementIndex - k] = elements[endMoveElementIndex - k - 1];
}
}
}
选择排序
- 选择排序也划分为已排序区和未排序区
- 排序开始时,未排序区没有任何元素
- 从未排序区第一个元素开始,依次与后面所有元素比较,将最小的元素放到已排序区尾部
public class SelectSort implements Sort<Integer> {
private Integer[] elements;
public SelectSort(Integer[] elements) {
this.elements = elements;
}
@Override
public Integer[] sort() {
for (int unSortIndex = 0; unSortIndex < elements.length - 1; unSortIndex++) {
int min = elements[unSortIndex];
int minIndex = unSortIndex;
for (int j = unSortIndex + 1; j < elements.length; j++) {
if (elements[j] < min) {
min = elements[j];
minIndex = j;
}
}
// 如果相等,则说明未排序区首位元素已经是最小值,不需要交换位置
if (minIndex != unSortIndex) {
elements[minIndex] = elements[unSortIndex];
elements[unSortIndex] = min;
}
}
return elements;
}
}
最后
这些排序算法的时间复杂度都是 O ( n 2 ) O(n^2) O(n2)。
排序过程中没有申请新的数组空间,都是在原始数组的基础上完成的排序,所以这些都属于原地排序算法,它们的空间复杂度为 O ( n ) O(n) O(n)
下一篇我们来看看归并和快排
如果大家感觉对自己有帮助,请给个赞,这将是对我最大的鼓励了~~~