前言:
本文是个人阅读以下三篇文章做的笔记。跟着敲了几遍并尝试背诵。
这里仅仅记录个人阅读代码的注解,建议还是去看原文,从排序思想到代码都十分详细,拷过来就能跑,就能直接提交leetcode的那种。
排序(一)——简单排序:插入排序 && 冒泡排序_无限之阿尔法的博客-CSDN博客
排序(二):分而治之——快速排序 && 归并排序_无限之阿尔法的博客-CSDN博客_分而治之的意思是什么
排序(三):谁主沉浮——堆与堆排序_无限之阿尔法的博客-CSDN博客
算法这块以前没有重视过,希望之后能在前辈们的博客中慢慢学习。
原创老哥的个人博客:搬砖日志
大家多多支持呀
一、直接插入排序
/**
* 直接插入排序
*/
int i,j;
for (i =1; i < nums.length; i++){
// 先比较排好序的最后一个元素,如果小于最后一个元素,开始交换
if (nums[i] < nums[i-1]){
int temp = nums[i];
nums[i] = nums[i-1];
// 一直比较,如果nums[i]小于这些元素,这些元素一直向后移动
for (j = i-2; j >= 0 && nums[j] > temp; j--){
nums[j+1] = nums[j];
}
// 直到nums[i]>nums[j], 应该把nums[i]放在nums[j+1]的位置
nums[j+1] = temp;
}
}
二、冒泡排序及其优化
1. 简单的冒泡排序。
/**
* 冒泡排序
*
* i=0, j从0扫描到length-1,最后第length-1个元素是最大元素
* i=1, j从0扫描到length-2,最后第length-2,length-1两个位置是有序的。
* 以此类推
* 第i躺排序开始前,无序序列是0到length-i-1
* 结束时,第length-i-1个元素是这趟排序的最大值,
* 结束时,无序序列是0到length-i-2
* 然后第i+1趟排序开始
*/
int i, j;
for (i = 0; i < nums.length; i++){
for (j = 0; j < nums.length-i-1; j++){
if (nums[j] > nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
2. 优化的冒泡排序。
如果一趟排序发现没有发生交换,则说明已经有序,直接退出循环。
/**
* 冒泡排序优化1
* 记下最后一次交换的位置,后边没有交换,必然是有序的,
* 然后下一次排序从第一个比较到上次记录的位置结束即可。
* 同时引入布尔变量 sorted 作为标记,如果在一趟排序中没有交换元素,
* 说明这组数据已经有序,不用再继续下去。
*/
int temp = 0;
/**
* 最后一次交换的位置j,就是无序、有序的边界
* 0到j是无序的,j+1到length-1是有序的
*/
int lastExchangeIndex = 0; // 上次(最后一次)交换位置
int border = nums.length-1; // 无序列表边界
for (int i = 0; i < nums.length; i++){
/**
* 每一躺开始前,把sorted置为true,如果发生交换,就置为false,说明这一趟经历的序列是无序的
* 一趟结束后,sorted如果还是true,说明这一趟没有交换,序列是有序的,直接跳出排序
*/
boolean sorted = true;
for (int j = 0;j<border;j++){
if (nums[j]>nums[j+1]){
temp = nums[j+1];
nums[j+1]= nums[j];
nums[j]=temp;
/**
* a[j] a[j+1],交换后j+1到length-1是有序的,0到j是无序的
*/
lastExchangeIndex = j;
sorted = false;
}
}
if (sorted) {
break;
}
/**
* 注意区分,边界border 最后交换位置lastExchangeIndex
*/
border = lastExchangeIndex;
}
3. 鸡尾酒排序
基于冒泡排序的优化的双向冒泡排序,适合用于基本有序的序列。
/**
* 冒泡排序优化2
* 鸡尾酒排序(双向冒泡排序)
* 奇数轮从左到右,偶数轮从右到左
*
* 适用于基本有序的序列
*/
int borderRight = nums.length - 1;
int borderLeft = 0;
int lastExchangeIndexRight = 0;
int lastExchangeIndexLeft = 0;
//i<len/2. 不取等号
for (int i = 0; i< nums.length/2; i++){
//正向
boolean sorted = true;
// i到borderRight. 不取等号
// 为什么是i? 因为第i趟左边有i个已经排好序的元素
for (int j = i;j< borderRight; j++){
if (nums[j]>nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
lastExchangeIndexRight = j;
sorted = false;
}
}
if (sorted){
break;
}
borderRight = lastExchangeIndexRight;
//反向
sorted = true;
// len-i-1. 不取等号
// 为什么是len-1-i? 因为第i趟右边有i个已经排好序的元素
for (int j = nums.length-1-i; j>borderLeft;j--){
//特别注意这里是j与j-1比较
if (nums[j] < nums[j-1]){
int temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
lastExchangeIndexLeft = j;
sorted = false;
}
}
if (sorted){
break;
}
borderLeft = lastExchangeIndexLeft;
}
三、快速排序
/**
* 每个部分排序
*/
public int partition(int[] nums, int low, int high){
// 1. 取出枢纽(第一个元素)
int base = nums[low];
// 2. 两个指针 left,right
int left = low, right = high;
// 3. 扫描,要时刻注意left<right这个条件!
while(left<right){
// 从后开始扫描,找到第一个比base小的元素
while (left<right && nums[right]>=base){
right--;
}
//left<right这个条件一定要加上
if(left<right){
nums[left] = nums[right];
}
// 从前开始扫描,找到第一个比base大的元素
while(left<right && nums[left]<=base){
left++;
}
if(left<right){
nums[right] = nums[left];
}
}
// 4. left == right,把base放到这个位置
nums[left] = base;
// 5. 返回枢纽位置
return left;
}
/**
* 递归快速
*/
public void quickSort(int[] nums, int left, int right){
// 1. 递归结束条件,特别注意!
if (left>right){
return;
}
// 2. 递归调用
count++;
// 先进行一趟排序,再对两部分进行排序
int pos = partition(nums, left, right);
//0 - pos-1
quickSort(nums,left, pos-1);
//pos+1 - right
quickSort(nums,pos+1,right);
}
/**
* 快速排序
*/
quickSort(nums,0,nums.length-1);
四、归并排序
- 快速排序,将序列分成两份,是根据一趟排序,枢纽的最终位置划分的
- 二路归并排序,将序列分成两份,是直接(high+low)/2拿到中间坐标的
- 两种排序方法都是递归调用,并且都有一个处理函数
/**
* 进行归并
* 归并的具体过程
* 1. 已经分别得到两个有序序列,
* 2. 要将他归并成一个有序序列
*/
public void merge(int[] nums, int left, int mid, int right){
//1. 临时存储的数组,存储排序好的元素
int[] temp = new int[right - left +1];
//两个序列,左序列left -- mid,右序列 mid+1 -- right 分别有序
//两个指针i, j分别指向左右序列的头
//k用来遍历临时数组temp
//注意不是0,是left
int i = left;
int j = mid+1;
int k = 0;
//2. 开始扫描归并。当两个序列都没扫描完时。注意这个条件。
while(i <= mid && j <= right){
//左序列值 < 右序列值, 将左序列值放到temp数组,扫描左序列下一个
if (nums[i] < nums[j]){
temp[k++] = nums[i++];
}else {
//左序列值 >= 右序列值, 将右序列值放到temp数组,扫描左右序列下一个
temp[k++] = nums[j++];
}
}
//3. 左右序列有一个已经扫描完(以下两个while只会执行其中一个)
//情况1:右序列扫描完,左序列没有。
//把左序列所有的数依次放到temp数组
while (i <= mid){
temp[k++] = nums[i++];
}
//情况2:左序列扫描完,右序列没有。
//把右序列所有的数依次放到temp数组
while (j <= right){
temp[k++] = nums[j++];
}
//4. temp数组就是两个序列归并好的有序的数组,依次放入nums数组中,位置是left--right
// 不能忘记
for (k = 0; k<=right-left; k++){
nums[k+left] = temp[k];
}
}
/**
* 递归处理归并
* 递归调用,直接分成两份分别递归,然后归并
* ★与快排对比,快排是先partition处理,再两个分别递归quickSort★
* ★归并是先递归mergeSort,在merge归并处理★
* ★去理解递归★
*/
public void mergeSort(int[] nums, int left, int right){
if (left < right){
//1. 二路归并,将序列拆分成2个序列
// 注意mid的求法,不是left - right + 1,left - right + 1是长度
int mid = (left + right)/2;
//两个序列分别递归处理
mergeSort(nums, left, mid);
mergeSort(nums, mid+1, right);
//2. 进行处理:将上面拆分的2个序列,归并成一个
merge(nums,left,mid,right);
}
}
mergeSort(nums, 0, nums.length-1);
五、堆排序
这里使用筛选法进行调整堆。
/**
* 堆排序
* 1. 建大根堆,堆顶是最大元素
* 2. 将其与末尾元素交换,此时末尾元素为最大值
* 3. 然后重新调整剩余的元素,得到大根堆,交换倒数第二个元素
* 4. 循环,最终得到一个有序序列。
*/
int len = nums.length;
//1. 从第len/2个元素开始,向上进行建堆
//因为是 0 -- len-1,所以第len/2个元素,下标是len/2 -1
for (int i = len/2 -1; i>=0; i--){
adjustHeap(nums,i,len);
}
//2. 堆顶的最大元素 与 未排序好的序列的末尾元素交换,重新调整堆
//注意这个变量j,堆顶元素与未排序好的末尾元素交换后 nums[0]--nums[j]是无序的序列。需要重新调整堆
// (1)j=len-1: (传入adjustHeap方法中时,作为size,意思是对0--j重新调整)
//① 先把最大的元素放到 len-1 处
//② 然后对0--len-2这些元素重新构建堆
// (2)j=len-2
//① 先把最大的元素放到 len-2 处
//② 然后对0--len-3这些元素重新构建堆
//......
// (len-2)j=2
//① 先把最大的元素放到 2 处
//② 然后对0--1这些元素重新构建堆
// (len-1)j=1
//① 先把最大的元素放到 1 处
//② 然后对0这些元素重新构建堆
for (int j=len-1; j>0; j--){
int temp = nums[j];
nums[j] = nums[0];
nums[0] = temp;
adjustHeap(nums, 0, j);
}
public void adjustHeap(int[] nums, int i, int size){
int temp = nums[i];
/**
* 原来还可以这样for循环
*/
//由于是 0 -- size-1, 所以左孩子是2*i+1
for (int child = 2*i+1; child<size; child = 2*child+1){
//1. 选择 较大的子节点
//如果,结点有右孩子,且 右孩子比左孩子大
if (child+1 < size && nums[child+1]>nums[child]){
//那么选择 右孩子
child++;
} //否则就是左孩子
//2. 父节点 和 较大的子结点 进行比较
//(1)如果父节点 < 较大的子节点,进行交换
if (temp < nums[child]){
// 这里不是交换,这里是覆盖,因为有temp,可以放到最后正确的位置
nums[i] = nums[child];
// (注意)把这个孩子当作父节点,继续执行循环,向下比较
i = child;
}else {
//(2)如果父节点 >较大的子节点,结束比较,跳出循环
break;
}
}
//3. 值放到最后的位置
nums[i] = temp;
}