简介
我们知道任何一个编程语言只是一个工具,学习编程最重要的是编程思维,为此我们学习八大经典排序算法对我们的编程思维有很大的帮助的,为了更好的理解经典的排序算法,使用图文并茂的方式来加深理解,本博文使用的是我个人最容易理解的动态来展示其原理。
冒泡排序
冒泡排序是八大经典排序算法最简单也是最容易理解的算法,它也是这八大排序算法的基础,在这些算法中大部分涉及到交换元素的操作这也是冒泡排序算法的核心所在。
算法基本思想:比较相邻的元素,如果前一个元素比后一个元素大就交换位置,直观的描述为每次遍历都将最大的元素排序到序列的最末端
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况: 平均情况: 最坏情况: | 稳定 |
代码实现
/**
* 冒泡排序
*
* @Author: Tangjiachang
* @Date: 2021/12/22 15:51
* @param num
* @return: void
* @throws
* */
public static void bubbleSort(int[] num){
boolean flg = false;
do{
flg = false;
for (int i = 1; i < num.length; i++) {
if (num[i - 1] > num[i]) {
num[i - 1] = num[i - 1] ^ num[i];
num[i] = num[i - 1] ^ num[i];
num[i - 1] = num[i - 1] ^ num[i];
flg = true;
}
}
}while (flg);
}
选择排序
选择排序基本思想: 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 不稳定 |
代码实现
/**
* 选择排序
*
* @Author: Tangjiachang
* @Date: 2021/12/22 16:36
* @param num
* @return: void
* @throws
* */
public static void selectSort(int[] num) {
for (int i = 0; i < num.length; i++) {
int min = i;
for (int j = i + 1; j < num.length; j++) {
if (num[j] < num[min]) {
min = j;
}
}
int temp = num[i];
num[i] = num[min];
num[min] = temp;
}
}
插入排序
插入排序基本思想: 它通过逐个比较当前元素和其之前已排好序的元素的大小(即比它大放在它的右边,比它小放在它的左边),找到合适的位置插入,并把插入位置后的元素往后移动。
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 稳定 |
代码实现
/**
* 插入排序
*
* @Author: Tangjiachang
* @Date: 2021/12/23 12:32
* @param num
* @return: void
* @throws
* */
public static void insertSort(int[] num) {
for (int i = 1; i < num.length; i++) {
int temp = num[i];
int j;
for (j = i; j > 0 && num[j-1] > temp; j--) {
num[j] = num[j-1];
}
num[j] = temp;
}
}
Shell排序
Shell排序是插入排序的改进版本
Shell排序基本思想:把记录按步长gap分组,对每组记录采用直接插入排序方法进行排序;
随着步长逐渐减小,所分成的组包含的记录越来越多;
当步长值减小到1时,整个数据合成一组,构成一组有序记录,完成排序;
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 不稳定 |
代码实现
/**
* 希尔排序
*
* @Author: Tangjiachang
* @Date: 2021/12/24 11:23
* @param num
* @return: void
* @throws
* */
public static void shellSort(int[] num) {
for (int grap = num.length / 2; grap > 0; grap/= 2) {
for (int i = grap; i < num.length; i++) {
int j = i ;
int temp = num[i];
if (num[j] < num[j - grap]) {
while (j - grap >= 0 && temp < num[j-grap]) {
num[j] = num[j-grap];
j-= grap;
}
num[j] = temp;
}
}
}
}
快速排序
快速排序基本思想: 它利用分治法来对待排序序列进行分治排序,它的思想主要是通过一趟排序将待排记录分隔成独立的两部分,其中的一部分比关键字小,后面一部分比关键字大,然后再对这前后的两部分分别采用这种方式进行排序,通过递归的运算最终达到整个序列有序 。
一个数为基数temp,设置两个指针left = 0,right = n.length,
①从right开始与基数temp比较,如果n[right]>基数temp,则right指针向前移一位,继续与基数temp比较,直到不满足n[right]>基数temp
②将n[right]赋给n[left]
③从left开始与基数temp比较,如果n[left]<=基数temp,则left指针向后移一位,继续与基数temp比较,直到不满足n[left]<=基数temp
④将n[left]赋给n[rigth]
⑤重复①-④步,直到left==right结束,将基数temp赋给n[left]
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 不稳定 |
代码实现
/**
* 快速排序
*
* @Author: Tangjiachang
* @Date: 2021/12/22 15:51
* @param num
* @param left
* @param right
* @return: void
* @throws
* */
public static void quitSort(int[] num, int left, int right) {
int i, j, temp;
if (left > right) {
return;
}
i = left;
j = right;
temp = num[left];
while (i < j) {
while (temp <= num[j] && i < j) {
j--;
}
while (temp >= num[i] && i < j) {
i++;
}
if (i < j) {
num[i] = num[i] ^ num[j];
num[j] = num[i] ^ num[j];
num[i] = num[i] ^ num[j];
}
}
num[left] = num[i];
num[i] = temp;
quitSort(num, left, j - 1);
quitSort(num, j + 1, right);
}
堆排序
利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序基本思想:1、构建初始堆,将待排序列构成一个大顶堆(或者小顶堆),升序大顶堆,降序小顶堆;
2、将堆顶元素与堆尾元素交换,并断开(从待排序列中移除)堆尾元素。
3、重新构建堆。
4、重复2~3,直到待排序列中只剩下一个元素(堆顶元素)。
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
假设给定无序序列结构如下
此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换
将堆顶元素9和末尾元素4进行交换
重新调整结构,使其继续满足堆定义
再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 不稳定 |
代码实现
/**
* 堆排序
*
* @Author: Tangjiachang
* @Date: 2021/12/27 9:30
* @param num
* @return: void
* @throws
* */
public static void heapSort(int[] num) {
for (int i = (num.length) / 2 - 1; i >= 0; i--) {
heapAdjust(num, num.length, i);
}
for (int i = num.length - 1; i >= 0; i--) {
int temp = num[0];
num[0] = num[i];
num[i] = temp;
heapAdjust(num, i, 0);
}
}
/**
* 堆调整
*
* @Author: Tangjiachang
* @Date: 2021/12/27 9:32
* @param num
* @param len
* @param i
* @return: void
* @throws
* */
public static void heapAdjust(int[] num, int len, int i) {
int k = i, temp = num[i], index = 2 * k + 1;
while (index < len) {
if (index + 1 < len) {
if (num[index] < num[index + 1]) {
index += 1;
}
}
if (num[index] > temp) {
num[k] = num[index];
k = index;
index = 2 * k + 1;
} else {
break;
}
}
num[k] = temp;
}
归并排序
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)
归并排序基本思想:对于给定的一组记录,首先将每两个相邻的长度为1的子序列进行归并,得到 n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列。
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 稳定 |
代码实现
/**
* 归并排序
*
* @Author: Tangjiachang
* @Date: 2021/12/24 11:43
* @param num
* @param start
* @param end
* @return: void
* @throws
* */
public static void mergeSort(int[] num, int start, int end) {
if (start < end) {
int mid = (start + end) / 2;
mergeSort(num, start, mid);
mergeSort(num, mid + 1, end);
merge(num, start, mid, end);
}
}
/**
* 两个归并排序算法,两个排好的子序列合并为一个子序列
*
* @Author: Tangjiachang
* @Date: 2021/12/24 11:37
* @param num
* @param left
* @param mid
* @param right
* @return: void
* @throws
* */
public static void merge(int[] num, int left, int mid, int right) {
// 辅助数组
int[] temp = new int[num.length];
int p1 = left, p2 = mid + 1, k = left;
while (p1 <= mid && p2 <= right) {
if (num[p1] <= num[p2]) {
temp[k++] = num[p1++];
} else {
temp[k++] = num[p2++];
}
}
while (p1 <= mid) {
temp[k++] = num[p1++];
}
while (p2 <= right) {
temp[k++] = num[p2++];
}
// 复制回源数组
for (int i = left; i <= right; i++) {
num[i] = temp[i];
}
}
基数排序
基数排序基本思想:把元素统一为同样长度的数组长度 元素较短的数前面补0,比如(1 15 336 看成 001 015 336)然后从最低位开始,以此进行排序。
时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|
最好情况:平均情况:最坏情况: | 稳定 |
r代表关键字的基数,d代表长度,n代表关键字的个数
代码实现
/**
* 基数排序
*
* @Author: Tangjiachang
* @Date: 2021/12/24 11:58
* @param num
* @return: void
* @throws
* */
public static void baseSort(int[] num) {
// 定义二维数组表示十个桶
int[][] buckets = new int[10][num.length];
// 定义一个一维数组,记录放入桶中的元素有多少个
int[] bucketlem = new int[10];
// 获取最大位数
int max = num[0];
for (int i = 1; i < num.length; i++) {
if (num[i] > max) {
max = num[i];
}
}
// 判断max是几位数
int maxlength = (max + "").length();
// 第一轮: 针对个位数进行排序
for (int i = 0, n = 1; i < maxlength; i++, n *= 10) {
for (int j = 0; j < num.length; j++) {
int digit = num[j] / n % 10;
// 放入桶中
buckets[digit][bucketlem[digit]] = num[j];
bucketlem[digit]++;
}
// 把元素返回到原来数组
int index = 0;
for (int k = 0; k < bucketlem.length; k++) {
if (bucketlem[k] != 0) {
for (int l = 0; l < bucketlem[k]; l++) {
num[index++] = buckets[k][l];
}
}
bucketlem[k] = 0;
}
}
}