[数据结构]排序——八大排序
Posted Mr、Jerry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[数据结构]排序——八大排序相关的知识,希望对你有一定的参考价值。
[数据结构]排序——八大排序
前言
我们在学习数据结构中,我们经常接触到排序这个名词,但我们在学习时,直接面对代码时可能一脸茫然,为什么这个接口要用这些参数,为什么要前后交换…………这些就是我们不知道作者在实现这些接口时他们的思想是怎么样的,这篇博客我将通过对排序的概念、代码的分析、代码的分析、来逐步讲解每一种排序的思考方式与实现过程,真正帮助大家理解排序的实现,让我们面对代码时不再茫然
排序
①排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序: 数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
②排序运用
在生活中我们经常使用到排序,购物时的比价,点外卖时的好评比较,销量高低等
1.手机购买时的排序使用
2.外卖中的排序使用
插入排序
①概念
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
②分析
我们在看概念的时候可能还是不理解是什么意思,这里我们举例生活中的例子:
当我们在打扑克的时候,我们会不断的抓牌,而这个抓牌的过程就是我们的插入排序,首先我们会抓到我们的第一张牌,我们默认我们现在的牌有序;当我们抓到新的牌时,我们就有对之前已经有序的牌中插入这一张新牌,使我们的牌组依然有序,以此往复,直至我们摸牌结束,结束的同时,我们手里的牌也依旧按照从小到大的顺序排序。
③代码分析
通过上面的分析我们知道了代码要实现的结果,首先我们要处理一个数据,这个数据有两个部分组成,一个部分是已经排好序的数组,另一部分是一个要插入我们当前有序排列的元素。而这个过程我们分析可知,其实就是从数组中的第一个元素视为处理数据开始,我们通过我们的插入排序代码实现将其有序,然后再将前两个元素视为处理数据,依次递增,前三个元素,前四个元素…………直至所有元素都是为处理数据。
然后我们逐一对这些处理数据执行我们的排序插入代码,实现我们在插入元素之后,有序排列任然有序,那么我们应该如何实现我们的有序排序呢?
我们先通过下面的图解进行过程分析:
这个时候我们知道了这个过程是如何实现的,那么代码我们应该怎么编写呢?我们这里可以先从一趟开始写起,然后我们在去考虑循环的过程
下面我们先写我们一趟插入排序的代码:
void InsertSort(int*a, int n)
{
int end = ? ;//因为这个时候我们不知道我们的end坐标的位置,所以我们先用?代替,在循环中我们再修改
int tmp = a[end + 1];//这里我们将要插入的元素先进行保存,因为我们后续的代码会将这个位置的元素进行覆盖
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
//a[end+1] = tmp;
break;
}
}
a[end + 1] = tmp;//这里是考虑了多种情况:①当我们要插入的数一直比我们的有序数列的数字都要小的时候,我们将其放到end+1的位置;②正常情况,我们也要将其放到end +1的位置
}
现在我们只要再将end的大小考虑清楚即可实现我们的插入排序代码,现在我们来对end的大小取值进行分析
通过上面的分析,我们就可以得出完整的代码
void InsertSort(int*a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
④代码检验
Sort.h
#include<stdio.h>
#include<stdlib.h>
void InsertSort(int* a, int n);
void PrintArray(int*a, int n);
void InsertTest();
Sort.c
#include"Sort.h"
void InsertSort(int*a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
void PrintArray(int*a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ",a[i]);
}
printf("\\n");
}
void InsertTest()
{
int a[] = { 54, 38, 96, 23, 15, 72, 60, 45, 83 };
int size = sizeof(a) / sizeof(int);
PrintArray(a, size);
InsertSort(a, size);
PrintArray(a, size);
}
test.c
#include"Sort.h"
int main()
{
InsertTest();
return 0;
}
当我们执行代码后,编译执行的结果为
这里说明我们的分析思路与代码编写是正确的
⑤插入排序的利弊
现在我们对插入排序的时间复杂度进行分析,我们可以得出插入排序的时间复杂度的大小,取决于我们的处理数据,当我们的处理数据为逆序时,我们的时间复杂度就是O(N^2);而当我们的处理数据为顺序或者接近顺序时,我们的时间复杂度就是O(N)
注意,我们得出插入排序的利弊是:时间复杂度的大小取决于处理数据的排序程度,那么如果我们处理的数据在开始时即比较接近有序排列,这个时候我们再进行插入排序时我们的时间复杂度就可以大幅下降,那么我们可不可以实现在进行插入排序前先进行一次排序,使处理数据接近有序排列呢?
希尔排序
①概念
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
②分析
我们在第一次读上面概念时吗,可能不理解在讲述什么,我们通过下面的图解先进行简单分析,来讲解希尔排序的操作步骤
通过上面的图解分析,我们知道当我们对处理数据进行一次希尔排序时,我们的处理数据从一开始的无序变为较为有序,这个时候我们想,如果我们再一次进行希尔排序,会不会让处理数据较上一次更加有序呢? 但这个时候我们发现, 如果我们的gap的数值不发生改变的话,那么我们再一次进行希尔排序后,数据不会发生改变,所以我们需要改变我们的gap的数值大小, 那么这个时候我们和我们的插入排序联想,我们可以思考出,如果我们的gap值较大,那么我们数据移动的单位距离也就越大,那么我们的处理数据的有序性就越低,相反,如果想我们的插入排序,每一次的元素移动距离为一个单位距离,这个时候我们得出的就是有序排列
③代码分析
通过上面的分析,我们得知我们希尔排序的方式为多次进行移动单位距离为gap的插入排序,并且我们的gap在每执行一次希尔排序之后,我们的gap都会减小,多次之后我们的处理数据就从无序变为有序排列
这个时候我们知道了这个过程是如何实现的,那么代码我们应该怎么编写呢?我们这里可以先从一趟开始写起,然后我们在去考虑循环过程中使我们的gap逐次减小的问题
void ShellSort(int* a, int n)
{
int gap = 3;//这里我们假设我们的gap为3
for (int i = 0; i < n - gap; i++)
{
int end = i ;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
对代码语句分析
现在我们得出了一趟执行的代码,其实就是我们的插入排序,只不过我们每一次移动的距离不再是单位距离,而是大小为gap的距离,那么这个时候我们就应该考虑,我们的gap的大小应该为多少?
这里关于希尔排序中gap的大小,官方给出的建议是每一次将gap的数据 / 3,因为这样处理数据时效率更高 而我们知道如果 我们在执行希尔排序时如果不将我们的希尔排序中gap减为1的话,也就是最终不执行插入排序的话,我们的处理数据永远不可能变为有序排列,永远只是无限接近有序排列,但不是有序排列
在进过这些分析之后我们可以得出我们的代码
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;//这里可以保证我们最后一次gap的数值为1,使我们最后一次执行插入排序
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
④代码检验
sort.h
#include<stdio.h>
#include<stdlib.h>
void PrintArray(int* a, int n);
void ShellSort(int* a, int n);
void ShellTest();
sort.c
#include"Sort.h"
void PrintArray(int*a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ",a[i]);
}
printf("\\n");
}
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
void ShellTest()
{
int a[] = { 54, 38, 96, 23, 15, 72, 60, 45, 83 };
int size = sizeof(a) / sizeof(int);
PrintArray(a, size);
ShellSort(a, size);
PrintArray(a, size);
}
test.c
#include"Sort.h"
int main()
{
ShellTest();
return 0;
}
当我们执行代码后,编译执行的结果为
这里说明我们的分析思路与代码编写是正确的
查找排序(选择排序)
①概念
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
②分析
查找排序和前两个排序比较起来简单许多,这里我们对上面讲述的过程进行一个简单的讲述,这里我们先对上面讲述的概念进行一个分析:
上面概念中讲解的是每一次挑选出当前处理数据中的最大值或最小值,然后我们通过交换,遍历处理数据,最终实现排序排列,那么我们可以对这一过程进行一下升级,我们每一次找到当前处理数据中的最大值和最小值,然后我们将其分别与最后一个元素和第一个元素交换,然后缩小我们的查找范围,再重复上述的步骤,最终实现我们的有序排列,这样我们的效率可以更高一些
③代码分析
现在我们来对我们代码的实现进行分析
按照我们上述的内容,我们需要创建两个标志,分别记录我们处理数据的头元素和尾元素,这里我们用begin和end标记;然后我们每对数组遍历一次之后,我们就将我们在这一趟中寻找到的最大值和尾元素交换,最小值和头元素交换,之后我们要减小我们下一次的查找范围,那么我们的就要执行:begin++;end–;然后重复我们上述的步骤,最终实现将处理数据变为有序排列
void Swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin <= end)
{
int mini = begin;
int maxi = begin;
for (int i = begin; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;//记录我们最小值的下标
}
if (a[i] > a[maxi])
{
maxi = i;//记录我们最大值的下标
}
}
Swap(&a[mini], &a[begin]);
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
④代码检验
sort.h
#include<stdio.h>
#include<stdlib.h>
void PrintArray(int* a, int n);
void SelectSort(int* a, int n);
void SelectTest();
sort.c
#include"Sort.h"
void PrintArray(int*a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ",a[i]);
}
printf("\\n");
}
void Swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin <= end)
{
int mini = begin;
int maxi = begin;
for (int i = begin; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[mini], &a[begin]);
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
void SelectTest()
{
int a[] = { 54, 38, 96, 23, 15, 72, 60, 45, 83 };
int size = sizeof(a) / sizeof(int);
PrintArray(a, size);
SelectSort(a, size);
PrintArray(a, size);
}
test.c
#include"Sort.h"
int main()
{
SelectTest();
return 0;
}
当我们执行代码后,编译执行的结果为
现在的执行结果显示我们的分析思路与代码编写是正确的;
可真的正确吗?
现在我们换一组处理数据进行检验
int a[] = { 154, 38, 96, 23, 15, 72, 60, 45, 83 };
当我们执行代码后,编译执行的结果为
数据结构初阶第九篇——八大经典排序算法总结(图解+动图演示+代码实现+八大排序比较)