常见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;
}
}
}
快速排序
思路
视频讲解:王道计算机考研 数据结构8.3_2_快速排序_哔哩哔哩_bilibili
核心思路
在数组中选一个基准值,扫描其余数字,小的放到基准值左边,大的放到基准值右边。然后再对基准值左右两边进行递归。
算法描述
左指针从左往右遍历,找到左边得到内奸(即 不是小于基准值的数),右指针从右往左边里,找到右边的内奸(即 不是大于基准值的数),两者都有内奸的内时候,互换内奸。两指针相遇且都指向基准值的时候,结束。然后再对基准值左右两边进行递归。
代码
/**
* 快速排序
*
* @author 康有为
* @date 2023/04/25
*/
public class QuickSort {
public void quickSort(int[] arr){
quickSort(arr,0,arr.length - 1);
}
public void quickSort(int[] arr,int start,int end){
if (start >= end){
return;
}
int left = start;
int right = end;
int mid = (left + right) / 2;
//三个指针重合就结束
while (!(mid == left && left == right)){
if (arr[left] < arr[mid]){
left++;
}
if (arr[right] > arr[mid]){
right --;
}
//三个指针重合就结束
if (left == right && right == mid){
break;
}
//两支针所指都不符合自己要求,就互换
if (arr[left] >= arr[mid] && arr[right] <= arr[mid]){
int index = arr[left];
arr[left] = arr[right];
arr[right] = index;
}
}
if (start < mid){
//向左递归
quickSort(arr,start,mid);
}
if (mid > end){
//向右递归
quickSort(arr,mid,end);
}
}
}
堆排序
思路
视频讲解:排序算法:堆排序【图解+代码】_哔哩哔哩_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;
}
}
归并排序
思路
视频讲解:8.5_1_归并排序_哔哩哔哩_bilibili
先写出 将两个分别有序部门合并成一个整体有序的 代码: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++;
}
}
}
基数排序(桶排序)
思路
视频讲解:8.5_2_基数排序_哔哩哔哩_bilibili
之前的排序都是每次直接比较元素的大小,而基数排序不是。
- 将所有带比较数值统一为同样的数位长度,数据较短的数前面补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++;
}
}
}
}
}
}
负数的问题
负数不能排序,要排序负数
桶排序对于负数的处理_桶排序可以排负数吗_macro_buaa的博客-CSDN博客