文章目录

鸽了好久了,希望大家能够理解,排序算法的学习和码文确实挺费时间的,本篇文章重要讲解以下排序算法和其拓展,其他排序以后会补充

概念提前说明一下,方便大家理解后续文章
- 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起
来的操作- 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记
录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍
在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的- 内部排序:数据元素全部放在内存中的排序
- 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据
的排序
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想


我们这里设排好了的数组的最后一位的位置为end,设temp = a[end + 1]

end+1=2, 2向前找比它小的数,如果没找到或者已经到头了,就将比他大的数全部为后面移动一位,所以这里5位后面移动一位,然后将已经保存在temp的2赋给啊a[end+1]
可能有人说这种情况不典型,好再讲一个比较典型的

这里3向前面找比他小的,然后他找到了2,在没找到之前,讲4,5,6向移动一位,在找到后,将3赋给找到的数的前面一位
以下为完整过程

void InsertSort(int* a, int m)
{
int i;
for (i = 0; i < m - 1; ++i)
{
int end = i;
int temp = a[end + 1];
while (end >= 0)
{
if (a[end] > temp)//这一步是将大于temp的数全部向后移动
{
a[end + 1] = a[end];
end--;
}
else//找到比temp小的,就直接退出
{
break;
}
}
a[end + 1] = temp;
}
}
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:最坏情况为逆序时,或者接近逆序时O(N^2),最好情况为升序时,或者接近升序时O(N)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
当接近有序时,我们将比temp大的数移动的次数将会减少,所以算法的效率更高,因此我们就需要对原本的数据进行处理,使得效率得以提升,那就是希尔排序
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序
这里放一张图,基本就清楚了
我们对其进行分组,每组都用直接插入排序进行排序,那么整体就接近有序了,当gap=1时就是直接插入排序了
可能有细心的小伙伴就发现了,无非就是将插入排序的1改成了gap吗?也确实是这样,无非是在插入排序上加了一个while循环控制gap而已
void ShellSort(int* a, int m)
{
int gap = m;
while (gap > 1)
{
gap = gap / 3 + 1;//这里加一就可以保证gap最后为1,从而变成直接插入排序
for (int i = 0; i < m - gap; i++)
{
int end = i;
int temp = a[end + gap];
while (end >= 0)
{
if (a[end] > temp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = temp;
}
}
}
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试对比。
- 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
- 稳定性:不稳定
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的
数据元素排完
maxi向前面找最大的数,maxi记录最大的数的下标,mini向前找最小的数,mini记录最小的数的下标,然后将min与begin位置的值交换,将max与end位置的值交换
如果在maxi交换或者mini的交换过程中交换的值刚好出现在begin或者end,那么就会出现bug,bug有以下处理方法

你问我为什么手写?哈哈哈,手写的更加引人瞩目一点,你要是说我字丑,我就打洗你(
void SelectSort(int* a, int m)
{
int begin = 0;
int end = m - 1;
while (begin < end)
{
int maxi = begin;
int mini = begin;
for (int i = begin; i <= end; i++)
{
if (a[maxi] < a[i])
{
maxi = i;
}
if (a[mini] > a[i])
{
mini = i;
}
}
Swap(&a[end], &a[maxi]);
if (mini == end)//这里不懂看注意事项
{
mini = maxi;
}
Swap(&a[begin], &a[mini]);
begin++;
end--;
}
}
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:传统O(N^2),改进的代码能到N*logN
- 空间复杂度:O(1)
- 稳定性:不稳定
堆排序就需要对前面的二叉树有一定的理解才能弄明白,如果不懂的可以移步至我的前一篇文章
一张图解决
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是
通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
堆排序主要用的是向下调整算法·,向下调整算法建立在左右子树已经是大堆或者是小堆才能有用,那么向下调整的思想是怎么实现的?
如果我们是建大堆,向下调整算法是将一个节点与其子节点的最大值比较比较,如果子节点有比其大的,就交换两者的值,然后继续向下,将交换的节点的子节点继续向下比直到叶子节点(此算法有一个大前提),就是子节点必须是大堆或者小堆
那么就有人问如果子节点不是大堆或者小堆怎么办?我们可以从最底部不是叶子节点的位置开始(因为叶子节点本身就身就是大堆或者小堆),对每个每个节点都使用向下调整算法,一直调到root节点,那么一个堆就完成了,然后最大的值放在最后,然后继续对这个最大的数的前面的数建堆,然后继续与未排序好的数组的交换将是不是就ok了呢?这就是堆排序的思想,总是搞出最大的数排在最后
我们拿这个二叉树讲

这里我们建大堆,我们首先找到最后一个非叶子节点8,然后用8比较其节点,无需交换在到7这个节点,其应该与9交换,然后就到2,其应该与6交换(这是建堆的过程,后面的交换过程我就不讲了)

然后看这图

//向下调整代码(父子关系不清楚的看堆的介绍)
void AdjustDown(int* a, int n, int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序算法
void HeapSort(int* a, int n)
{
int i;
int end = n - 1;
//建堆,将一个数组建成一个堆的样子
for (i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//交换堆的root到最后,然后将数组的大小减小,然后继续向下用调整算法
while (end > 0)
{
Swap(a, &a[end]);
end--;
AdjustDown(a, end + 1, 0);//因为这个数组已经是一个堆了所以我们只需要对其用向下调整算法就行了
}
}
这个算法的时间复杂度不好求解,所以我来讲解一下
有人问为什么用图?因为图清晰明了(忽略字,说字丑的小心我打洗你)
求堆排序的复杂度需要知道高中的数列的错位相减法(自行了解)
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度:向下调整算法O(N)
- 空间复杂度:O(1)
- 稳定性:不稳定
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排
序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
与水中的气泡上浮相似,大的先上去,小的后上去
一图解决

这里偷下懒,更加推荐各位去看我前面的文章,这里对冒泡排序有更加深刻的解释冒泡排序改进
- 冒泡排序是一种非常容易理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序
元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有
元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所
有元素都排列在相应位置上为止
先设立一个key记录begin的值,再在begin处设个坑pivot,先让end从后往前找比key小的值,找到了就将这个值放入坑中,然后这个比key小的值就成为了坑,然后用begin向后找比key大的值找到了就放入坑中,重复上述过程最后begin和end相交,这个位置就是key应该到的位置(代码中的三数取中后面的改进方法会讲)
//挖坑法
int PartSort1(int* a, int left, int right)
{
int begin = left, end = right;
int mid = GetMidIndex(a, left, right);//三数取中
Swap(&a[mid], &a[begin]);
int key = a[begin];
int pivot = begin;
while (begin < end)
{
while (begin < end && a[end] >= key)
{
end--;
}
a[pivot] = a[end];
pivot = end;
while (begin < end && a[begin] <= key)
{
begin++;
}
a[pivot] = a[begin];
pivot = begin;
}
a[begin] = key;
return pivot;
}
先设立一个keyi用来记录begin,用begin向后找比keyi大的值,找到了就停下,让end从后往前找比key小的值找到了就停下,然后交换两者的值,两者相遇的位置即是keyi的位置,然后交换keyi位置的值和相遇位置的值
//左右指针法
int PartSort2(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int begin = left, end = right;
int keyi = begin;
while (begin < end)
{
while (begin < end && a[end] >= a[keyi])
{
end--;
}
while (begin < end && a[begin] <= a[keyi])
{
begin++;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[keyi], &a[begin]);
return begin;
}
keyi记录begin,设prev = left, cur = left + 1,让cur往后面找比key小的值,找到了就给prev++,然后给交换prev和cur的值,一直重复直到cur>=n,prev的位置就应该放key,下面是一个例子的过程

//三数取中
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) >> 1;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
{
return right;
}
}
else//a[left] > a[mid]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
}
//挖坑法
int PartSort1(int* a, int left, int right)
{
int begin = left, end = right;
int mid = GetMidIndex(a, left, right);//三数取中
Swap(&a[mid], &a[begin]);
int key = a[begin];
int pivot = begin;
while (begin < end)
{
while (begin < end && a[end] >= key)
{
end--;
}
a[pivot] = a[end];
pivot = end;
while (begin < end && a[begin] <= key)
{
begin++;
}
a[pivot] = a[begin];
pivot = begin;
}
a[begin] = key;
return pivot;
}
//左右指针法
int PartSort2(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int begin = left, end = right;
int keyi = begin;
while (begin < end)
{
while (begin < end && a[end] >= a[keyi])
{
end--;
}
while (begin < end && a[begin] <= a[keyi])
{
begin++;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[keyi], &a[begin]);
return begin;
}
//前后指针法
int PartSort3(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int prev = left, cur = left + 1;
int keyi = left;
while (cur <= right)
{
while (a[cur] < a[keyi] && ++prev != cur)//自己交换自己浪费资源++prev != cur是减少不必要的浪费
{
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[prev], &a[keyi]);
return prev;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int keyindex = PartSort3(a, left, right);//可以调用3种方法的任意一种
if (keyindex - 1 - left > 10)
{
QuickSort(a, left, keyindex - 1);
}
else
{
InsertSort(a + left, keyindex - 1 - left + 1);
}
if (right - (keyindex + 1) > 10)
{
QuickSort(a, keyindex + 1, right);
}
else
{
InsertSort(a + keyindex + 1, right - (keyindex + 1) + 1);
}
}
看到这里,有人可能有两个疑问
1.三数取中是什么,为什么要用三数取中?
2.快排主代码中为什么有插入排序?
>1.三数取中是将头和尾还有中间值这3个数中取一个·不大不小的值来当key,可以有效地防止出现数据有序(升序和降序)的最坏的情况
>因为一旦有序,每一次都相当于遍历了,效率很低
>2.这快排主要是依靠递归实现的,一旦区间分的很小后,递归深度会很深,所以被分成较小的区间时,数据比较有序,这时用插入排序效率更好
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
两图解决,当们将一组数据分为两半,用begin1和begin2分别指向两边的数据的首元素,依次比较,将小的放到另外的一个数组,在任意一个数组被拿完后,将另外一个数组全部复制过去,如果这个数组两边是有序的,我们就完成了,可惜两边不是有序的,那我们就继续分,直到分成只有一个,一个数据他始终有序,然后将两个一个的数组合在一起归并,这两个数据就有序了,然后这两个和另外的合并,直到完全合并,就行了


void _MergeSort(int* a, int left, int right, int* temp)
{
if (left >= right)
{
return;
}
int mid = (left + right) >> 1;
_MergeSort(a, left, mid, temp);
_MergeSort(a, mid + 1, right, temp);
//这里用二叉树的知识理解,左右节点就是一层,当我我们解决掉一层后,我们就需要对这个代码进行归并
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int index = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
temp[index++] = a[begin1++];
}
else
{
temp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[index++] = a[begin2++];
}
for (int i = left; i <= right; i++)
{
a[i] = temp[i];
}
}
void MergeSort(int* a, int m)
{
int* temp = (int*)malloc(sizeof(int) * m);
_MergeSort(a, 0, m - 1, temp);
free(temp);
}
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问
题。- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定

好了,这篇文章总算爆肝完了(哭),希望对你有帮助,有错误请指正,谢谢
目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非
我需要用任何语言编写一个算法,根据3个因素对数组进行排序。我以度假村为例(如Hipmunk)。假设我想去度假。我想要最便宜的地方、最好的评论和最多的景点。但是,显然我找不到在所有3个中都排名第一的方法。Example(assumingthereare20importantattractions):ResortA:$150/night...98/100infavorablereviews...18of20attractionsResortB:$99/night...85/100infavorablereviews...12of20attractionsResortC:$120/night
我正在尝试按Rails相关模型中的字段进行排序。我研究的所有解决方案都没有解决如果相关模型被另一个参数过滤?元素模型classItem相关模型:classPriority我正在使用where子句检索项目:@items=Item.where('company_id=?andapproved=?',@company.id,true).all我需要按相关表格中的“位置”列进行排序。问题在于,在优先级模型中,一个项目可能会被多家公司列出。因此,这些职位取决于他们拥有的company_id。当我显示项目时,它是针对一个公司的,按公司内的职位排序。完成此任务的正确方法是什么?感谢您的帮助。PS-我
我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排
例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果
我有一个对象如下:[{:id=>2,:fname=>"Ron",:lname=>"XXXXX",:photo=>"XXX"},{:id=>3,:fname=>"Dain",:lname=>"XXXX",:photo=>"XXXXXXX"},{:id=>1,:fname=>"Bob",:lname=>"XXXXXX",:photo=>"XXXX"}]我想按fname排序,不区分大小写,所以它会导致编号:1,3,2我该如何排序?我正在尝试:@people.sort!{|x,y|y[:fname]x[:fname]}但这没有任何效果。 最佳答案
有人可以告诉我如何根据自定义字符串对嵌套数组进行排序吗?比如有没有办法排序:[['Red','Blue'],['Green','Orange'],['Purple','Yellow']]“橙色”、“黄色”,然后是“蓝色”?最终结果如下所示:[['Green','Orange'],['Purple','Yellow'],['Red','Blue']]它不是按字母顺序排序的。我很想知道我是否可以定义要排序的值以实现上述目标。 最佳答案 sort_by对于这种排序总是非常方便:a=[['Red','Blue'],['Green','Ora
我有以下现有的Dog对象数组,它们按age属性排序:classDogattr_accessor:agedefinitialize(age)@age=ageendenddogs=[Dog.new(1),Dog.new(4),Dog.new(10)]我现在想插入一条新的狗记录,并将它放在数组中的正确位置。假设我想插入这个对象:another_dog=Dog.new(8)我想把它插入到数组中,让它成为数组中的第三项。这是一个人为的示例,旨在演示我特别想如何将一个项目插入到现有的有序数组中。我意识到我可以创建一个全新的数组并重新对所有对象进行排序,但这不是我的目标。谢谢!
我有一个这样的哈希{55=>{:value=>61,:rating=>-147},89=>{:value=>72,:rating=>-175},78=>{:value=>64,:rating=>-155},84=>{:value=>90,:rating=>-220},95=>{:value=>39,:rating=>-92},46=>{:value=>97,:rating=>-237},52=>{:value=>73,:rating=>-177},64=>{:value=>69,:rating=>-167},86=>{:value=>68,:rating=>-165},53=>{:va
如何在ruby中先根据值然后根据键对散列进行排序?例如h={4=>5,2=>5,7=>1}将排序为[[7,1],[2,5],[4,5]]我可以根据值进行排序h.sort{|x,y|x[1]y[1]}但我不知道如何根据值进行排序,然后在值相同时键入 最佳答案 h.sort_by{|k,v|[v,k]}这使用了Array的事实混入Comparable并定义逐元素。注意上面等价于h.sort_by{|el|el.reverse}相当于h.sort_by(&:reverse)这可能会或可能不会更具可读性。如果你知道Hashes一般都是先