排序算法-冒泡、选择、堆、插入、归并、快速、希尔

简介: 排序算法-冒泡、选择、堆、插入、归并、快速、希尔
  • 排序算法,默认是升序,左边的值是属于“小”值

理解比较大小后的交换:当前元素cur 和 左边的元素cur-1, 左边的比较大,就交换或者挪动 array[cur] = array[cur-1];

  • 编码的区间设置:建议是左闭 右开,方便 [begin, end)
  • 计算方面:使用右移 代替 除法

☺ 排序算法---重点放到比较的排序算法---冒泡、选择、堆排序 插入、归并、快速、希尔,

对于计数排序、基数排序不是面试的重点


排序算法-冒泡、选择、堆排序

一、冒泡排序:

为什么要叫冒泡排序? 因为形象生动上,每一轮都是最大的那个数冒到结尾

定义:

从头开始index=1 比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置;执行完一轮后,最末尾那个元素就是最大的元素。

② 忽略 ① 中曾经找到的最大元素,重复执行步骤 ①,直到全部元素有序。

一轮的比较范围是从1 到 end,然后根据理解得到end 从最后一个元素开始不断减少】


  • 第一轮的比较,下标从1开始,左边比右边大进行交换

int array[] = {1, 3, 5, -1, -8};
    //比较 end 的结束范围,从最后一个元素-第二个元素
    for(int end = array.length - 1; end > 0; end--) {
      //冒泡排序-一轮的交换
      for(int begin = 1; begin <= end; begin++) {
        if(array[begin] < array[begin-1]) {//当前元素的左边元素比较大时要交换
          int temp = array[begin];
          array[begin] = array[begin-1];
          array[begin-1]=temp;
        }
      }
    }


优化1-冒泡排序

▪ 提前有序,终止比较(不一定有用:因为一般在随机数据中要提前有序的概率是很低的,)

  • 布尔变量:假设本轮已经有序
//比较 end 的结束范围,从最后一个元素-第二个元素
    for(int end = array.length - 1; end > 0; end--) {
      boolean sorted = true;//假设提前有序
      //冒泡排序-一轮的交换
      for(int begin = 1; begin <= end; begin++) {
        if(array[begin] < array[begin-1]) {//当前元素的左边元素比较大时要交换
          int temp = array[begin];
          array[begin] = array[begin-1];
          array[begin-1]=temp;
          sorted = false;//本轮有交换,证明假设提前有序 不成立
        }
      }
      if(sorted) break;//果然提前有序,不用再比较了
    }


优化2-冒泡排序

  • 如果序列尾部已经局部是有序的,可以记录最后一次交换的位置,从而减少比较的次数。

int array[] = {1, 3, 5, -1, -8};
    //比较 end 的结束范围,从最后一个元素-第二个元素
    for(int end = array.length - 1; end > 0; end--) {
      int sortedIndex = 1;//记录最后一次交换的位置,初始值为1,是为了考虑一开始数组就是完全有序的
      //冒泡排序-一轮的交换
      for(int begin = 1; begin <= end; begin++) {
        if(array[begin] < array[begin-1]) {//当前元素的左边元素比较大时要交换
          int temp = array[begin];
          array[begin] = array[begin-1];
          array[begin-1]=temp;
          sortedIndex = begin;//记录最后一次交换的位置
        }
      }
      end = sortedIndex;//更新比较范围到最后一次交换的位置
    }



二、选择排序

为什么要叫选择排序? 因为每一轮都是把最大的那个数的位置给选择出来

定义:

从序列中找出最大的那个元素,然后与最末尾的元素交换位置;执行完一轮后,最末尾的那个元素就是最大的元素。

② 忽略 ① 中曾经找到的最大元素,重复执行步骤 ①

▪ 选择排序本质上看是冒泡的优化:

因为冒泡是从头到尾:相邻两个元素比较完就交换

选择排序是从头到尾:先找到最大元素位置,然后记录位置,最后才交换

int array[] = {1, 3, 5, -1, -8};
    //比较 end 的结束范围,从最后一个元素-第二个元素
    for(int end = array.length - 1; end > 0; end--) {
      int maxIndex = 0;
      //选择排序-找出本轮的最大值
      for(int begin = 1; begin <= end; begin++) {
        if(array[maxIndex] <= array[begin]) {//取等号是为了变成稳定的排序算法 10  10()  8 --一轮比较--> 10   8   10()  
                    maxIndex = begin;
        }
      }
      //交换,把本轮找到的最大一个数放到结尾
      int temp = array[maxIndex];
      array[maxIndex] = array[end];
      array[end] = temp;
    }



三、堆排序

▪ 堆排序本质上看是冒泡的优化:

选择排序是从头到尾:每一轮都在 从头到尾找到最大元素位置(内循环在找最大值)

---- 找最值,可以交给堆负责(优化)

▪ 堆排序:先原地建堆,然后尾部元素放到堆顶,然后下滤

int array[] = {1, 3, 5, -1, -8};
// 原地建堆
heapSize = arrayay.length;
for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
    siftDown(i);
}
while (heapSize > 1) {
  // 交换堆顶元素和尾部元素
  int temp = array[0];
  array[0] = array[heapSize];
  array[heapSize] = temp;
  heapSize--;
    // 对0位置进行siftDown(恢复堆的性质)
  siftDown(0);
}
    /**
   * 下滤
  */
  private void siftDown(int index) {  
    int half = heapSize >> 1;
    Integer element = array[index];
    while(index < half) { //index 必须是非叶子节点!!!
            // 默认拿是左边跟父节点比大小
      int childIndex = (index << 1) + 1;
      Integer childElement = array[childIndex];
      int rightIndex = childIndex + 1;
      if(rightIndex < size
          && compare(array[rightIndex], childElement) > 0) {
        childElement = array[childIndex = rightIndex];
      }
      if(compare(element, childElement) >= 0) break;
      //子结点大的话
      array[index] = childElement;
      index = childIndex;
    }
    array[index] = element;
  }



排序算法-插入排序

一、插入排序

插入排序非常类似于扑克牌的排序

定义:① 在执行过程中,插入排序会将序列分为2部分;头部是已经排好序的,尾部是待排序的

② 从头开始扫描每一个元素;每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序

private void sort(Integer[] array) {
------------------------------------------- 核心代码开始 ------------------------------------------------------------   
    for(int begin = 1; begin < array.length; begin++) {//从无序区抓起的牌
      int cur = begin;
      while(cur > 0 && cmp(array[cur], array[cur - 1]) < 0) {//这张牌不断往左边走,和紧邻[cur-1]的头部有序区进行比较,小于左边就交换
        swap(array[cur], array[cur - 1]);
        cur--;//交换完,这个牌的位置
      }
    }
------------------------------------------- 核心代码结束 ------------------------------------------------------------   
  }
  protected int cmp(int v1, int v2) {
    return v1 - v2;
  }
  protected void swap(int v1, int v2) {
    int tmp = v1;
    v1 = v2;
    v2 = tmp;
  }


优化1-插入排序

  • 优化:挪动替换交换

private void sort(Integer[] array) {
    for(int begin = 1; begin < array.length; begin++) {//从无序区抓起的牌
      int cur = begin;
      Integer v = array[cur];
      while(cur > 0 && cmp(v, array[cur - 1]) < 0) {//头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
        array[cur] = array[cur-1];//左边的值比较大就往右边挪动
        cur--;
      }
      //找到合适的位置,插入
      array[cur] = v;
    }
  }


优化2-插入排序

  • 找位置--使用二分搜索法(通过挪动左右指针,不断缩小一半的可能范围)
  • 使用二分搜索优化了比较次数

细节:二分搜素的开始-结束区间[begin, end)

  • int begin = 0; int end = array.length;
  • 为什么结束要取 end,因为方便后续其他计算,比如算出该区间有多少元素


  • 优化二分搜素[原:查找位置]为[查找待插入的位置]
    ▪ 原:查找到位置

查找目标位置的:查找过程分成三种判断条件:小于,去左边查找;大于,去右边查找;等于直接返回目标


▪ 现:查找到待插入位置

查找待插入的目标位置的:查找过程分成两种种判断条件:小于,去左边查找;大等于,去右边查找;因为这个等于不是待插入的目标位置

  • 待插入的目标位置是第一个大于 待插入原始的值v 的元素位置


/**
   * 查找待插入位置的索引
   */
  private int search(int index) {//index 是待插入索引
    int begin = 0;
    int end = index;
    while(begin < end) {
      int mid = (begin + end) >> 2;
      if(cmp(array[index], array[mid]) < 0) {
        end = mid;
      }else {
        begin = mid + 1;
      }
    }
    return begin;
  }
  private void sort(Integer[] array) {
    for(int begin = 1; begin < array.length; begin++) {//从无序区抓起的牌
      Integer v = array[begin];//备份插入元素
      int insertIndex = search(begin);//查找到合适的插入位置
      for(int i = begin; i > insertIndex; i--) {//挪动腾出空间
        array[i] = array[i - 1];
      }
      array[insertIndex] = v;
    }
  }



排序算法-归并排序

一、归并排序

定义:

① 不断地将当前序列平均分割成2个子序列;直到不能再分割(序列中只剩1个元素)

② 不断地将2个子序列合并成一个有序序列;直到最终只剩下1个有序序列

int[] leftArray;
  int[] array;
  private void sort(int begin, int end) {
    if(end - begin < 2) return;//至少要有2个元素
    int mid = (begin + end) >> 1;
    sort(begin, mid);//左边归并
    sort(mid, end);//右边归并
    merge(begin, mid, end);//最后进行合并
  }
  /**
   * 将 [begin, mid) 和 [mid, end) 范围的序列数组合并成一个有序序列 
   */
  private void merge(int begin, int mid, int end) {
    int li = 0, le = mid - begin;//左边数组leftArray
    int ri = mid, re = end;//右边数组
    int ai = begin;//arrayay 的索引
    for(int i = li; i < le; i++) {//拷贝arrayay左边数组到leftArray
      leftArray[i] = array[begin + i];
    }
    while(li < le) {//左边还有元素
      if(ri < re && cmp(array[ri], leftArray[li]) < 0) {//右边有元素,且右边的值更加小
        array[ai++] = array[ri++];
      }else {
        array[ai++] = leftArray[li++];//左边有元素,且左边的值更加小
      }
    }



排序算法-快速排序、希尔排序

一、快速排序

快速排序的本质:逐渐将每一个元素都转换成轴点元素

定义:

① 从序列中选择一个轴点元素(pivot)

▪ 假设每次选择 0 位置的元素为轴点元素

② 利用 pivot 将序列分割成 2 个子序列

▪ 将小于 pivot 的元素放在pivot前面(左侧)

▪ 将大于 pivot 的元素放在pivot后面(右侧)

▪ 等于pivot的元素放哪边都可以

③ 对子序列进行 ① ② 操作

▪ 直到不能再分割(子序列中只剩下1个元素)


是一个递归排序:从轴点切分成两部分,不断地对左部分进行快速排序,不断地对右部分进行快速排序

判断该元素是 "甩"到左边 还是 右边,不加等号,因为加上等号会使得轴点元素分割出来的子序列极度不均匀

  • 比如 6 6 6 6 6,轴点是6,那么取等号,全部给甩到一边了


int[] array;
  private void sort(int begin, int end) {
    if(end - begin < 2) return;//至少要有两个元素
    int mid = pivotIndex(begin, end);//轴点,以轴点分割成了左右两部分
    sort(begin, mid);//对左部分进行快速排序
    sort(mid + 1, end);//对右部分进行快速排序
  }
  /**
   * 对 [begin, end) 范围内的元素进行快速排序
   */
  private int pivotIndex(int begin, int end) {
    swap(begin, begin + (int)(Math.random() * (end - begin)));//随机交换begin位置的元素
    Integer pivot = array[begin];//备份轴点元素
    end--;//让右边指针直到元素上
------------------------------------------- 核心代码开始 ------------------------------------------------------------     
    while(begin < end) {
      //内部使用了两个while 搭配 break,实现在右边找“小”、在左边找“大”后,对数组指针指向"调头"
      while(begin < end) {              //不加等号,因为加上等号会使得轴点元素分割出来的子序列极度不均匀
        if(cmp(pivot, array[end]) < 0) {// 右边元素>轴点元素,不是目标,要在右边找“小”
          end--;
        }else {
          array[begin++] = array[end];
          break;
        }
      }
      while(begin < end) {
        if(cmp(pivot, array[begin]) > 0) {// 左边元素<轴点元素,不是目标,要在左边找“大”
          begin++;
        }else {
          array[end--] = array[begin];
          break;
        }
      }
    }
------------------------------------------- 核心代码结束 ------------------------------------------------------------   
    array[begin] = pivot;//数组轴点元素进行赋值
    return begin;
  }
  protected int cmp(int v1, int v2) {
    return v1 - v2;
  }
  protected void swap(int v1, int v2) {
    int tmp = v1;
    v1 = v2;
    v2 = tmp;
  }



二、希尔排序

切分成n 列然后进行排序--> 逆序对数量逐渐减少 --> 希尔排序,底层是使用插入排序,也可以把希尔看成是对插入排序的改进版

int[] array;
  private void sort() {
    List<Integer> stepSequence = shellStepSequence();
    for(Integer step: stepSequence) {//按照每个步长进行切割,然后进行一一对应的比较 排序
      sort(step);
    }
  }
  /**
   * 按照给定的步长进行切割,然后对 step 列进行排序
   */
  private void sort(Integer step) {
 ------------------------------------------- 核心代码开始 ------------------------------------------------------------       
    // col: 第几列
    for(int col = 0; col < step; col++) {//对col列进行排序
      // 第col 列中的元素: col、col+2*step、col+3*step、col+4*step
      // 结合了步长的插入排序
      for(int begin = col + step; begin < array.length; begin++) {
        int cur = begin;
        while(cur > col && cmp(array[cur], array[cur - step]) < 0) {
          swap(array[cur], array[cur - step]);
          cur -= step;
        }
      }
    }
------------------------------------------- 核心代码结束 ------------------------------------------------------------         
  }
  /**
   * 步长序列:获取 step 步长分割数组
   */
  private List<Integer> shellStepSequence(){
    List<Integer> stepSequence = new ArrayList<>();
    int step = array.length;
    while((step >>= 1) > 0) {
      stepSequence.add(step);
    }
    return stepSequence;
  }
  protected int cmp(int v1, int v2) {
    return v1 - v2;
  }
  protected void swap(int v1, int v2) {
    int tmp = v1;
    v1 = v2;
    v2 = tmp;
  }


▪ 冒泡、选择、插入、归并、快速、希尔、堆排序,都是基于比较的排序 [面试会考]

  • 平均时间复杂度目前最低是 O(nlogn)

▪ 计数排序、桶排序、基数排序,都不是基于比较的排序 [面试不考,作为了解]

  • 它们是典型的用空间换时间,在某些时候,平均时间复杂度可以比 O(nlogn) 更低


排序算法-计数排序、基数排序、桶排序

一、计数排序

细节:在java 整型数组,new出数组之后,内部元素默认都是 0

int counts = new int[10]; //new 完,默认所有元素都是0

int[] array;
  private void sort() {
    //找出最大值
    int max = array[0];
    for(int i = 1; i < array.length; i++) {
      if(array[i] > max) {
        max = array[i];
      }
    }
 ------------------------------------------- 核心代码开始 ------------------------------------------------------------       
    // 开辟内存空间,存储每一个整数出现的次数
    int[] counts = new int[1 + max];//整型数组,new出数组之后,内部元素默认都是 0 
    //统计每个整数出现的次数
    for(int i = 0; i < array.length; i++) {
      counts[array[i]]++;
    }
    //根据整数出现次数,对整数进行排序
    int index = 0;//整数数组上的指针
    for(int i = 0; i < counts.length; i++) {
      while(counts[i]-- > 0) {
        array[index++] = i;
      }
    }
------------------------------------------- 核心代码结束 ------------------------------------------------------------          
  }


■ 计数排序缺点:

  • 无法对负整数进行排序
  • 极其浪费内存空间
  • 是个不稳定的排序


■ 计数排序的优化

int[] array;
  private void sort() {
    // 找出最大值、最小值
    int max = array[0];//最大值
    int min = array[0];//最小值
    for(int i = 1; i < array.length; i++) {
      if(array[i] > max) {
        max = array[i];
      }
      if(array[i] < min) {
        min = array[i];
      }
    }
 ------------------------------------------- 核心代码开始 ------------------------------------------------------------    
    //开辟内存空间,存储次数
    int[] counts = new int[max - min + 1];
    //统计每个整数出现的次数
    for(int i = 0; i < array.length; i++) {
      counts[array[i] - min]++;
    }
    //累加次数
    for(int i = 1; i < counts.length; i++) {
      counts[i] += counts[i - 1];
    }
    //从后往前遍历元素,将它放到有序数组中的合适位置
    int[] newArray = new int[array.length];
    for(int i = array.length - 1; i >= 0; i--) {
      newArray[--counts[array[i] - min]] = array[i];// 公式 count[k - min] -p 其中p是该元素k倒数着出现的次数
    }
------------------------------------------- 核心代码结束 ------------------------------------------------------------       
    //将有序数组赋值到array
    for(int i = 0; i < newArray.length; i++) {
      array[i] = newArray[i];
    }
  }



二、基数排序

定义:依次对个位数、十位数、百位数、千位数、万位数...进行排序(从低位到高位)


int[] array;
  private void sort() {
    // 找出最大值
    int max = array[0];
    for (int i = 1; i < array.length; i++) {
      if (array[i] > max) {
        max = array[i];
      }
    }
    // max = 593
    // 个位数 array[i] / 1 % 10 = 3
    // 十位数 array[i] / 10 % 10 = 9
    // 百位数 array[i] / 100 % 10 = 5
    for (int divider = 1; divider <= array.length; divider *= 10) {
      countingSort(divider);
    }
  }
  private void countingSort(int divider) {
    // 开辟内存空间,存储次数
    int[] counts = new int[10];//给个位数、十位数、百位数排序,它们的范围都是 0-9
    // 统计每个整数出现的次数
    for (int i = 0; i < array.length; i++) {
//      counts[array[i]的基数]++;
      counts[array[i] / divider % 10]++;
    }
    // 累加次数
    for (int i = 1; i < counts.length; i++) {
      counts[i] += counts[i - 1];
    }
    // 从后往前遍历元素,将它放到有序数组中的合适位置
    int[] newArray = new int[array.length];
    for (int i = array.length - 1; i >= 0; i--) {
//      newArray[--counts[array[i]的基数] = array[i];
      newArray[--counts[array[i] / divider % 10]] = array[i];
    }
    // 将有序数组赋值到array
    for (int i = 0; i < newArray.length; i++) {
      array[i] = newArray[i];
    }
  }


■ 基数排序的另外一种思路:元素先存到桶里,然后再回收



三、桶排序



如果本文对你有帮助的话记得给一乐点个赞哦,感谢!

目录
相关文章
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
65 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
14 0
|
1月前
|
搜索推荐 算法
排序算法---冒泡&选择&插入总结
排序算法---冒泡&选择&插入总结
15 0
|
3月前
|
搜索推荐 算法
十大排序算法-快排-希尔-堆排-归并-冒泡-桶排-选择-插入-计数-基数
十大排序算法-快排-希尔-堆排-归并-冒泡-桶排-选择-插入-计数-基数
十大排序算法-快排-希尔-堆排-归并-冒泡-桶排-选择-插入-计数-基数
|
3月前
|
数据采集 算法
基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真
该程序利用PSO算法优化5个4*20矩阵中的模块采集轨迹,确保采集的物品数量及元素含量符合要求。在MATLAB2022a上运行,通过迭代寻优,选择最佳模块组合并优化轨道,使采集效率、路径长度及时间等综合指标最优。具体算法实现了粒子状态更新、需求量差值评估及轨迹优化等功能,最终输出最优轨迹及其相关性能指标。
|
3月前
|
数据采集 搜索推荐 算法
【高手进阶】Java排序算法:从零到精通——揭秘冒泡、快速、归并排序的原理与实战应用,让你的代码效率飙升!
【8月更文挑战第21天】Java排序算法是编程基础的重要部分,在算法设计与分析及实际开发中不可或缺。本文介绍内部排序算法,包括简单的冒泡排序及其逐步优化至高效的快速排序和稳定的归并排序,并提供了每种算法的Java实现示例。此外,还探讨了排序算法在电子商务、搜索引擎和数据分析等领域的广泛应用,帮助读者更好地理解和应用这些算法。
42 0
|
26天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
11天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
12天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
13天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。