• 数据结构与算法(java版)第二季 - 1 冒泡、选择、堆排序


    初识排序

    什么叫排序?

    排序前:3,1,6,9,2,5,8,4,7
    排序后:1,2,3,4,5,6,7,8,9(升序) 或者 9,8,7,6,5,4,3,2,1(降序)
    排序的应用

    十大排序算法 

     冒泡排序(Bubble Sort)

    冒泡排序也叫做起泡排序
    ◼ 以升序为例子
    ① 从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置
    执行完一轮后,最末尾那个元素就是最大的元素
    ② 忽略 ① 中曾经找到的最大元素,重复执行步骤 ①,直到全部元素有序
    代码
    1. public class Bubble_Sort {
    2. public static void main(String[] args) {
    3. int[] array = {10,9,20,36,89,45,65,15,64,47};
    4. //进行一个冒泡排序的过程
    5. for(int end = array.length-1 ; end > 0;end--) {
    6. for (int begin = 1; begin <= end; begin++) {
    7. if (array[begin] < array[begin - 1]) {
    8. int tmp = array[begin];
    9. array[begin] = array[begin - 1];
    10. array[begin - 1] = tmp;
    11. }
    12. }
    13. }
    14. //进行一个遍历的操作
    15. for(int i = 0;i < array.length;i++)
    16. {
    17. System.out.println(" " + array[i]);
    18. }
    19. }
    20. }

    冒泡排序 - 优化一

    如果给定的数据就是有序的,可以提前终止排序.

    1. static void bubbleSort2(Integer[] array)
    2. {
    3. //生成一个随机数字
    4. //Integer[] array = Integers.random(10,1,100);
    5. // 生成一个随机数字,由于jdk的版本,这个版本的java没有这个东西
    6. //打印可以使用Integers.println(array)
    7. /*
    8. Times.test("冒泡排序",null);
    9. */
    10. //进行一个冒泡排序的过程
    11. for(int end = array.length-1 ; end > 0;end--) {
    12. //标记是否有序,放在这个位置的原因是在扫描的过程可能就是有序的
    13. boolean sorted = true;
    14. for (int begin = 1; begin <= end; begin++) {
    15. //一旦这个条件是有序的,说明这个就是无序的
    16. if (array[begin] < array[begin - 1]) {
    17. int tmp = array[begin];
    18. array[begin] = array[begin - 1];
    19. array[begin - 1] = tmp;
    20. sorted = false;//如果要是能进来说明无序
    21. }
    22. }
    23. //只要是检测到排序的过程中,已经排好了,直接跳出即可.
    24. if(sorted) break;
    25. }
    26. //进行一个遍历的操作
    27. for(int i = 0;i < array.length;i++)
    28. {
    29. System.out.println(array[i]);
    30. }
    31. }

    上述代码用到了一个测试时间的java类,Times.class代码是如下所示:

    1. import java.text.SimpleDateFormat;
    2. import java.util.Date;
    3. public class Times {
    4. private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
    5. public Times() {
    6. }
    7. public static void test(String title, Times.Task task) {
    8. if (task != null) {
    9. title = title == null ? "" : "【" + title + "】";
    10. System.out.println(title);
    11. System.out.println("开始:" + fmt.format(new Date()));
    12. long begin = System.currentTimeMillis();
    13. task.execute();
    14. long end = System.currentTimeMillis();
    15. System.out.println("结束:" + fmt.format(new Date()));
    16. double delta = (double)(end - begin) / 1000.0D;
    17. System.out.println("耗时:" + delta + "秒");
    18. System.out.println("-------------------------------------");
    19. }
    20. }
    21. public interface Task {
    22. void execute();
    23. }
    24. }

    注意:上述的优化过程,只有在数据是提前有序的时候才会花费的时间更少,否则的话,运行的时间还是非常长的.

    冒泡排序 - 优化二

    如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数.
    由上图观察可知,最后一个需要进行交换的6位置的数据,如果我们是提前可以观察到相应的示意图,就是可以不用进行理会后面的数据的.
    1. static void bubbleSort3(Integer[] array)
    2. {
    3. //进行一个冒泡排序的过程
    4. for(int end = array.length-1 ; end > 0;end--) {
    5. //用来记录哪一个是已经替换好的,这里的初始值是存在一些讲究的
    6. //sortedIndex的初始值在数组完全有序的时候有用
    7. int sortedIndex = 0;
    8. for (int begin = 1; begin <= end; begin++) {
    9. //一旦这个条件是有序的,说明这个就是无序的
    10. if (array[begin] < array[begin - 1]) {
    11. int tmp = array[begin];
    12. array[begin] = array[begin - 1];
    13. array[begin - 1] = tmp;
    14. //直接记录最后一次扫描的位置
    15. sortedIndex = begin;
    16. }
    17. end = sortedIndex;
    18. }
    19. }
    20. //进行一个遍历的操作
    21. for(int i = 0;i < array.length;i++)
    22. {
    23. System.out.println(array[i]);
    24. }
    25. }
    最坏、平均时间复杂度: O(n^ 2 )
    最好时间复杂度: O(n)
    空间复杂度: O(1) [没有用到额外的空间]

    排序算法的稳定性(Stability)

    如果相等的2个元素,在排序前后的相对位置保持不变,那么这是 稳定 的排序算法
    排序前: 5, 1, 3 𝑎 , 4, 7, 3 𝑏
    稳定的排序: 1, 3 𝑎 , 3 𝑏 , 4, 5, 7
    不稳定的排序: 1, 3 𝑏 , 3 𝑎 , 4, 5, 7
    ◼  对自定义对象进行排序时,稳定性会影响最终的排序效果
    冒泡排序属于稳定的排序算法(只有当右边的小于左边的才会进行一个交换)
    稍有不慎,稳定的排序算法也能被写成不稳定的排序算法,比如下面的冒泡排序代码是不稳定的

    原地算法(In-place Algorithm)

    何为原地算法?
    不依赖额外的资源或者依赖少数的额外资源,仅依靠输出来覆盖输入
    空间复杂度为 𝑂(1) 的都可以认为是原地算法
    非原地算法,称为 Not-in-place 或者 Out-of-place
    冒泡排序属于 In-place

    选择排序(Selection Sort)

    执行流程
    ① 从序列中找出最大的那个元素,然后与最末尾的元素交换位置
    执行完一轮后,最末尾的那个元素就是最大的元素
    ② 忽略 ① 中曾经找到的最大元素,重复执行步骤 ①
    选择排序的交换次数要远远少于冒泡排序,平均性能优于冒泡排序
    最好、最坏、平均时间复杂度: O(n^ 2 ) ,空间复杂度: O(1) ,属于稳定排序
    Asserts.test(判断语句);//可以得到判断语句是否是正确的

    选择排序还是有优化的空间,使用堆的方式.

    选择排序的优化 - 堆数据结构

    堆排序是选择排序的一种优化
    执行流程
    ① 对序列进行原地建堆(heapify)
    ② 重复执行以下操作,直到堆的元素数量为 1
    交换堆顶元素与尾元素
    堆的元素数量减 1
    对 0 位置进行 1 次 siftDown 操作
    • 对下面的元素进行原地建堆

    • 将堆顶元素拿出来,放到最后面,并且进行一个下滤操作,操作如下所示

    •  重复上一步操作,直到元素0位置是最小的.
    •  最好、最坏、平均时间复杂度:O(nlogn),空间复杂度:O(1),属于不稳定排序

    原始的比较代码的写法

    1. protected void sort() {
    2. for(int end = array.length -1;end > 0;end--)
    3. {
    4. int maxIndex = array[0];
    5. for(int begin = 1;begin <= end;begin++)
    6. {
    7. if(array[maxIndex] <= array[begin]) //<=的原因是要使用稳定的排序算法
    8. {
    9. maxIndex = begin;
    10. }
    11. }
    12. int temp = array[maxIndex];
    13. array[maxIndex] = array[end];
    14. array[end] = temp;
    15. }
    16. }

    但是上述的代码在使用的过程中,交换和比较函数是可以进行提出来的,因此,可以直接进行一个类的外部声明,然后进行调用即可.

    父类Sort的写出

    1. import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
    2. import java.util.Arrays;
    3. public abstract class Sort implements Comparable{
    4. protected Integer[] array;
    5. protected int cmpCount;//进行一个比较的次数
    6. protected int swapCount;//进行交换的此时
    7. private long time;//记录执行时间
    8. public void sort(Integer[] array)
    9. {
    10. if(array == null || array.length < 2) return;
    11. this.array = array;
    12. long begin = System.currentTimeMillis();
    13. sort();
    14. time = System.currentTimeMillis() - begin;
    15. }
    16. protected abstract void sort();
    17. private String numberString(int number)
    18. {
    19. if(number < 10000) return number + " ";//注意这里是返回字符串,因此,必须要加一个" "
    20. if(number < 100000000) return number/10000.0 + "万";
    21. return number/100000000.0 + "亿";
    22. }
    23. /*
    24. 返回值等于0,代表array[i1] == array[i2]
    25. 返回值小于0,代表array[i1] < array[i2]
    26. 返回值大于0,代表array[i1] > array[i2]
    27. */
    28. protected int cmp(int i1,int i2)
    29. {
    30. cmpCount++;
    31. return array[i1] - array[i2];
    32. }
    33. protected int cmpElements(Integer v1,Integer v2)
    34. {
    35. cmpCount++;
    36. return v1 - v2;
    37. }
    38. protected void swap(int i1,int i2)
    39. {
    40. swapCount++;
    41. int tmp = array[i1];
    42. array[i1] = array[i2];
    43. array[i2] = tmp;
    44. }
    45. @Override
    46. public String toString() {
    47. return "Sort{" +
    48. "array=" + Arrays.toString(array) +
    49. ", cmpCount=" + cmpCount +
    50. ", swapCount=" + swapCount +
    51. ",Consume Time =" + time/1000.000 +
    52. '}';
    53. }
    54. public int compareTo(Sort o) {
    55. int result = (int)(time - o.time);
    56. if(result != 0) return result;
    57. result = cmpCount - o.cmpCount;
    58. if(result != 0) return result;
    59. return swapCount - o.swapCount;
    60. }
    61. }

    子类SelectionSort代码是如下所示:

    1. public class SelectionSort extends Sort{
    2. protected void sort() {
    3. for(int end = array.length -1;end > 0;end--)
    4. {
    5. int maxIndex = array[0];
    6. for(int begin = 1;begin <= end;begin++)
    7. {
    8. // if(array[maxIndex] <= array[begin]) //<=的原因是要使用稳定的排序算法
    9. if(cmp(maxIndex,begin) <= 0)
    10. {
    11. maxIndex = begin;
    12. }
    13. }
    14. // int temp = array[maxIndex];
    15. // array[maxIndex] = array[end];
    16. // array[end] = temp;
    17. swap(maxIndex,end);
    18. }
    19. }
    20. }

    前面的BubbuleSort也是可以进行一个改正的过程,比较好理解,懒得改了.

    堆排序HeapSort

    1. public class HeapSort extends Sort{
    2. //1.记录堆里面的数据
    3. private int heapSize;
    4. protected void sort() {
    5. //2.原地建堆 - 将array之中的元素变成一个堆
    6. heapSize = array.length;
    7. for(int i = (heapSize >>1) -1;i >= 0;i--)
    8. {
    9. siftDown(i);
    10. }
    11. //3.交换堆顶的元素与尾部的元素
    12. while(heapSize > 1)
    13. {
    14. swap(0,heapSize-1);
    15. //4.将堆之中的数据减少
    16. heapSize--;
    17. //5.对0位置进行siftDown(恢复堆的性质)
    18. siftDown(0);
    19. }
    20. }
    21. /**
    22. * 让index位置的元素下滤
    23. * @param index
    24. */
    25. private void siftDown(int index) {
    26. Integer element = array[index];
    27. int half = heapSize >> 1;
    28. // 第一个叶子节点的索引 == 非叶子节点的数量
    29. // index < 第一个叶子节点的索引
    30. // 必须保证index位置是非叶子节点
    31. while (index < half) {
    32. // index的节点有2种情况
    33. // 1.只有左子节点
    34. // 2.同时有左右子节点
    35. // 默认为左子节点跟它进行比较
    36. int childIndex = (index << 1) + 1;
    37. Integer child = array[childIndex];
    38. // 右子节点
    39. int rightIndex = childIndex + 1;
    40. // 选出左右子节点最大的那个
    41. if (rightIndex < heapSize && cmpElements(array[rightIndex], child) > 0) {
    42. child = array[childIndex = rightIndex];
    43. }
    44. if (cmpElements(element, child) >= 0) break;
    45. // 将子节点存放到index位置
    46. array[index] = child;
    47. // 重新设置index
    48. index = childIndex;
    49. }
    50. array[index] = element;
    51. }
    52. }

  • 相关阅读:
    开发中如何克服tomcat热部署弱的缺陷?看这篇文章就够了
    arco-design:列tsx语法,点击弹窗
    手摸手带你撸一个拖拽效果
    [附源码]java毕业设计基于学生信息管理系统
    如何清理ogg日志文件
    100种思维模型之错误记录思维模型-66
    Linux系统中查看NextJS程序的CPU、内存占用情况
    C++入门
    4.9 nodejs操作多种数据库
    基于java项目 服务器远程debug开启教程
  • 原文地址:https://blog.csdn.net/m0_47489229/article/details/126436203