目录
常见8大排序算法
分别是冒泡排序、选择排序、插入排序、希尔排序、快速排序、堆排序、归并排序、基数排序(桶排序)
冒泡排序
思路
n个数字从小到大排序,每个数和它后面的数比较,小的放前面,大的放后面,依次执行,这样一轮下来就能将一个最大的放到最后。执行n-1轮就能全部排序完。
代码
/**
* @author 康有为
* 冒泡排序
*/
public class BubbleSort {
public void bubbleSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1; j++) {
if (arr[j] > arr[j+1]){
int index = arr[j];
arr[j] = arr[j+1];
arr[j+1] = index;
}
}
}
}
}
选择排序
思路
n个数字从小到大排序,每轮选择一个最小的放到最前面,执行n-1轮
代码
package 排序;
/**
* @author 康有为
* 选择排序
*/
public class SelectSort {
public void selectSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
//找出一个最小的
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]){
min = j;
}
}
//与第一个交换
int index = arr[i];
arr[i] = arr[min];
arr[min] = index;
}
}
}
插入排序
思路
类似打斗地主时,摸一张插入到手里排序,再摸一张插入到手里排序。
n个数字从小到大排序,将数字分成未排序的和已排序的两部分,每次 从未排序的中拿出一个,放入到已排序中进行排序:如果比前面的大,那直接结束,如果比前面的小,就与之交换位置,再与前面的交换,一直到头,一轮结束。
代码
/**
* @author 康有为
* 插入排序
*/
public class InsertSort {
public void insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]){
int index = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = index;
}else {
break;
}
}
}
}
}
希尔排序
思路
视频讲解:王道计算机考研 数据结构8.2_2_希尔排序_哔哩哔哩_bilibili
希尔排序是在插入排序的基础上进行改进,所以请先理解好插入排序,再来看希尔排序。简单插入排序是对一组数据进行插入排序,而希尔排序是现将数组分成几个组,让其在小组里面排序,一开始分成 len/2个小组,排序完毕后,再分成len/2/2个小组,一直到分成1个小组,执行简单插入排序。
代码
/**
* 希尔排序
*
* @author 康有为
* @date 2023/04/25
*/
public class ShellSort {
public void shellSort(int[] arr) {
int gap = arr.length / 2;
//while循环来改变gap,每次除2
while (gap >= 1) {
//遍历每个元素,将其在其所在的组里进行简单插入排序
for (int i = 0; i < arr.length; i++) {
for (int j = i; j >= gap; j -= gap) {
if (arr[j] < arr[j - gap]) {
int index = arr[j];
arr[j] = arr[j - gap];
arr[j - gap] = index;
} else {
break;
}
}
}
gap = gap / 2;
}
}
}
快速排序
思路
视频讲解:基础篇-25-快速排序_单边循环(lomuto)_演示_哔哩哔哩_bilibili
核心思路
在数组中选一个基准值,扫描其余数字,小的放到基准值左边,大的放到基准值右边。然后再对基准值左右两边进行递归。
核心思路简单,但是代码思路不好想。
代码思路
单边循环快排(lomuto洛穆托分区方案)
- 选择最右元素作为基准点元素
- j 指针负责找到比基准点小的元素,一旦找到则与i 进行交换,交换完毕i++
- i 指针维护小于基准点元素的边界,也就是每次指向待交换的元素
- 最后基准点与i 交换,i 即为分区位置
- 交换完毕后,基准值左边比之小,右边比之大,再分别对其左右两边递归
演示图
代码
/**
* 快速排序
*
* @author 康有为
* @date 2023/05/05
*/
public class QuickSort {
public QuickSort(int [] arr){
helpSort(arr,0,arr.length-1);
}
void helpSort(int [] arr,int start,int end){
int i = 0;
//以最后一个数字作为基准点
//j遍历数组从头到倒数第二个,负责找出比基准点小的数字,找出来之后与 i 交换,i右移
for (int j = 0; j < end; j++) {
if (arr[j] < arr[end]){
swap(arr,j,i);
i++;
}
}
//遍历完毕之后,i的左边都是比基准值小的,i包括i的右边都是比基准值大的
swap(arr,i,end);
//交换完毕后,i所指的就是基准值
//左递归:如果中间值i 的左边有两个以上数字时才需要左递归
if (i >= start+2){
helpSort(arr,start,i-1);
}
//右递归:如果中间值i 的右边有两个以上数字的时候才需要右递归
if (i <= end-2){
helpSort(arr,i+1,end);
}
}
/**
* 交换
*/
void swap(int[] arr, int a ,int b){
int index = arr[a];
arr[a] = arr[b];
arr[b] = index;
}
}
堆排序
思路
视频讲解:排序算法:堆排序【图解+代码】_哔哩哔哩_bilibili
步骤就是两步:1.构建大顶堆 2.进行堆排序
1.构建大顶堆
什么是大顶堆?
下图就是一个大顶堆
大顶堆要求:每个根节点都要比两个子节点大。(小顶堆同理,每个根节点要比两个子节点小)
使用数组来存储大顶堆
规律
下标为i的节点的父节点下标::( i - 1 ) / 2【整数除法】
下标为i的节点的左孩子下标:i * 2 + 1
下标为i的节点的右孩子下标:i * 2 + 2
怎么维护一个大顶堆?
参数:
- 数组
- 要维护的父节点的小标
步骤
- 找到父节点的两个孩子节点
- 将大的那个孩子节点与父节点交换
- 交换之后,对交换的那个子节点,进行递归维护,因为刚刚的维护,原本子节点也是大顶堆,交换之后可能破坏了
代码
/** * heapify * 维护大顶堆 * * @param arr 数组 * @param parentNode 要维护的父节点的下标 * @param len 大顶堆的长度 */ public void heapify(int[] arr, int parentNode, int len){ int lSon = parentNode * 2 + 1; int rSon = parentNode * 2 + 2; //左孩子节点 如果 大,那就交换位置 if (lSon < len && arr[lSon] > arr[parentNode]){ swap(arr,lSon,parentNode); //交换完毕后再对左孩子节点进行递归维护 heapify(arr,lSon,len); } //右孩子节点 如果 大,那就交换位置 if (rSon < len && arr[rSon] > arr[parentNode]){ swap(arr,rSon,parentNode); //交换完毕后再对右孩子节点进行递归维护 heapify(arr,rSon,len); } } /** * 数组元素交换 */ void swap(int[] arr,int a, int b){ int index = arr[a]; arr[a] = arr[b]; arr[b] = index; }
构建大顶堆也就是将一个无序的堆维护成大顶堆
维护的顺序是从最后一个父节点开始,从后往前维护
例如下面的大顶堆
那么我们就得从最后一个4开始维护,维护完4这个父节点,再维护1这个节点。
因为 下标为i的节点的父节点下标::( i - 1 ) / 2【整数除法】,所以我们开始遍历的维护大顶堆
2.进行堆排序
思路
将无序的数组排成大顶堆,这样首元素就是最大的,然后将其放到最后,大顶堆长度减1,再维护大顶堆,重复操作
类似选择排序,选择排序是将最小的元素放到前面,堆排序是通过大顶堆,将最大的元素放到后面
也就是将最后一个元素和 大顶堆堆顶的元素交换位置,并将最后一个元素从大顶堆中删除,也就是让大顶堆的长度减1,再维护以下这个大顶堆即可
void heapSort(int[] arr){
int len = arr.length;
//1.建立大顶堆:从最后一个父节点开始向前遍历维护大顶堆
for (int i = (len-1-1)/2; i >= 0 ; i--) {
heapify(arr,i,len);
}
//2.开始排序:将最后一个元素与大顶堆堆顶元素交换,然后大顶堆长度减1
for (int i = len-1; i > 0; i--) {
swap(arr,i,0);
heapify(arr,0,i);
}
}
代码
package 排序;
/**
* 堆排序
*
* @author 康有为
* @date 2023/04/26
*/
public class HeapSort {
public void heapSort(int[] arr){
//1.建立大顶堆:需要从后往前的遍历每个父节点,对父节点进行维护
int len = arr.length;
for (int i = (len-2)/2; i >= 0 ; i--) {
heapify(arr,i,len);
}
//2.开始排序
for (int i = 0; i < len ; i++) {
swap(arr,0,len-1);
len--;
heapify(arr,0,len);
}
}
/**
* heapify
* 维护大顶堆
*
* @param arr 数组
* @param parentNode 要维护的父节点的下标
* @param len 维护数组的长度
*/
void heapify(int[] arr, int parentNode, int len){
int lSon = parentNode * 2 + 1;
int rSon = parentNode * 2 + 2;
//左孩子节点 如果 大,那就交换位置
if (lSon < len && arr[lSon] > arr[parentNode]){
swap(arr,lSon,parentNode);
//交换完毕后再对左孩子节点进行递归维护
heapify(arr,lSon,len);
}
//右孩子节点 如果 大,那就交换位置
if (rSon < len && arr[rSon] > arr[parentNode]){
swap(arr,rSon,parentNode);
//交换完毕后再对右孩子节点进行递归维护
heapify(arr,rSon,len);
}
}
/**
* 交换
*/
void swap(int[] arr, int a ,int b){
int index = arr[a];
arr[a] = arr[b];
arr[b] = index;
}
}
归并排序
思路
先写出 将两个分别有序部门合并成一个整体有序的 代码:merge
然后递归对每部分进行合并
1.合并两个有序部分
找一个辅助数组,从两个有序数组中分别取出小的,放到里面,最后就是有序的。
/**
* 合并
* 合并数组中 两个 有序的连续片段
* 也就是将左边有序的部分 和 右边有序的部分 合并成一个大的有序部分
*
* @param arr 数组
* @param left 左
* @param middle 中间
* @param right 右
* @param temp 临时数组
*/
void merge(int [] arr,int left,int middle,int right,int []temp){
int i = left; // 初始化i, 左边有序序列的初始索引
int j = middle + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= middle && j <= right) {//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp数组
//然后 t++, i++
if(arr[i] <= arr[j]){
temp[t] = arr[i];
t++;
i++;
}else { //反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t++;
j++;
}
}
//把有剩余数据的一边的数据依次全部填充到temp
//左边的有序序列还有剩余的元素,就全部填充到temp
while (i <= middle){
temp[t] = arr[i];
t++;
i++;
}
//右边的有序序列还有剩余的元素,就全部填充到temp
while (j <= right){
temp[t] = arr[j];
t++;
j++;
}
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
while (tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
2.递归将数组分成两份,进行合并
一直除2 来分成两部分,直到分成一个数,这时候,一个数是有序的,再回退就是两个数,将两个数合并,再往上。。。。。。
void sortEntrance(int [] arr,int left, int right,int []temp){
if (left < right){
int mid = (left+right)/2;
//向左递归
sortEntrance(arr,left,mid,temp);
//向右递归
sortEntrance(arr,mid+1,right,temp);
//归并
merge(arr,left,mid,right,temp);
}
}
代码
package 排序;
import java.util.Arrays;
/**
* 归并排序
*
* @author 康有为
* @date 2023/04/28
*/
public class MergeSort {
public void mergeSort(int []arr){
int [] temp = arr.clone();
sortEntrance(arr,0,arr.length-1,temp);
}
void sortEntrance(int [] arr,int left, int right,int []temp){
if (left < right){
int mid = (left+right)/2;
//向左递归
sortEntrance(arr,left,mid,temp);
//向右递归
sortEntrance(arr,mid+1,right,temp);
//归并
merge(arr,left,mid,right,temp);
}
}
/**
* 合并
* 合并数组中 两个 有序的连续片段
* 也就是将左边有序的部分 和 右边有序的部分 合并成一个大的有序部分
*
* @param arr 数组
* @param left 左
* @param middle 中间
* @param right 右
* @param temp 临时数组
*/
void merge(int [] arr,int left,int middle,int right,int []temp){
int i = left; // 初始化i, 左边有序序列的初始索引
int j = middle + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= middle && j <= right) {//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp数组
//然后 t++, i++
if(arr[i] <= arr[j]){
temp[t] = arr[i];
t++;
i++;
}else { //反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t++;
j++;
}
}
//把有剩余数据的一边的数据依次全部填充到temp
//左边的有序序列还有剩余的元素,就全部填充到temp
while (i <= middle){
temp[t] = arr[i];
t++;
i++;
}
//右边的有序序列还有剩余的元素,就全部填充到temp
while (j <= right){
temp[t] = arr[j];
t++;
j++;
}
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
while (tempLeft <= right){
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
}
基数排序(桶排序)
思路
之前的排序都是每次直接比较元素的大小,而基数排序不是。
- 将所有带比较数值统一为同样的数位长度,数据较短的数前面补0。
- 定义10个“桶子”,依次是0-9。
- 将待排序数组的元素的最低位 对应放到桶子里面,然后再收集起来。
- 在将待排序数组的元素的下一位 对应放到桶子里面,再收集。
- 循环到最高位,收集起来就排序好了。
总的来说就是将元素 按照它的从最低位放到对应的桶里面、收集起来,这样从最低位一直到最高位也放到桶里面,收集完毕,数组就有序了。
如果要从小到大排序,就从0号桶开始收集,如果要从大到小排序,就从10号桶排序
代码
package 排序;
/**
* 基数排序
*
* @author 康有为
* @date 2023/05/04
*/
public class RadixSort {
public void radixSort(int []arr){
int arrLen = arr.length;
//找出数组最大的元素
int max = arr[0];
for (int i = 1; i < arrLen; i++) {
if (arr[i] > max){
max = arr[i];
}
}
//统计最大元素的长度
int maxLen = 1;
int index = 1;
while (true){
index = index * 10;
if (Math.abs(max) >= index){
maxLen ++;
}else {
break;
}
}
//将数组元素的 某一位 放到桶里面,位数从末位到首位
//如何拿到元素的 每一位?例如数字321,321 % 10 /1 = 1,321 % 100 /10 = 2,321 % 1000 /100 = 3
//从最低位 遍历 到最高位
for (int i = 1; i <= maxLen; i++) {
//定义桶
int[][] barrel = new int[10][arrLen];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
int[] barrelNum = new int[10];
//将元素放到桶里面
for (int j = 0; j < arrLen; j++) {
//拿到数组的某一位
int num =(int) (arr[j] % Math.pow(10,i) /Math.pow(10,i-1));
//将数组的元素按照某一位放置到 对应的桶里面
barrel[num][barrelNum[num]] = arr[j];
barrelNum[num]++;
}
//从每个桶里取出元素
index = 0;
for (int j = 0; j < 10; j++) {
//只有桶里面有数据才取出
if (barrelNum[j] != 0){
for (int k = 0; k < barrelNum[j]; k++) {
arr[index] = barrel[j][k];
index++;
}
}
}
}
}
}
负数的问题
负数不能排序,要排序负数