• 数据结构之八大排序及代码



    在这里插入图片描述
    因为排序太多了,为了快速回忆,这里挑战一句话说清原理,更详细的可以看后面的内容

    • 插入排序
      • 直接插入排序:将一个数组中的数据看作两个数组,有序和无序数组,将无序数组的第一个与有序数组的最后一个依次对比,并且插入合适的位置
      • 希尔排序:直接插入排序的升级版,按分组将一个无序数组变得大致有序(内部使用直接插入法),再进行直接插入排序
    • 交换排序
      • 冒泡排序:相邻两个数字比较交换,有俩for循环,每排序一趟能确定无序数组中的最大值
      • 快速排序:找到一个基准值,让基准值左边的数都比基准值小,右边的数都比基准值大,最后确定基准值的位置,每一次排序都能让基准值归位。详解:https://blog.csdn.net/weixin_43586713/article/details/119820797
    • 选择排序法;
      • 简单选择排序:每次都找最小的放前边
        • 选择排序不稳定,为啥?
        • 假设有一个待排序的序列: 2 3 2 1 4
        • 我们知道第一趟排序后就会选择第1个元素2和元素1交换,那么原来序列中两个2的相对顺序就被破坏了,所以选择排序是不稳定的排序算法。
      • 堆排序:完全二叉树结构,构建大顶堆/小顶堆,将根节点与最深那层的叶子节点交换,去除这个叶子节点,继续构建顶堆
        • 首先将N个数据元素存入一个一维数组,并把这个数组视作一棵完全二叉树
        • 从二叉树的最后一个非叶结点开始用从上向下过滤的方法调整以该非叶结点为根节点的二叉树为最大堆。
        • 对前面的结点依次执行2的操作直到根结点执行完成为止。此时这棵二叉树就调整为了一个最大堆。(筛选法)
    • 归并排序:分而治之,先把大数组慢慢分成两个一组,排完之后再合并排序,稳定高效
    • 基数排序:按个十百等排

    老分不清直接插入和简单选择,记住一个口诀:直接插入不直接,简单选择真简单

    快速排序

    • 看了代码才理解这个指针的事情,我一直以为指针是一个从左一个从右的遍历,但是居然不是,用快慢指针就行了,慢指针指向比基准值大的数,快指针寻找比基准值小的数
    • partition函数每次返回的是回到正确位置的基准值,数组已经在调用函数的过程中排过一遍了
    def partition(arr,low,high): 
        i = ( low-1 )         # 最小元素索引
        pivot = arr[high]     
      
        for j in range(low , high): 
      
            # 当前元素小于或等于 pivot 
            if   arr[j] <= pivot: 
              
                i = i+1 
                arr[i],arr[j] = arr[j],arr[i] 
      
        arr[i+1],arr[high] = arr[high],arr[i+1] 
        return ( i+1 ) 
      
     
    # arr[] --> 排序数组
    # low  --> 起始索引
    # high  --> 结束索引
      
    # 快速排序函数
    def quickSort(arr,low,high): 
        if low < high: 
      
            pi = partition(arr,low,high) 
      
            quickSort(arr, low, pi-1) 
            quickSort(arr, pi+1, high) 
      
    arr = [10, 7, 8, 9, 1, 5] 
    n = len(arr) 
    quickSort(arr,0,n-1) 
    print ("排序后的数组:") 
    for i in range(n): 
        print ("%d" %arr[i]),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    冒泡排序代码

    public static void sort(int[] a) {
         //只能用于判断趟数,省趟数,不省次数
         boolean flag = true;
         for (int i = 0; i < a.length - 1; i++) {
             //排序n - 1for (int j = 0; j < a.length - 1 - i; j++) {
                 if (a[j] > a[j+1]) {
                     swag(a,j,j+1);
                     flag = false;
                 }
             }
             if (flag) {
                 break;
             } else {
                 //不重置只要交换过就没用了
                 flag = true;
             }
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 每一次循环i,都能确定一个最大值
    • 注意这里的一个停止条件:在某个i的循环里,如果a[j]和a[j+1]始终没有交换,那么就break了,这里说明了序列已经是升序了
    • 这个停止条件很重要,在数组基本有序时,解释了为什么快速排序是最慢的而不是冒泡

    复杂度

    排序稳定性

    • 所谓稳定性是指待排序的序列中有两元素相等,排序之后它们的先后顺序不变.假如为A1,A2.它们的索引分别为1,2.则排序之后A1,A2的索引仍然是1和2.
    • 排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
    • 一般来说,冒泡算法是稳定的,但是如果将交换的条件改成 a [ j ] ≥ a [ j + 1 ] a[j]\ge a[j+1] a[j]a[j+1],那冒泡又不稳定了
    • 稳定/不稳定的常见排序算法
    • 在这里插入图片描述

    堆排序的时间、空间复杂度

    • 空间复杂度为 O ( 1 ) O(1) O(1),,因为没有用到其他的空间,只是原地交换
    • 堆排序的最好最坏复杂度都是 O ( n log ⁡ 2 n ) O(n\log_2 n) O(nlog2n),实际复杂度 = O ( n + n log ⁡ 2 n ) =O(n+n\log_2 n) =O(n+nlog2n)(建堆+重建堆)

    代码

    搬运自:https://blog.csdn.net/weixin_42264992/article/details/124968645
    大顶堆:

    def topHeap(list,size):
        #i节点的子节点为2i+1,2i+2
        for i in range(size-1,-1,-1):
            if 2*i+1<size: #左孩子存在
                if list[2*i+1]>list[i]:
                    list[2*i+1],list[i]=list[i], list[2*i+1]
            if 2*i+2<size: #右孩子存在
                if list[2*i+2]>list[i]:
                    list[2*i+2],list[i]=list[i], list[2*i+2]
    list=[11,2,3,4,12,18,16]
    for i in range(len(list),0,-1):
        topHeap(list,i)
        list[0],list[i-1]=list[i-1],list[0] #每轮交换栈顶元素至相应位置
    print(list)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    小顶堆

    def topHeap(list,size): #i节点的子节点为2i+1,2i+2
        for i in range(size-1,-1,-1):
            if 2*i+1<size: #左孩子存在
                if list[2*i+1]<list[i]:
                    list[2*i+1],list[i]=list[i], list[2*i+1]
            if 2*i+2<size: #右孩子存在
                if list[2*i+2]<list[i]:
                    list[2*i+2],list[i]=list[i], list[2*i+2]
    list=[11,2,3,4,12,18,16]
    for i in range(len(list),0,-1):
        topHeap(list,i)
        list[0],list[i-1]=list[i-1],list[0] #每轮交换栈顶元素至相应位置
    print(list)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    数组元素基本有序的情况下

    • 选择直接插入排序法最快,假设一个已经是完全有序的数组,只要排序一次即可
    • 这个时候采用快速排序法最慢,每个都要比一下了
    • 补充外排序:多路归并排序

    参考链接:https://blog.csdn.net/a574780196/article/details/122646309

    • 以上排序都是内排序,当内存足够存放时当然没有问题,全输进去再排序就好,但是假如文件大小超过了内存容量了咋办?假如,文件10GB,内存只有4GB这种情况
    • 机智的人们想到了多路归并的方法
    • 思路:将10GB的文件划分成10个1GB的文件,对这10个文件单独排序后,写入磁盘
    • 然后再对这10个已初步排序的文件做处理
      • 1、将10个文件表示成矩阵形式:每一行是一个文件,后续需要用到x,y值
      • 在这里插入图片描述

    基本思想

    • 1、将所有文件的top()元素聚集在一起构建一个小顶堆
    • 2、再让根节点出堆,从根节点所在的文件中找下一个元素入堆,再构建小顶堆,再出堆,然后继续,直到所有元素读入并写入
    #include 
    #include 
    #include 
     
    int main()
    {
        using namespace std;
        using VI = vector<int>;
     
        vector<VI> src = { {1, 2, 3}, {2, 4, 6}, {3, 5, 8} };
        priority_queue<VI, vector<VI>, greater<VI>> pq;
        VI vc;
     
        //1.先将每路的最小值(第一列元素)放入堆中, 以获得全局的最小值
        for(int i = 0; i < src.size(); ++i)
        {
            vc = {src[i][0], i, 0};  //值, 纵坐标, 横坐标
            pq.emplace(vc);
        }
     
        VI ans;
        while( !pq.empty() )
        {
            vc = pq.top();  //弹出堆顶元素
            pq.pop();
     
            ans.emplace_back(vc[0]);
            //将堆顶元素所在数组的下一个元素放入堆中
            if(vc[2] < src[vc[1]].size() - 1) {
                vc = {src[ vc[1] ][ vc[2] + 1 ], vc[1], vc[2] + 1};
                pq.emplace(vc);
            }
        }
     
        for(auto &x : ans)
        {
            cout << x << " ";
        }
     
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 这里的emplace类似于append
    • emplace_back:和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。
    • priority_queue:在优先队列中,优先级高的元素先出队列,并非按照先进先出的要求,类似一个堆(heap)
      • priority_queue(),默认按照从小到大排列。所以top()返回的是最大值而不是最小值!
      • 使用greater<>后,数据从大到小排列,top()返回的就是最小值而不是最大值!
    priority_queue<int,vector<int> , greater<>> pq;//这是对的
    
    • 1
  • 相关阅读:
    ZZNUOJ_用C语言编写程序实现1541:除法(附完整源码)
    Jupyter使用技巧-环境篇
    css一些常见属性
    Linux之C编程错误处理、多文件工程、man的使用技巧
    java-数据迁移-定制拓展
    App常用接口
    实在智能携手中国电信翼支付,全球首款Agent智能体亮相2023数字科技生态大会
    PAT 乙级 1052 卖个萌 python
    7、脏话检测
    2023年第三届认证杯数学中国全国大学生数学竞赛
  • 原文地址:https://blog.csdn.net/universe_1207/article/details/126616719