当前位置: 首页 > news >正文

一篇文章弄懂数据结构中的各种排序_插入排序_冒泡排序_快速排序_堆排序_归并排序_基数排序

文章目录

  • 一篇文章弄懂数据结构中的各种排序
  • 1.排序的概念
  • 2. 插入排序
    • 2.1 直接插入排序
    • 2.2 折半插入排序
    • 2.3 希尔排序
  • 3.冒泡排序
    • 3.1 算法原理
    • 3.2 性能分析
  • 4.快速排序
    • 4.1 算法原理
    • 4.2 性能分析
  • 5. 选择排序
    • 5.1 简单选择排序
    • 5.2 堆排序
      • 5.1 算法流程
      • 5.2 算法效率分析
      • 5.3 堆排序的插入和删除操作(了解)
        • 5.3.1 堆排序的插入操作
        • 5.3.2 堆排序的删除操作
      • 5.4 代码实现堆排序
  • 6. 归并排序
    • 6.1 算法思想和算法流程
    • 6.2 算法效率分析
  • 7. 基数排序
    • 7.1 算法思想和算法流程
    • 7.2 算法效率分析
  • 8.例题
    • 堆排序
    • 快速排序
    • 希尔排序

一篇文章弄懂数据结构中的各种排序

1.排序的概念

排序:将各元素按关键字递增或递减顺序重新排列
评价指标:
稳定性:关键字相同的元素经过排序后相对顺序是否会发生改变。
注意:稳定性跟算法的优劣无关
时间复杂度,空间复杂度

分类:
内部排序:数据都在内存中
外部排序:数据太多,无法全部存入内存。

2. 插入排序

2.1 直接插入排序

算法思想:
从头开始,每次将一个待排序的记录,按照关键字大小加入到前面已经排好的子序列中。

流程是:
一个待排序的记录,从已经排好的子序列的尾部开始比较,找到小于或等于它的值,将其插入进去,得到新的子序列。

空间复杂度: O(1)
时间复杂度
最好时间复杂度(全部有序):O(n)
最坏时间复杂度(全部逆序):O(n2)
平均时间复杂度:O(n2)

算法稳定性:稳定

2.2 折半插入排序

前面说的插入排序,是通过顺序的方式查找需要插入的位置,由于子序列已经是顺序的,可以优化成通过折半(二分)查找插入位置。

比起“直接插入排序”,比较关键字的次数减少了,但是移动元素的次数没变,整体来看时间复杂度依然是o(n2)

对链表进行插入排序
虽然对链表进入插入排序移动的次数变少了,但是关键字对比的次数依然是O(n2)树数量级,时间复杂度依然是O(n2)

2.3 希尔排序

引入希尔排序:

我们不难发现插入排序最适合用于已经有序的序列,或者部分有序有序的序列。
希尔排序就是让部分有序-直接插入-部分有序–直接插入 的过程

算法思想:
先将待排序表分割成若干特殊子表,对每个子表进行直接插入的过程。

如何划分?
每一次划分设置一个增量d,如d=4,就是把相隔距离=4的元素,化为一组,如 1,5,9…,为一组2,6,10,为一组,以此类推,希尔建议每次设置增量后,下一次取增量取上一次增量的一半,但是在实际的解题中,要看具体要求,具体情况具体分析。

在这里插入图片描述

性能分析:
空间复杂度:o(1)
时间复杂度:无法计算,但优于直接插入排序
稳定性:不稳定
适用性:仅适用于顺序表,不适用于链表

3.冒泡排序

3.1 算法原理

算法原理:
每次从后往前后或从前往后,两两一组,逐次比较,如果后一个更小(从后往前),则交换它们,注意,整个过程中,元素只交换,容纳两个元素的窗口移动。

每一趟比较都可以使一个元素移动到最终位置,已经确定最终位置的元素在之后的比较中无需再对比。

如果某一趟排序过程中未发生交换,则算法可提前结束。

代码思路:
两层循环,最外层保证,每一次循环让一个元素移动到最后一个位置,故n-1次就能保证n-1个元素都在最后的位置上,故n个元素都在最后的位置上。

for(int i=0;i<n-1;i++)

内层每次和最后一个元素j比较,若最后一个元素小,则和j-1交换,逐步往前遍历,小就交换,不小就遍历,直到到达最终位置。

for(int j=n-1;j>i;j--)
{if(a[j]<a[j-1]){swap(a[j],a[j-1]); //伪代码}
}

优化是定一个布尔变量flag,若一次循环并未改变flag的值,则直接退出循环

给出一个完整的c语言冒泡排序的算法:

 for(int i=0;i<numsSize-1;i++) {for(int j=0;j<numsSize-1-i;j++){if(nums[j]>nums[j+1]){int temp=0;temp=nums[j+1];nums[j+1]=nums[j];nums[j]=temp;}}}

3.2 性能分析

空间复杂度:o(1)

时间复杂度:
最好:o(n)
最差:o(n2)
平均:o(n2)

稳定性:稳定

适用性:顺序表,链表都可以。

4.快速排序

4.1 算法原理

首先将最左边待排元素移出数组,最左边设置low指针,最右边设置high指针,两边交替使用,从high指针开始移动,判断当前high指针所指向元素,是否大于待排元素,如果小于,则继续向左移动,继续比较,如果小于,则将该元素,移入到low指针指向的空间中,然后开始移动low指针,进行low指针的判断,此时low与high指针的职能交换。然后彼此互换。

快速排序代码如下:

int partition(int a[],int low,int high)
{int pivot=a[low];while (low<high) {while(a[high]>=pivot&&low<high){high--;}  //出循环之后,要移动high指向的元素赋值给low中空下来的位置a[low]=a[high];while(a[low]<=pivot&&low<high){low++;}a[high]=a[low];}a[low]=pivot;return low;  //此时low=high
}void quicksort(int a[],int low,int high)
{if(low<high){int pivot=partition(a, low, high);quicksort(a, low, pivot-1);quicksort(a, pivot+1, high);}
}

4.2 性能分析

空间复杂度:

  • 最好:O(n)
  • 最坏:O(logn)

空间复杂度:递归工作栈,确定空间复杂度

时间复杂度:
最好:O(n2)
最坏:O(n logn)
平均:O(n long)

稳定性:不稳定

5. 选择排序

5.1 简单选择排序

每一趟在待排序元素选取关键字最小的元素加入有序子序列。

算法性能分析
空间复杂度:o(1)

时间复杂度:o(n2)
总共需要对比关键字:n(n-1)/2

稳定性:不稳定

适用性:既可以用于顺序表,也可用于链表

5.2 堆排序

什么是堆?
大根堆,意味着根最大,根结点大于它的左右结点,同理,每一个结点都满足这个性质,我们就把这个树形结构叫做大根堆。
小根堆反之。

5.1 算法流程

1️⃣建立大根堆

把所有非终端结点都检查一遍,是否满足大根堆的要求,如果不满足,则从后往前进行调整。
检查当前结点是否满足根>=左右,若不满足,则当前结点与更大的一个孩子互换
若元素互换破坏了下一级的堆,则采用相同的方法继续往下调整(小元素不断“下坠”)

2️⃣取根结点加入有序子序列(结果),并将待排序序列中的最后一个元素交换(肯定是当前的一个叶子结点)。然后回到1️⃣

5.2 算法效率分析

一个结点,每下坠一层,最多只需比较关键字2次。
若树高为h,某结点在第i层,则将这个结点向下调整最多只需要下坠h-i层,关键字对比次数不超过2(h-i)

建堆的过程,关键字对比次数不超过4n,建堆时间复杂度=o(n).
2️⃣的过程,时间复杂度是O(nlog2n)
总的时间复杂度=O(nlogn)

稳定性:不稳定
基于大根堆的堆排序得到递增序列,基于小根堆得到递减序列。

5.3 堆排序的插入和删除操作(了解)

5.3.1 堆排序的插入操作

新元素放到表尾(堆底)
根据大/小根堆堆要求,新元素不断上升,直到无法继续上升为止。

每次上升调整只需对比关键字1次

5.3.2 堆排序的删除操作

被删除元素用表尾(堆底)元素代替
根据大/小根堆堆要求,替代元素不断下坠,直到无法继续下坠为止。

每次下坠调整可能需要对比关键字2次,也可能只需对比1次。

5.4 代码实现堆排序

void swap(int *a, int *b)
{int temp = *a;*a=*b;*b=temp;
}void heapify(int a[],int length,int i)
{//找到当前结点和它的左右孩子孩子中最大的一个,将最大的结点与当前结点交换,若当前结点就是最大的,则不交换int largest=i;int lson=i*2+1;int rson=i*2+2;if(lson<length&&a[largest]<a[lson]) largest=lson;         //左右孩子不能超出这个树的范围<lif(rson<length&&a[largest]<a[rson]) largest=rson;if(largest!=i){swap(&a[i], &a[largest]);heapify(a,length,largest);}
}void heap_sort(int a[],int length)
{//堆排序的大体过程,首先建立堆,然后将当前的根节点和最大下标的叶子结点,交换,然后将新的叶子结点删除,并且重新调整堆// 第一步,建立堆,遍历全部的非叶子结点,从下往上的遍历,调整,确保一遍完成建立堆堆操作//因为是从0开始存储,对应的父结点下标是length/2-1,假如从1开始存储,就是length/2for(int i=length/2-1;i>=0;i--) //从最后一个非叶子结点开始,遍历全部的非叶子结点,此时的就能找到最大的值放在根上{heapify(a, length, i);}//第二步开始,多次调整堆,每一次都会确定一个位置,即将当前的根与最后一个叶子结点交换,再重新进行建堆操作for(int i=length-1;i>0;i--){swap(&a[0], &a[i]); //a[0]是最大的那个,a[i]是当前要确定的位置heapify(a, i, 0); //当前的长度就是i,当前要从根开始调整;}}

在这里插入图片描述

6. 归并排序

6.1 算法思想和算法流程

算法思想:
把两个或多个已经有序的序列合并成一个序列,合并的过程,就是两个有序序列依次对比把更小(或更大的拿出来合并)。

算法流程:
以二路归并为例
在这里插入图片描述

从每一个元素都是一个队列开始都是一个独立的有序序列,相邻两个元素合并成一个,在排好序,以此类推,不断将相邻的两个有序序列合并并排好序,直到就剩一个有序序列为止。

6.2 算法效率分析

算法的空间复杂度:O(n)
算法的时间复杂度:O(nlogn)
稳定性:稳定的

7. 基数排序

7.1 算法思想和算法流程

算法思想:
不比较关键字,而是按位数比较,比如给多个三位数排序,就先比较它们的个位数,再比较它们的十位数,最后比较它们的百位数。

算法流程:
以多个三位数的比较为例
在这里插入图片描述

首先设置十个队列,因为0-9是10个数

从左往右,根据个位数的,将相应的个位数加入队列中,然后再从左往右,把各个队列拿出来拼到一起
在这里插入图片描述

上面个位数就排好列,再排十位数,如法炮制,最后排百位数

7.2 算法效率分析

n是元素个数,d是趟数,比如三位数就是三趟,r是辅助队列的个数,比如0-9就是10个

空间复杂度:o(r)
时间复杂度:o(d(n+r))
稳定性:稳定
擅长处理什么样的问题?
n大,但是r和d小的问题

8.例题

堆排序

在这里插入图片描述
在这里插入图片描述

快速排序

真题1:
在这里插入图片描述
在这里插入图片描述
真题2:
在这里插入图片描述

在这里插入图片描述

希尔排序

希尔排序中,值得注意的点是,一个数据可能被多次的比较,比如d=5,第一个和第六个元素比较,等到了第6个元素,第6个元素再与第11个元素比较(如果存在第11个元素的话)


http://www.mrgr.cn/news/38604.html

相关文章:

  • 栏目二:Echart绘制动态折线图+柱状图
  • Linux —— Socket编程(二)
  • BUUCTF蜘蛛侠呀
  • Neo4J介绍
  • Go基础学习08-并发安全型类型-通道(chan)深入研究
  • 想做个WPS的自动化代码,参考如下:
  • 制造业智能化建设的指标详解
  • linux安装jdk
  • 【CTF Web】Pikachu 反射型xss(get) Writeup(反射型XSS+GET请求)
  • Unity实战案例全解析:RTS游戏的框选和阵型功能(1) 基础要素
  • AVLTree【c++实现】
  • 2409vim,vim写文件有问题
  • Java语法-类和对象之抽象类和接口
  • 国产动漫论坛系统小程序的设计
  • linux网络编程实战
  • 什么是SQL注入?
  • MySQL-数据库约束
  • JSON的C实现(上)
  • LeetCode讲解篇之33. 搜索旋转排序数组
  • 哈希知识点总结:哈希、哈希表、位图、布隆过滤器