在本文章开始之前给大家介绍个网站,可以通过下面动画网址来理解 ,(国外的网站帮助学习数据结构很多知识,可以翻译下来,在搜索框搜索相应的排序算法进行动画演示,非常好用。)https://www.cs.usfca.edu/~galles/visualization/https://www.cs.usfca.edu/~galles/visualization/ 动画使用方法是,先点play,然后及时点击pause,自己通过Step Forward来查看。
作者QQ:2529702031 备注CSDN 可以问不会的问题,解答疑惑
目录
冒泡排序
快速排序
插入排序
选择排序
堆排序
归并排序
所有排序算法时间与空间复杂度汇总:
冒泡排序
冒泡排序的基本思想是:从后往前(或从前往后)两两比较相邻元素的值,(若A[j-1]>A[j]),则交换它们,直到序列比较完。我们称它为第一趟冒泡,结果是将最小的元素交换到待排序列的第一个位置。关键字最小的元素如气泡一般逐渐往上“漂浮”直至“水面”。下一趟冒泡时, 前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素放到了序列的最终位置……这样最多做n - 1趟冒泡就能把所有元素排好序。https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html给大家已经找好了,直接点击就可看排序算法动画演示。
(用的是王道的资料,王道还是比较权威的)
代码如下:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef int ElemType;
typedef struct{
ElemType *elem;//存储元素的起始地址
int TableLen;//元素个数
}SSTable;
void ST_Init(SSTable &ST,int len)
{
ST.TableLen=len;
ST.elem=(ElemType*)malloc(sizeof (ElemType)*ST.TableLen);//申请堆空间,当数组来使用
srand(time(NULL));
for (int i = 0; i < ST.TableLen; ++i) {
ST.elem[i]=rand()%100;//利用的是随机数生成 生成的是0-99之间
}
}
//打印数组的元素
void ST_print(SSTable ST)
{
for (int i = 0; i < ST.TableLen; ++i) {
printf("%3d",ST.elem[i]);
}
printf("\n");
}
//交换两个元素
void swap(ElemType &a,ElemType &b)
{
ElemType temp;
temp=a;
a=b;
b=temp;
}
//冒泡排序
void BubbleSort(ElemType A[],int n)
{
bool flag;
for (int i = 0; i < n-1; ++i) //这里可以推出i最多可以访问到8
{
flag= false;//元素是否发生交换的标志
for (int j = n-1; j > i;j--)//把最小值就放在最前面
{
if(A[j-1]>A[j])
{
swap(A[j-1],A[j]);
flag=true;
}
}
if(false==flag)//如果一趟比较没有发生任何变换,说明有序,也不再浪费时间,直接用这个哨兵提前结束排序
{
return;
}
}
}
int main() {
SSTable ST;
ST_Init(ST,10);//初始化
ST_print(ST);
BubbleSort(ST.elem,10);
ST_print(ST);
return 0;
}
时间复杂度O,空间复杂度O(1)
稳定性:冒泡排序是一种稳定的排序算法
适用性:适用于顺序存储和链式存储的线性表
快速排序
快速排序的核心是分治思想:假设我们的目 标依然是按从小到大的顺序排列,我们找到 数组中的一个分割值,把比分割值小的数都 放在数组的左边,把比分割值大的数都放在 数组的右边,这样分割值的位置就被确定。 数组一分为二,我们只需排前一半数组和后 一半数组,复杂度直接减半。采用这种思想, 不断地进行递归,最终分割得只剩一个元素 时,整个序列自然就是有序的。可通过下面链接进行更加详细理解(注意大致思想一样,但有区别,下面有我的草稿图,呜呜呜,字太丑了,配合教材吧,再配上我的代码来理解)
Comparison Sorting Visualizationhttps://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html
(重点理解图示)
代码如下:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
typedef int ElemType;
typedef struct
{
ElemType *elem;//存储元素的起始地址
int TableLen;//元素个数
}SSTable;
void ST_Init(SSTable &ST,int len)
{
ST.TableLen=len;
ST.elem=(ElemType*) malloc(sizeof (ElemType)*ST.TableLen);//申请堆空间,当数组来使用
int i;
srand(time(NULL));//随机数生成,每一次代码执行就会得到随机的10个元素
for (int i = 0; i < ST.TableLen; ++i) {
ST.elem[i]= rand()%100;//生成的是0-99之间
}
}
void ST_Print(SSTable ST)
{
for (int i = 0; i < ST.TableLen; ++i) {
printf("%3d",ST.elem[i]);
}
printf("\n");
}
int Partition(ElemType A[],int low,int high)
{
ElemType pivot=A[low];//首先使用左边变量存储分割值
while (low < high)
{
while (low < high && A[high]>=pivot)//从后往前遍历找到一个比分割值小的
high--;
A[low]=A[high];//把比分隔值小的那个元素,放到A[low]
while (low < high && A[low]<=pivot)//从前往后遍历,找到一个比分隔值大的
low++;
A[high]=A[low];//把比分割值大的那个元素,放到A[high],因为刚才high位置的元素已经放到low位置
}
A[low]=pivot;
return low;//返回分隔值所在的下标
}
//递归实现
void QuickSort(ElemType A[],int low,int high)
{
if(low < high)
{
int pivotpos=Partition(A,low,high);//分割点左边的元素都比分割点要小,右边的比分割点大
QuickSort(A,low,pivotpos-1);
QuickSort(A,pivotpos+1,high);
}
}
int main() {
SSTable ST;
ST_Init(ST,10);//初始化
// ElemType A[10]={64,94,95,79,69,84,18,22,12,78};//内存copy接口,当你copy整型数组,或者浮点型时,要用memcpy,不能用strcpy,初试考memcpy概率很低
// memcpy(ST.elem,A,sizeof (A));//这是为了降低调试难度,每次数组数据固定而设计的
ST_Print(ST);
QuickSort(ST.elem,0,9);//注意这个位置是n-1,也就是9,因为函数里取了high位置的值
ST_Print(ST);
return 0;
}
时间复杂度O、空间复杂度O
稳定性:快速排序是所有内部排序算法中平均性能最优的排序算法,但这是一种不稳定的排序算法
插入排序
插入排序分为 1、直接插入排序 2、折半插入排序 3、希尔排序 以上 3 种插入类型的排序,考研都是考选择题,考大题概率很低,因此我们仅讲解直接插 入排序的原理与代码实战,折半插入排序与希尔排序原理可以在后面的课程中进行学习。如果一个序列只有一个数,那么该序列自然是有序的。插入排序首先将第一个数视为有 序序列,然后把后面 9 个数视为要依次插入的序列。首先,我们通过外层循环控制要插入的 数,用 insertVal 保存要插入的值 87,我们比较 arr[0]是否大于 arr[1],即 3 是否大于 87,由 于不大于,因此不发生移动,这时有序序列是 3, 87。接着,将数值 2 插入有序序列,首先将 2 赋给 insertVal,这时判断 87 是否大于 2,因为 87 大于 2,所以将 87 向后移动,将 2 覆盖, 然后判断 3 是否大于 2,因为 3 大于 2,所以 3 移动到 87 所在的位置,内层循环结束,这时 将 2 赋给 arr[0]的位置,得到下表中第 2 次插入后的效果
继续循环会将数依次插入有序序列,最终使得整个数组有序。插入排序主要用在部分数有序 的场景,例如手机通讯录时时刻刻都是有序的,新增一个电话号码时,以插入排序的方法将 其插入原有的有序序列,这样就降低了复杂度。
接下来我们借助动画网站来理解一下 https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html
代码如下 :
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef int ElemType;
typedef struct {
ElemType *elem;
int TableLen;
}SSTable;
void ST_Init(SSTable &ST,ElemType len)
{
ST.TableLen=len;//申请10个元素空间
ST.elem=(ElemType*) malloc(sizeof (ElemType)*ST.TableLen);
srand(time(NULL));
for (int i = 0; i < ST.TableLen; ++i) {
ST.elem[i]=rand()%100;//随机10个数
}
}
void ST_Print(SSTable ST)
{
for (int i = 0; i < ST.TableLen; ++i) {
printf("%3d",ST.elem[i]);
}
printf("\n");
}
void InsertSort(ElemType *A,int len)
{
int i,j,insertVal;
for ( i = 1; i < len; ++i) //控制要插入的数
{
insertVal=A[i];//先保存要插入的数值
//内层控制比较,j要大于等于0,同时A[j]大于insertVal,A[j]位置元素往后覆盖
for(j=i-1;j>=0&&A[j]>insertVal;j--)
{
A[j+1]=A[j];
}
A[j+1]=insertVal;
}
}
int main() {
SSTable ST;
ST_Init(ST,10);//申请10个元素空间
ST_Print(ST);//排序前打印
InsertSort(ST.elem,10);
ST_Print(ST);//排序后打印
return 0;
}
时间复杂度O、空间复杂度O(1)
稳定性:因为每次插入元素时总是从后往前先比较再移动,所以不会出现相同的元素相对位置发生变化的情况,即直接插入排序是一个稳定的排序算法
适用性:直接插入排序适用于顺序存储和链式存储的线性表,采用链式存储时无需移动元素
选择排序
简单选择排序原理:假设排序表为 L[1…n],第 i 趟排序即从 L[i…n]中选择关键字最小 的元素与 L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过 n - 1 趟排序就可 使得整个排序表有序。 首先假定第零个元素是最小的,把下标 0 赋值给 min(min 记录最小的元素的下标),内层比 较时,从 1 号元素一直比较到 9 号元素,谁更小,就把它的下标赋给 min,一轮比较结束后,将 min 对应位置的元素与元素 i 交换,如下表所示。第一轮确认 2 最小,将 2 与数组开头的元素 3 交 换。第二轮我们最初认为 87 最小,经过一轮比较,发现 3 最小,这时将 87 与 3 交换。持续进行, 最终使数组有序。
接下来我们借助动画网站来理解一下 https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html
代码如下:
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
typedef int ElemType;
typedef struct {
ElemType *elem;
int TableLen;
}SSTable;
void Init_ST(SSTable &ST,int len)//申请空间,并进行随机数生成
{
ST.TableLen=len;
ST.elem=(ElemType*) malloc(sizeof (ElemType)*ST.TableLen);
srand(time(NULL));
for (int i = 0; i < ST.TableLen; ++i) {
ST.elem[i]=rand()%100;
}
}
void Print_ST(SSTable ST)
{
for (int i = 0; i < ST.TableLen; ++i) {
printf("%3d",ST.elem[i]);
}
printf("\n");
}
void swap(ElemType &a,ElemType &b)
{
ElemType temp;
temp=a;
a=b;
b=temp;
}
void SelectSort(ElemType *A,int n)
{
int i,j,min;//min是用来记录每一趟最小元素的下标
for(i=0;i<n-1;i++)
{
min=i;
for(j=i+1;j<n;j++)
{
if(A[j]<A[min])
min=j;
}
if(i!=min)
{
swap(A[i],A[min]);
}
}
}
int main() {
SSTable ST;
Init_ST(ST,10);
Print_ST(ST);
SelectSort(ST.elem,10);
Print_ST(ST);
return 0;
}
时间复杂度:O、空间复杂度:O(1)
稳定性:是一种不稳定的排序算法
适用性:简单选择排序适用于顺序存储和链式存储的线性表,以及关键字较少的情况
堆排序
堆(Heap)是计算机科学中的一种特殊的树状数据结构。 若满足以下特性,则可称为堆:“给定堆中任意结点P和C ,若P是C的父结点,则P的值小于等于(或大于等于)C的 值。”若父结点的值恒小于等于子结点的值,则该堆称为最 小堆(min heap);反之,若父结点的值恒大于等于子结 点的值,则该堆称为最大堆(max heap)。堆中最顶端的 那个结点称为根结点(root node),根结点本身没有父结 点(parent node)。平时在工作中,我们将最小堆称为小 根堆或小顶堆,把最大堆称为大根堆或大顶堆。
假设我们有3, 87, 2, 93, 78, 56, 61, 38, 12, 40共10个元素 ,我们将这10个元素建成一棵完全二叉树,这里采用层次 建树法,虽然只用一个数组存储元素,但是我们能将二叉树 中任意一个位置的元素对应到数组下标上,我们将二叉树 中每个元素对应到数组下标的这种数据结构称为堆,比如最 后一个父元素的下标是N/2-1,也就是a[4],对应的值为78 。为什么是N/2-1?因为这是层次建立一棵完全二叉树的特 性。可以这样记忆:如果父结点的下标是dad,那么父结点 对应的左子结点的下标值是2*dad+1。接着,依次将每棵子 树都调整为父结点最大,最终将整棵树变为一个大根堆。
(这是建立大根堆的示意图,并不是代表堆排序完成了,第一步大根堆有利于后面抽出最大值)
通过下面动画网址来理解
https://www.cs.usfca.edu/~galles/visualization/HeapSort.html
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
typedef int ElemType;
typedef struct {
ElemType *elem;
int TableLen;
}SSTable;
void Init_ST(SSTable &ST,int len)//申请空间,并进行随机数生成
{
ST.TableLen=len;
ST.elem=(ElemType*) malloc(sizeof (ElemType)*ST.TableLen);
srand(time(NULL));
for (int i = 0; i < ST.TableLen; ++i) {
ST.elem[i]=rand()%100;
}
}
void Print_ST(SSTable ST)
{
for (int i = 0; i < ST.TableLen; ++i) {
printf("%3d",ST.elem[i]);
}
printf("\n");
}
void swap(ElemType &a,ElemType &b)
{
ElemType temp;
temp=a;
a=b;
b=temp;
}
void AdjustDown(ElemType A[],int k,int len)
{
int dad=k;
int son=2*dad+1;//左孩子下标
while (son<=len)
{
if(son+1<=len && A[son]<A[son+1])//看下有没有右孩子,比较左右孩子选大的
{
son++;
}
if(A[son]>A[dad])//比较孩子和父亲,如果孩子大于父亲,那么进行交换
{
swap(A[son],A[dad]);
dad=son;//孩子重新作为父亲,判断下一颗子树是否符合大根堆
son=2*dad+1;
} else
{
break;
}
}
}
void HeapSort(ElemType A[],int len)
{
int i;
//建立大根堆
for(i=len/2;i>=0;i--)
{
AdjustDown(A,i,len);
}
swap(A[0],A[len]);//交换顶部和数组最后一个元素
//下面的策略就是,不但调整剩余元素为大根堆,因为根部最大,所以再次与A[i]交换(相当于放到数组后面),循环往复
for(i=len-1;i>0;i--)
{
AdjustDown(A,0,i);//剩下元素调整为大根堆
swap(A[0],A[i]);
}
}
int main() {
SSTable ST;
Init_ST(ST,10);//初始化
ElemType A[10]={3,87,2,93,78,56,61,38,12,40};
memcpy(ST.elem,A, sizeof(A));//此处使用固定的数组进行分析,不用随机的了
Print_ST(ST);
HeapSort(ST.elem,9);//所有元素参与排序
Print_ST(ST);
return 0;
}
时间复杂度 O、空间复杂度O(1)
稳定性:不稳定
适用性:堆排序仅适用于顺序存储的线性表
归并排序
接下来我们借助动画网站来理解一下:
https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html
归并排序的代码是采用递归思想实现的,考研掌握递归实现即可。首先,最小下标值和最 大下标值相加并除以 2,得到中间下标值 mid,用 MergeSort 对 low 到 mid 排序,然后用 MergeSort 对 mid+1 到 high 排序。当数组的前半部分和后半部分都排好序后,使用 Merge 函 数。Merge 函数的作用是合并两个有序数组。为了提高合并有序数组的效率,在 Merge 函数内 定义了 B[N]。首先,我们通过循环把数组 A 中从 low 到 high 的元素全部复制到 B 中,这时游 标 i(遍历的变量称为游标)从 low 开始,游标 j 从 mid+1 开始,谁小就将谁先放入数组 A,对 其游标加 1,并在每轮循环时对数组 A 的计数游标 k 加 1。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#define N 7
typedef int ElemType;
void Merge(ElemType A[],int low,int mid,int high)
{
static ElemType B[N];//加static的目的是无论多次递归,都只有一个B[N]
int i,j,k;
for(k=low;k<=high;k++)//复制元素到B中
B[k]=A[k];
//合并数组
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)
{
if(B[i]<=B[j])
A[k]=B[i++];
else
A[k]=B[j++];
}
while (i<=mid)//如果有剩余元素,接着放入即可
A[k++]=B[i++];//前一半有剩余的放入
while (j<=high)//如果有剩余元素,接着放入即可
A[k++]=B[j++];//后一半有剩余的放入
}
void MergeSort(ElemType A[],int low,int high)//递归分割
{
if(low<high)
{
int mid=(low+high)/2;
MergeSort(A,low,mid);//排序好前一半
MergeSort(A,mid+1,high);//排序好后一半
Merge(A,low,mid,high);//将连个排序好的数组合并
}
}
void print(int *a)
{
for (int i = 0; i < N; ++i) {
printf("%3d",a[i]);
}
printf("\n");
}
int main() {
int A[7]={49,38,65,97,76,13,27};
MergeSort(A,0,6);
print(A);
return 0;
}
时间复杂度 O、空间复杂度O(n)
稳定性:稳定的算法
适用性:归并排序适用于顺序存储和链式存储的线性表