【学习笔记】数据结构与算法:排序

目录

    • 排序
    • 冒泡排序 Bubble Sort
    • 选择排序 Selection Sort
    • 直接插入排序 Straight Insertion Sort
    • 希尔排序 Shell Sort
    • 堆排序 Heap Sort
        • 优先队列
        • 利用堆进行排序
    • 归并排序 Merge Sort
    • 快速排序 Quick Sort
        • 快速排序
        • 快速排序和归并排序的对比

排序

  1. 排序的定义:假设含有n个元素的序列{r1,r2,…,rn},对应关键字分别为{k1,k2,…,kn},需确定一种排列p1,p2,…,pn,使得对应关键字满足kp1<=kp2<=…<=kpn关系。
  2. 排序的稳定性:关键字相同的元素顺序是否有可能在排序后交换前后顺序
  3. 内排序和外排序
    • 内排序:待排序的所有元素被放置在内存中
    • 外排序:排序过程需要在内外存之间多次交换数据
  4. 排序的性能衡量
    • 时间性能(关键字的比较+元素移动)
    • 辅助空间大小
    • 算法本身的复杂性
  5. 内排序算法的种类
    交换排序 插入排序 选择排序 归并排序
    简单算法 冒泡排序 直接插入排序 选择排序
    改进算法 快速排序 希尔排序 堆排序 归并排序
  6. 排序算法对比
    排序方法 平均时间复杂度 辅助空间 稳定性
    冒泡排序 O(n^2) O(1) 稳定:两两交换时,关键字相同的元素不会被左右交换
    选择排序 O(n^2) O(1) 稳定:在选择待排序列表的最小值元素时是从左到右查找的
    直接插入排序 O(n^2) O(1) 稳定:元素在插入左边的有序列表时,遇到相等值的元素就不会再移动
    希尔排序 O(nlogn)~O(n^2) O(1) 不稳定:元素跳跃式移动
    堆排序 O(nlogn) O(1) 不稳定:堆的定义导致的
    归并排序 O(nlogn) O(n+logn) 稳定:merge时可以优先挑选左子数组的元素
    快速排序 O(nlogn) O(logn) 不稳定:partition时元素跳跃式交换

冒泡排序 Bubble Sort

  1. 思路:两两比较相邻元素的关键字,如果反序则交换,直到没有反序的元素为止

  2. 伪代码

      def BubbleSort():
          for i = 1:n(循环操作n-1次,不针对哪个元素):
              for j = n:i(从后往前遍历):
                  if front>back: exchange(front, back) #把已发现的最小元素抓到最前面
    
  3. 逻辑

    • 列表左侧是已经排好的有序的列表,右侧是待处理的
    • 每一轮迭代都是对待处理部分进行操作:从后往前(从右往左)两两比较,每次都抓取已发现的最小元素,将其带到有序部分的末端(也就是待处理部分的左端)
      【学习笔记】数据结构与算法:排序_第1张图片
  4. 复杂度分析:O(n^2)

选择排序 Selection Sort

  1. 思路:在第i轮迭代中,通过n-i次关键字的比较,从n-i+1个元素中选出关键字最小的元素,并和第i个元素交换

  2. 伪代码

       def SelectSort():
           for i = 1:n-1 (循环n-1次,每次第i个元素要给找出的最小值腾出空位):
               for: 
                   find_min(第i个后面的元素中)
               exchange(i,min) #将剩下未排序的元素中的最小那个排上来
    
  3. 逻辑

    • 列表左侧是已经排好的有序的列表,右侧是待处理的
    • 每一轮迭代的任务是在右侧剩下的元素中找到最小的,并放到左侧有序部分的最右边(相当于对于每次的下一轮迭代,要找的都是次小的);
      【学习笔记】数据结构与算法:排序_第2张图片
  4. 复杂度分析:O(n^2)

    • 第i轮要比较n-i次,总比较次数n(n-1)/2次
    • 交换次数n-1次

直接插入排序 Straight Insertion Sort

  1. 思路:将一个元素插入到已经排好序的列表,从而得到一个新的、元素数增1的有序表
  2. 伪代码
     def InsertionSort():
         for i = 2:n(循环n-1次): #每轮循环中是第i个元素被执行插入排序
             for j=i:1 (从后往前遍历已排序的列表):
                 if front>back: exchange(front, back) #将原来的第i个元素插到正确的位置
    
  3. 逻辑
    • 列表左侧是已经排好的有序的列表,右侧是待处理的
    • 每一轮迭代的任务是直接取待处理列表最靠近左侧有序列表的元素(最左边的一个),并将该元素通过逐次交换的方式向左移动到有序表里的合适位置【学习笔记】数据结构与算法:排序_第3张图片4. 复杂度分析:O(n^ 2)(平均比较和移动次数是n^ 2/4,比简单选择排序略好)

希尔排序 Shell Sort

  1. 理念:使列表向基本有序的方向发展
  2. 思路:直接插入排序的改进版——将相距某个增量的元素组成一个子序列,每次在这些子序列内进行直接插入排序。通过进行局部插入排序,且间隔越来越小,最终完成整体排序
  3. 伪代码
      def shellSort():
          h = 初始化间隔
          while h >= 1: #进行多次间隔不断缩小的插入排序,直至间隔为1
              for:
                  for:
                      if front>back: exchange(front, back) #两层循环,对子列表做插入排序
              h = 缩小间隔
    
  4. 逻辑
    • 在第一轮外循环中,将增量h定为一个初始值(常为列表长度的1/4~1/3),这样原列表可被视为h个子列表,这些子列表的长度为n//h(或n//h + 1)。
    • 对这h个子列表进行直接插入排序(但不是分开进行,而是同步进行。在内迭代中,从第h+1个元素遍历到第n个元素时,慢慢地完成了这h个子列表的插入排序)
    • 减少增量h(通常取上次的1/4~1/3),列表又被分为了子列表(子列表个数更多,长度更短);重复①②两步,直到增量取h=1时也被进行过同样的操作
      【学习笔记】数据结构与算法:排序_第4张图片
  5. 复杂度分析:希尔排序的时间复杂度没有明确值

堆排序 Heap Sort

优先队列
  1. 是具有这样性质的完全二叉树:大顶堆:每个结点的值都>=左右孩子结点的值(小顶堆相反)
利用堆进行排序
  1. 逻辑

    • 总体逻辑:序列的最大值是堆顶的根结点。移走根结点(i.e.与堆数组的末尾交换),则堆数组末尾元素就是最大值;然后将剩余的n-1个元素重新调整为堆,移走根结点得到次大值;重复执行得到有序序列。
    • 将n-1元素调整为新的堆(HeapAdjust):
      此时除了被换上来的新堆顶元素以外,其余元素均满足堆的定义。基本思路是沿关键字较大的孩子结点向下筛选,把堆顶元素和下层的元素逐步交换到合适的位置
    • 由无序序列构建一个堆:
      从下往上,从右往左,对每个非叶节点当作根结点,进行HeapAdjust调整 【学习笔记】数据结构与算法:排序_第5张图片
  2. 伪代码

      def headSort():
          for 倒序遍历所有非叶节点:
              heapAdjust() #构建大顶堆
          for i = n:2 (倒序遍历n-1次):
              exchange(1,i)
              heapAdjust(剩下的1~i-1个元素) #将剩下的元素重新排成大顶堆
    
  3. 复杂度分析:O(nlogn)

归并排序 Merge Sort

  1. 思路:先递归地将数组分成两半排序,然后将结果归并起来(2路归并排序)

  2. 伪代码

    • 主程序 - MergeSort
       def mergeSort(序列, low, high):
           if 列表长度为1: return
           elif 列表长度为2: if >: exchange() return
           else
               mergeSort(左侧) #左侧子序列往里递归排序
               mergeSort(右侧) #右侧子序列往里递归排序
               merge(当前整个列表)
      
    1. 核心 - Merge(处理整个序列的部分)
         def merge():
             sorted = 定义一个辅助数组
             for i = low:high: #对
                 #在左右两侧每次选一个,挑出来放
                 if 左侧元素选完了: 选右侧的
                 elif 右侧元素选完了: 选左侧的
                 elif 左侧可选的元素更小: 选左侧的
                 elif 右侧可选的元素更小: 选右侧的
             return sorted
      
  3. 逻辑

    1. 在主程序MergeSort中,
      • 先将数组分成两半递归→使得两半各自已经有序→再处理整个数组→使得整个数组完全有序
      • 递归时数组不断分半,直到遇到两种情况之一时不再递归:
        1)子数组仅有一个元素,什么也不做
        2)子数组仅有两个元素,进行必要的交换
        【学习笔记】数据结构与算法:排序_第6张图片 2. 在Merge中,
        从两边已经排好序的子数组中逐个从小到大地抓取元素,从左到右放入一个新的辅助数组里,直到全部抓取完(要考虑其中一侧提前被抓取完的情况)【学习笔记】数据结构与算法:排序_第7张图片
  4. 复杂度分析:

    • 时间复杂度O(nlogn)
    • 空间复杂度O(n+logn)
      • O(n):要创建等长度的辅助数组
      • O(logn):递归时深度为log2n的栈空间

快速排序 Quick Sort

快速排序
  1. 思路:将序列分为独立的两部分,其中一部分所有关键字均比另一部分的关键字小,然后递归地对两部分继续排序

  2. 伪代码

    • 主程序 - QuickSort
        def quickSort(序列,low,high):
            if 序列长度大于1:
                分隔点=partition()
                quickSort(左侧)
                quickSort(右侧)
        ```
      
    • 核心 - Partition(处理整个序列的部分)
         def partition(序列,low,high):
             pivot = 设定枢轴值
             while 从左向右遍历的low和从右向左遍历的high未交汇:
                 if 序列[low] >pivot && 序列[high] < pivot:
                     exchange()
                 low++
                 high--
      
  3. 逻辑

    1. 在主程序QuickSort中,
      • 先将整个数组分成两边→大致分出规律→再递归处理两半→处理完两半后整个数组完全有序
      • 递归时,直到子序列长度为0或1时不再递归
    2. 在Partition中,
      • 先选取一个关键字(一般是第一个)(称为枢轴)
      • 其余部分列表从两侧同时开始往中间遍历,通过在必要时交换,实现一侧比枢轴值小,另一侧比枢轴值大(两侧未必等长)
        【学习笔记】数据结构与算法:排序_第8张图片
  4. 复杂度分析

    • 时间复杂度O(nlogn)
    • 空间复杂度O(logn):log2n大小的栈空间
快速排序和归并排序的对比
归并排序 快速排序
什么时候进行递归 递归调用发生在处理整个数组之前 递归调用发生在处理整个数组之后
切分后子数组的长度 数组被等分(或长度差1);两个子数组相邻 切分方法取决于数组的内容;两个子数组中间有一个元素隔开不参与后续递归

你可能感兴趣的:(数据结构与算法,学习,笔记,排序算法,算法,数据结构)