前序
今天我们来聊一聊数据结构里面的排序,我和大家说的方式会和大众的一点不同,我喜欢把他们分类,通过进阶的方式来一步一步的全部展现排序的方法。本文的排序都是从小到大啊
我们就先带大家知道一下主流的排序方法有什么,1.直接插入排序。2.希尔排序。3.选择排序。4.堆排序。5.冒泡排序。6.快速排序。7.归并排序。
正文
一讲到排序,我就想到了打牌,因为我们是几个人再一堆牌里面,各拿各的,就是你拿一张,我拿一张,这样的顺序(应该没有人连牌都没有打过吧,你觉得我会相信啊),而我们又需要理牌,因为这样方便我们打牌(你说你打牌不理,我尝试想一想啊,你最好来和我打,我就喜欢你这种散财童子,别带套路哦,不然我会跑路的),因此理牌就是必不可少的。那你一般怎么理牌啊,我见过两种理牌的方式,第一种是我拿一张就把这个插到这个牌应该在的位置上面,还有一种就是他等全部牌发完了,然后他拿起乱的牌,拿出里面最小的牌,然后是第二小的牌,放在最小的后面,也就是从小到大拿。而这两种理牌的方式,就是排序算法最基础的地方。
以直接插入排序来进阶的排序
直接插入排序
我们先聊第一种,拿到一张牌,就放到对应的位置,而这种方式就是直接插入排序,我们在计算机中就是假如在一个顺序表中添加其他的元素,而这样就一定会产生搬运(就是我后面的数都要向后移一位,最形象的就是你在排队的时候,别人在你前面插队,这样你是不是就相当于后移了一位,)而直接插入排序的好处就是在大多数有序的时候,效率是高的,而我们就利用这个特性创建了希尔排序,
希尔排序
你想我们想要利用这个特征的前提是大多数有序,但是我们总不可能要求用户的输入吧(唉,那个同学,你这个输入的不好,怎么一个大一个小,过来过来,给我重新搞,不然不让你下课),所以我们就要另想办法,就有人想到,很多数的时候大多数没有序,那我一点点数的时候,要修改的数是不是就少了,所以我们就可以把一个长的数组分为小数组来直接插入排序,所以我们就给数据分组,怎么分组呢,我们先设定要分几组,假如3组(好,全体队友,报数,1,2,3,4,这个同学,我没有说报1到3吗,你有没有在听啊,好好好,知道了,报数,1,2,3,1,2,3......,好报到1 的同学出列,20个俯卧撑,报到2的同学,30个俯卧撑,报到3的同学,边上看就好了)这样是不是就可以把一个班的同学(一个长的数组)分为了三个小团体(小数组),而我们计算机就是这么普通的方式,但是我们就这样分完了,再让他们小团体里面排好了队(假如按身高),让他们回到大团体的时候,大概率还是乱的,因为第一个团体的第一个人不一定比第二个团体的第一个人矮,所以我们就不可以只分一次 ,我们需要多分几次,而这个怎么分,就是一个专业人士(好像是希尔排序的发明人)说我们从总长的1/2,开始排,每次/2,一直到分1组(就是全部人在一起排)的时候,就全部排好了(应该有人想,不是,我最后都要分1组来排,为什么我不一开始直接排,但是事实证明你是有道理的,但是也不可以这么说,因为希尔排序就是使用大多数有序的时候直接插入效率是很高的,但是高的有限,所以我们也就很少使用了)
以选择排序来进阶的排序
选择排序和冒泡排序
这个就是第二种拿牌的方式,先选择最大的元素,放在数组的最后面,然后拿第二大的,依次放好,这个就是选择排序的排序思路,特别简单,而我们这么拿到最大的值呢,其实也特别简单,就是打擂台,每个人都上去打一架,全部打完擂台上面就是最强的那个(有没有上面的体力被消耗了,被后面更弱的打下来了呢,我们就假设打完一场就满血复活不就好了),我们就这样找出第一名,第二名,依次排好就可以了。而冒泡排序也是这样就不额外聊了,但是就有人想了,这样每一个都打一场,时候好像有点多,我们可不可以缩短这个时间呢。
堆排序
所以我们就想到了,我们第一次只需要最强的,我们就两人打架,赢的进入下一轮,就是我们现在很常见的半决赛,4个人打,赢的2个人打总决赛,再赢的就是冠军,而我们计算机中刚刚好就有一种数据结构完全符合,就是堆,所以我们就可以把全部的数据建一个堆,而堆顶元素就是最大的,我们把这个元素放到数组的最后面,我们再对这个少了一个元素的堆进行调整,让他们再次打出冠军来就是第二厉害的,这样依次操作就排序好了。
快速排序
而我们再选择排序另一个分支,我们拿起排来了,首先要找最大或最小的牌,假如你找到了,结果后面突然看到了还有几张更小的牌,就会有点无语。这样你就机缘巧合下触发了快速排序,为什么因为你找到的不是最小的牌啊,是全部牌中的一张随机牌,而快速排序就是选择一张随机牌,左边是小于这个牌的,右边是大于这个牌的。那我们要这么实现这个操作呢,聪明的人就想出了双指针法解决这个问题,
假如再一个随便的数组里面比如,9,6,1,8,3,7,我们把最右的7设为标准值(标准值哪个都可以,这个也是我随便写的数),我们第一个指针找到比标准值大的(从头开始不符合就下一个),第二个指针找到比标准值小的(从末尾开始不符合就下一个)
那我们什么时候停呢,这个简单就是两个指针重合了就停下,但是你想想,最后面的7这么处理啊,欸,左指针找到一个比他大的数替换不就好了啊,所以,我们需要左指针先走,左指针找到了,右指针才可以找,如果右指针没找到,就和左指针重合了,就把标准值和左指针替换。那有没有人想,我左指针一直都找不到怎么办,1是之前没有替换过,那不就是标准值就是最大的吗,2.是替换过,那重合的时候,右指针不刚刚好就比标准值大,拿右指针的不就好了如图
我们就完成了左边小于标准值小于右边,我们把左边拿去递归,右边拿去递归不就好了。这个还是有点意思的,你们看看代码吧
public static void QuickSort(int[] arr){
quickSort(arr,0,arr.length-1);
}
public static void quickSort(int[] arr, int left, int right){
if(left >= right){
return;
}
int index = partition(arr,left,right);
quickSort(arr,left,index - 1);
quickSort(arr,index + 1,right);
}
public static int partition(int[] arr,int left,int right){
int index = arr[right];
int leftIndex = left;
int rightIndex = right;
while (leftIndex < rightIndex) {
while (leftIndex < rightIndex && arr[leftIndex] <= index){
leftIndex ++;
}
while (leftIndex <rightIndex && arr[rightIndex] >= index){
rightIndex --;
}
Swap(arr,leftIndex,rightIndex);
}
Swap(arr,leftIndex,right);
return leftIndex;
}
public static void Swap(int[] arr, int left, int right){
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
再快速排序的基础上面我们不想要这个中间值了,就演变出了归并排序,原理不同啊
归并排序
我就想起我亲身经历的一件事,就是我在去寺庙的时候,大家不都想要祈福吗,我们还有点素质,嘿嘿,知道排队,但是我们排了两条队,而每次只可以进一个人,然后那个僧人就大吼大叫说我们排一条队,队都不会排,这个样子还来这里干什么,这个事使我印象深刻就在我印象中僧人不是很优雅,不知道算不算优雅,反正应该不会大吼大叫,更不应该说这样的话,我就感觉现在寺庙的风气变了,真正的僧人可能在苦修,而假的僧人剃个头就到寺庙里面来了,不说了,回归正题,我们两个队嘛,但是只能进一个人,所以我们就需要排一条队,总不可能让某条队的重新排吧,所以呢,我们就插了一个小队,他们也好讲话(毕竟大家都排了一下子)就让我们插队了,归并排序就是这样,就比如两个队列,我们按照一定的顺序排成一条队,
1插3前面,4插5前面,但是有没有发现一个前提,我们的两个数组是要有序的,排完后才是一个有序的,而我们怎么使这个这两个数组也有序呢,递归啊,没序就递归,一直到只有一个数的时候,不就有序了啊,我们在想上面这样穿插,归并排序的思路就写好了,这个也有点意思,看看代码
public static void mergeSort(int[] arr){
mergeSort(arr,0,arr.length-1);
}
public static void mergeSort(int[] arr,int left ,int right){
if(left >= right){
return;
}
int mid = (right + left)/2;
mergeSort(arr,left,mid);
mergeSort(arr, mid + 1, right);
merge(arr,left,mid,right);
}
public static void merge(int[] arr,int left, int mid ,int right){
int [] result = new int[right - left + 1];
int LeftHead = left;
int RightHead = mid + 1;
int resultId = 0;
while (LeftHead <= mid && RightHead <= right) {
if (arr[LeftHead] > arr[RightHead]) {
result[resultId++] = arr[RightHead++];
}else {
result[resultId++] = arr[LeftHead++];
}
}//把两个数组中整合起来
while (RightHead <= right) {
result[resultId++] = arr[RightHead++];
}
while (LeftHead <= mid){
result[resultId++] = arr[LeftHead++];
}
//处理数组中多余的数据
for (int i = 0;i < result.length;i++){
arr[left + i] = result[i];
}
}
简单又强势的算法--哈希表
主播主播前面的算法还是太吃操作了有没有简单又强势的操作推荐一下,有的兄弟有的,就是我们的主角也是最常用的算法--哈希表。
在查找数据的时候,我们为什么不直接设定一个数组,我们通过访问数组的下标来查找到响应的数字呢,而我们哈希表就是这样想的,假如我们有数据(范围是0到99,数量特别多),我们就可以设计一个数组范围就是0到99,一个下标就对应数据的值,而下标的内容就是这个值出现的次数,这样我们在查找这个数的时候,直接通过下标访问就好了,这样我们的查询效率就达到了O(1),是不是简单又强势,但是问题就来了,如今数据越来越多,一个int就-2,147,483,648 到 2,147,483,647,我们总不能创建一个int[2147483648]吧,所以我们就想要对进行改变,我们想要int[1000]就可以达到存储2147483648的数(这个就是降本增效),那这样我们不好想,我们想在公司里面的降本增效是什么样子的,不就是一个人多干几个人的伙吗,所以我们就想数组中一个元素可以多存储几个数据,那我们就需要想办法了,怎么多存放几个数据,哎,我们把这个元素变成一个小的数据结构不就可以解决了吗,所以我们就想到了数组中创建一个小链表(为什么是链表,后面细说),是不是就刚好解决了这个问题,那什么样子的数据,我们可以存放在一起呢,大佬们就一起发明了一些方法(常见的哈希函数),取余数就是很常见的一种方法,就比如100放在10的数组里面(数字大了0有点多不好看)我们就可以使用取余数的方法,比如9%10 = 9,19%10 = 9,所以9和19都可以放在数组中9的位子,只不过19可能就是以链表的形式挂在9的位子上,就是在排队的时候,你前面有情侣(他们并排站嘛),想象一下,我没有找到合适的图片。
负载因子
这个就是链表挂出来的平均数量,100个数据,10的位子,就是100/10,负载因子就是10,我们为什么要聊负载因子呢,你想想,链表的操作效率高不高,确实是不高的,因为我们要遍历链表啊,所以链表太长了就拖我们后腿,所以这个负载因子不易过大,而多少比较好呢,官方是说0.75比较好,多少我们还是要具体问题具体分析啊。
我自己的一些小问题
我一开始想,假如有1000个数据,我们存放在int[500]里面,负载因子就是1000/500 = 2,就是需要平均在每个元素下面都加一个节点,就是有500个节点,那我把500个节点和int[500]C加起来空间可能比int[1000]空间还要更大呢,代码编写还要更复杂,哈希表还要遍历链表,所以效率还要更低,那我为什么要使用哈希表呢,我也是把这个问了我的老师(不管了,不管老师会不会怀疑我是sb),老师说1亿的用户,你需要创建1亿的数组大小,而哈希表只需要1亿*0.75(负载因子还可以更高),空间是不是更小,哈希表主要是处理数据的动态变化,这样可以使空间的利用率更高,所以这个也是我们为什么使用链表的原因,链表的元素,我们可以随时创建随时释放,有没有人想在数组中的元素中在创建一个数组的,其实我也想过,嘻嘻,但是我知道这个是没用的,因为搞一个二维数组出来,你要恶心你自己啊。当然有没有不是链表的呢,也有就比如如果链表的深度太深了,我们就创建一个堆(反正就是那个效率高使用哪个呗)。
嘻嘻,今天的分享就到这里了啊,看到这里了还不点关注啊,下次就找不到我了嘞
&spm=1001.2101.3001.5002&articleId=147854861&d=1&t=3&u=8e37e8a9c8a04ebfb46c66eba4039d2e)
8393

被折叠的 条评论
为什么被折叠?



