从此不再无序:八大排序算法总结(附Java

swap(arr, j, j+1);

flag = 0; //如果if语句进来后,则说明当前数组还是无序的

}

}

}

}

[](()二、选择排序 (Selection Sort)


选择排序:通过n-i次元素之间的比较,从n-i+1个元素中选出最小(最大)的元素,跟第i个元素交换。

简单来说:看图。

void selectSort(int arr[], int arrLength)

{

if (arr == NULL)

return; //空指针,提前退出

int i,j,minIndex;

for (i = 0; i < arrLength; i++)

{

minIndex = i;

for (j = i + 1; j < arrLength; j++)

{

if (arr[minIndex] < arr[j])

minIndex = j; //保存最小值的下标

}

if (minIndex != i)

swap(arr, minIndex, i); //如果minIndex不是i,说明有最小值

}

}

[](()三、插入排序 (Insert Sort)


插入排序:将一个数据插入到已经排好序的有序表中,从而得到一个新的、数据个数增1的有序表。

动图演示:

void insertSort(int arr[], int arrLength)

{

if (arr == NULL)

return; //空指针,提前退出

int i = 0;

int j = 0;

for (i = 1; i < arrLength; i++)

{

int insertValue = arr[i];

for (j = i - 1; j >= 0 && insertValue < arr[j]; j–) //insertValue < arr[j]

{

arr[j + 1] = arr[j]; //前一个数据往后移动

}

if (insertValue != arr[i]) //如果经过循环后,这两个不相等,说明循环里面移动过数据

arr[++j] = insertValue; //这里值得注意的是,for循环停止时,j-- 已经自减了

}

}

插入排序,就像我们过年时,几个小伙伴一起斗地主一样,每从桌上拿起一张牌,我们就会按照3 4 5 6 7……J Q K A,的顺序进行排列。插入的过程中,其余的牌就要整体移动,给插入的这张牌让一个位置。

[](()四、归并排序 (Merger Sort)


归并排序:就是将一组数据进行二分拆开成左、右两个数组,分别使左右两个数组有序后,再合并到一起。

将大问题拆分为小问题,然而小问题也可以拆分为更小的问题,可以考虑递归函数。

先看动图:

//递归解法

#define MAXNUM 20 //数组最大元素个数

void mergerSort1(int arr[], int left, int right)

{

if (arr == NULL || left == right)

return;

int mid = left + ((right - left) >> 1); //取中间数,也就是 (left + right)/2

mergerSort(arr, left, mid); //递归调用左数组

mergerSort(arr, mid+1, right); //递归调用右数组

merger(arr, left, mid, right); //左右数组分别有序后,合并到一起

}

void merger(int arr[], int left, int mid, int right)

{

int help[MAXNUM]= {0}; //用于临时存储两个数组合并时的有序数组

int i = 0; //指向help数组

int p1 = left; //指向左数组

int p2 = mid + 1; //指向右数组

while (p1 <= mid && p2 <= right)

help[i++] = arr[p1] < arr[p2]? arr[p1++] : arr[p2++]; //谁更小,就放入help数组

while (p1 <= mid) //左边数组还有数据,就放入help

help[i++] = arr[p1++];

while (p2 <= right) //右边数组还有数据,就放入help

help[i++] = arr[p2++];

//将help数组的所有数据按照顺序放入原数组arr

int j = 0;

for (j = 0; j < i; j++)

arr[left+j] = help[j]; //注意是从left位置处开始拷贝

}

将整个数组分成小块,将每个小块的数据变为有序后,再合并起来,这就是归并。

想要写降序,第20行的三目操作符修改一下就可以!

还有就是第7行的取中间数,为什么要这样写??两点原因

  1. 位运算的速度远快于普通的加减乘除!
  1. 考虑溢出的情况,例如int最大的32亿左右,如果此时我的left是19亿,right是19亿,此时二者相加就溢出了整形的范围。

//非递归解法

void mergerSort2(int arr[],int arrLength)

{

int mergerSize = 1; //表示左数组的元素个数,最开始时,左边元素个数1个,右边也为1个

while (mergerSize < arrLength)

{

int L = 0;

while (L < arrLength) //一趟

{

int M = L + mergerSize - 1; //取中间值

if(M >= arrLength)

break; //如果中间值大于等于数组的长度,则提前退出

//取右边数组的范围

int R = (M + mergerSize) < (arrLength - 1)? M+mergerSize:arrLength-1;

merger(arr,L,M,R); //还是调用归并函数

L = R + 1;

}

if(mergerSize > arrLength / 2)

return; //防止整形溢出,提前判断一下

mergerSize <<= 1; //乘2 mergerSize = mergerSize * 2;

}

}

void merger(int arr[], int left, int mid, int right)

{

int help[MAXNUM]= {0}; //用于临时存储两个数组合并时的有序数组

int i = 0; //指向help数组

int p1 = left; //指向左数组

int p2 = mid + 1; //指向右数组

while (p1 <= mid && p2 <= right)

help[i++] = arr[p1] < arr[p2]? arr[p1++] : arr[p2++]; //谁更小,就放入help数组

while (p1 <= mid) //左边数组还有数据,就放入help

help[i++] = arr[p1++];

while (p2 <= right) //右边数组还有数据,就放入help

help[i++] = arr[p2++];

//将help数组的所有数据按照顺序放入原数组arr

int j = 0;

for (j = 0; j < i; j++)

arr[left+j] = help[j]; //注意是从left位置处开始拷贝

}

非递归与递归二者的区别?

当我们递归调用到数据的最底层,最先移动的数据还是下标为0和下标为1的数据,这二者进行比较,此时,其他的参数在栈区里面保存着,当这两个数据操作完成之后,才返回函数,去进入下一个下标2、3的数据进行比较和排序。

反观非递归的解法,则直接定义最开始时,左右数组的元素个数(mergerSize),当下标为0、1的数据操作之后,L直接跳到2、3的位置,一趟排序之后,此时数组中每两个数据是有序的; 此时扩大mergerSize为2,即就是左右数组大小各为2,再次重复上面的操作。

[](()五、快速排序 (Quick Sort)


在讲解快速排序问题之前,我们先了解一下“荷兰国旗问题”。

[荷兰国旗问题](()

从上面的这个题目,我们可以提取出一些算法思想。

假设待排序的数组是{10, 30, 50, 40, 20, 60, 40},我们将数组的最后一个元素40作为“中心点”,将数组中的所有数据都跟“中心点”(40)做比较,比中心点小的,放到数组的前面,比中心点大的放到数组的后面,等于中心点的放到中间。 这样,我们将整个数组分为了三个区域:< 区、= 区、> 区。

经过上面的步骤得到以下数组:

  • {10,30,20,40,60,50,40}; 此时蓝色区域就是 < 区,绿色区域就是 >区。
  • 此时将数组最后一个元素40与绿色区域的第一个元素交换位置
  • 得到{10,30,20 ,40,40, 50,60};

现在看上去,整体从左到右就是一个升序,至于 <区 和 >区 再重复以上步骤就能使其有序。又是递归函数

画图理解一下其中的算法思想,这样才更容易下面的代码!!!

void quickSort(int arr[], int arrLength)

{

if (arr == NULL || arrLength < 2)

return;

process(arr, 0, arrLength - 1);

}

void process(int arr[], int left, int right)

{

if (left >= right)

return;

//随机数srand放入主函数

swap(arr, left + rand() % (right - left + 1), right); //从数组中随机抽取一个元素与数组最后的元素交换位置

//这里也是快排时间复杂度变为O(N logN)的原因

int LMid, RMid; //三个区中,等于区的第一个元素下标,和最后一个元素下标----也就是上面文字解释中的{40,40}的下标

netherlandsFlag(arr, left, right, &LMid, &RMid); //就是上面解释的,将数组分为三个区

process(arr, left, LMid - 1); //递归调用 <区的数据,上面文字解释中的 {10,30,20}

process(arr, RMid + 1, right); //递归调用 >区的数据,上面文字解释中的 {50,60}

}

void netherlandsFlag(int arr[], int left, int right, int* LMid, int* RMid)

{

if (left > right)

{

*LMid = *RMid = -1;

return;

}

if (left == right)

{

*LMid = *RMid = left;

return 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源 ;

}

int minRange = left - 1; //<区范围

int maxRange = right; //>区范围,将最后一个元素先放到>区范围,总共整体排序后,与>区的第一个元素交换

int index = left; //循环判断的索引值

while (index < maxRange) //索引值不跟>区范围遇到,循环继续

{

if (arr[index] < arr[right])

swap(arr, index++, ++minRange); //放入<区,过后,索引值index++

else if (arr[index] > arr[right])

swap(arr, index, --maxRange); //放入>区,过后,索引值不变,因为从>区过来的值还不知道是大是小,所以还需要判断

else

index++; //相等的话,不交换,索引值index++即可

}

swap(arr, maxRange, right); //>区的第一个元素与数组的最后一个元素交换

*LMid = minRange + 1; //等于区的第一个元素

*RMid = maxRange; //等于区的最后一个元素

}

快速排序的时间复杂度能优化到O(N logN),最为关键的就是第14行的交换函数!!!

只有当“中心点”取到数组正中间时,此时的时间复杂度才是最好的,当取到数组的两边时,时间复杂度就很高了。所以加了随机数,使之“中心点”在数组中的出现是等概率的,具体的证明方式,就得看数学功底了!!!

[](()六、堆排序 (Heap Sort)


将堆排序之前,我们先来了解了解“完全二叉树”是个什么!!!

如上图所示

左边是完全二叉树,判断的理由是: 整棵树从上到下,从左到右,没有缺失结点。

右边则不是完全二叉树:理由是: 整颗树从上到下,从左到右,在第三层第二个结点处断了,而第三个结点又还在,形成了一个“空位”,则不是完全二叉树。若将第三层的第三个结点移动到这一层第第二个结点处,则就会变为完全二叉树,如下图:

了解了完全二叉树。 我们就直接进入正题。以大根堆为例,小根堆就是改一下条件即可!

大根堆:顾名思义,就是将数值大的作为根,有以下性质:

  1. 左孩子结点 小于或等于 根结点。
  1. Java开源项目【ali1024.coding.net/public/P7/Java/git】 孩子结点 大于或等于 根结点。

将整个数组变为大根堆之后,此时根结点当前数组中最大的数值,我们就把他取出来,与数组的最后一个元素进行交换即可。

这里需要处理的问题就是:

  • 取出根结点的值后,如何让整颗树还是保持大根堆的形式??

带着这个问题,我们来看代码。

void heapSort(int arr[], int arrLength)

{

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

image

image

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣
左孩子结点 小于或等于 根结点。

  1. Java开源项目【ali1024.coding.net/public/P7/Java/git】 孩子结点 大于或等于 根结点。

将整个数组变为大根堆之后,此时根结点当前数组中最大的数值,我们就把他取出来,与数组的最后一个元素进行交换即可。

这里需要处理的问题就是:

  • 取出根结点的值后,如何让整颗树还是保持大根堆的形式??

带着这个问题,我们来看代码。

void heapSort(int arr[], int arrLength)

{

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

[外链图片转存中…(img-5WD1gKem-1650015247687)]

[外链图片转存中…(img-oUukWkCq-1650015247687)]

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值