zoukankan      html  css  js  c++  java
  • Java数据结构(七)—— 排序算法

    排序算法(Sort Algorithm)

    排序算法介绍和分类

    将一组数据,依指定顺序进行排列

    排序的分类

    1. 内部排序

      指将需要处理的所有数据都加载到内部存储器中进行排序

    2. 外部排序

      数据量过大,无法全部加载到内存中,需借助外部存储进行排序

    常见的排序算法

    image-20201027123631072

    冒泡排序(Bubble Sort)

    基本思想

    通过对待排序序列从前向后(从下表较小的元素 开始),依次比较相邻元素的值,若发现逆序,交换相邻元素的值

    基本代码

    public static void bubbleSorting(int[] arr){
       for (int i = 0; i < arr.length-1; i++) {
           for (int j = 0; j < arr.length-i-1; j++) {
               if (arr[j]>arr[j+1]){
                   int temp = arr[j];
                   arr[j] = arr[j+1];
                   arr[j+1] = temp;
              }
          }
      }
       for (int i = 0; i < arr.length; i++) {
           System.out.println(arr[i]);
      }
    }

    优化

    因为排序过程 中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,,因此要在排序过程中设置一个标志flag判断元素是否交换,从而减少不必要的比较

    优化后代码

    public static void bubbleSorting(int[] arr){
           int temp = 0;
           //标志变量,表示是否进行过交换
           boolean flag = false;
           for (int i = 0; i < arr.length-1; i++) {
               for (int j = 0; j < arr.length-i-1; j++) {
                   if (arr[j]>arr[j+1]){
                       flag = true;
                       temp = arr[j];
                       arr[j] = arr[j+1];
                       arr[j+1] = temp;
                  }
              }
               if (flag = false){//在一趟排序中一次都没有交换
                   break;
              }else {
                   flag = false;//重置flag进行下次判断
              }
          }
    //       for (int i = 0; i < arr.length; i++) {
    //           System.out.print(arr[i]);
    //       }
    //       System.out.println();
      }

    选择排序(Select Sort)

    基本思想

    第一次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换,然后一次从后面的数据选取最小值,与数组前的值交换

    public static void selectSorting(int[] arr){
       for (int i = 0; i < arr.length-1; i++) {
           int temp = arr[i];
           for (int j = i; j < arr.length; j++) {
               if (arr[i]>arr[j]){//从小到大排序
                   temp = arr[j];
                   arr[j] = arr[i];
                   arr[i] = temp;
              }
          }
      }
       for (int i = 0; i < arr.length; i++) {
           System.out.print(arr[i]);
      }
       System.out.println();
    }

    插入排序(Insert Sort)

    基本思想

    插入排序属于内部排序,是对于排序的元素以插入的方式 找寻该元素的适当位置,以达到排序的目的

    把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序吗一次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,是指成为新的有序表。

    代码实现

    public static void insertionSorting(int[] arr){
       int insert = arr[0];//第一个插入的数,直接插入
       int insertIndex = 0;//arr[1]起那面这个数的下标
       arr[insertIndex] = insert;
       for (int i = 1; i < arr.length-1; i++) {
           //insertIndex >= 0,保证给insert找插入位置时不越界
           //insert < arr[insertIndex,待插入的数即arr[insertIndex]后移
           insert = arr[i];
           insertIndex = i - 1;
           while (insertIndex >= 0 && insert < arr[insertIndex]){
               arr[insertIndex+1] = arr[insertIndex];
               insertIndex--;
          }
           arr[insertIndex+1] = insert;
      }
       System.out.println(Arrays.toString(arr));
    }

    问题

    • 若数据过于小,后移的次数较多影响效率

    希尔排序(Shell Sort)

    • 希尔排序是简单排序改进后更高效的排序

    • 也称缩小增量排序

    基本思想

    • 把记录按下标的一定增量分组

    • 对每组使用直接插入排序算法

    • 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止

    交换式算法实现

    /**
    * 交换法,实现希尔排序,效率低
    * @param arr
    */
    public static void shellSortExchange(int[] arr){

       //将数据根据数组长度进行分组
       int len = (int) Math.floor(arr.length/2);
       int temp = 0;
       while (len>=1){
           for (int i = len; i < arr.length; i++) {
               //遍历各组所有的元素,(共有len组,每组2个元素),步长len
               for (int j = i-len; j >= 0; j -= len) {
                   //从小到大排序
                   //如果当前元素大于加上步长的元素,需要交换
                   if (arr[j] >  arr[j+len]){
                       temp = arr[j];
                       arr[j] = arr[j+len];
                       arr[j+len] = temp;
                  }
              }
          }
           len = (int) Math.floor(len/2);
      }
       System.out.println(Arrays.toString(arr));
    }

    移位式算法实现

    /**
    * 移位式,效率高
    * @param arr
    */
    public static void shellSortShift(int[] arr){
       int len = (int) Math.floor(arr.length/2);
       int temp = 0;
       //增量len,并逐步缩小增量
       while (len >= 1){
           //从第len个元素,诸葛对其所在的组进行直接插入排序
           for (int i = len; i < arr.length; i++) {
               int j = i;
               temp = arr[j];
               if (arr[j]<arr[j-len]){
                   while (j-len >= 0 && temp < arr[j-len]){
                       //移动
                       arr[j] = arr[j-len];
                       j -= len;
                  }
                   //退出循环后找到了插入的位置
                   arr[j] = temp;
              }
          }
           len = (int) Math.floor(len/2);
      }
       System.out.println(Arrays.toString(arr));
    }

    快速排序(Quick Sort)

    快速排序是对冒泡排序的一种改进

    基本思想

    • 通过一趟排序将要排序的数据分割为独立的两部分

    • 一部分所有的数据都比另一部分所有的数据小

    • 按上述方法对两部分数据进行快速排序

    • 整个排序过程可以递归进行,达到整个数据编程有序序列

    找一个基准,将数据序列分成两部分

    代码实现

    /**
    * 快速排序
    * @param arr
    * @param left
    * @param right
    */
    public static void quickSort(int[] arr,int left,int right){
       int l = left;//左下标
       int r = right;//右下标
       int pivot = arr[(left+right)/2];//基准数
       int temp = 0;//临时变量

       //左边索引大于等于右边索引时结束循环
       //将比pivot小的数据放到左边,大的放到右边
       while (l < r){
           //在pivot左边找到大于或等于pivot的值,才退出
           while (arr[l] < pivot){
               l += 1;
          }
           //在pivot右边找到小于或等于pivot的值,才退出
           while (arr[r] > pivot){
               r -= 1;
          }
           //l >= r,pivot左右两边的值,已按照左边全部是小于等于pivot的值
           //右边大于等于pivot的值
           if (l >= r){
               break;
          }
           //交换
           temp = arr[l];
           arr[l] = arr[r];
           arr[r] = temp;
           //如果交换完后发现arr[l] == pivot,则r--,前移一步
           if (arr[l] == pivot){
               r -= 1;
          }
           //如果交换完后发现arr[r] == pivot,则l++,后移一步
           if (arr[r] == pivot){
               l += 1;
          }
      }
       //如果l == r,必须l++,r--,否则会出现栈溢出
       if (l == r){
           l++;
           r--;
      }
       //向左递归
       if (left < r){
           quickSort(arr,left,r);
      }
       //向右递归
       if(right > l){
           quickSort(arr,l,right);
      }
    }

    归并排序(Merge Sort)

    归并排序是利用归并的思想实现的排序方法,采用分治的策略。

    基本思想

    image-20201031143431457

    最后一次合并

    image-20201031143741078

    代码实现

    /**
    * 归并排序
    * @param arr
    * @param left
    * @param right
    * @param temp
    */
    public static void mergeSort(int[] arr,int left,int right,int[] temp){
       if (left < right){
           int mid = (left + right) / 2;
           //向左递归进行分解
           mergeSort(arr,left,mid,temp);
           //向右递归进行分解
           mergeSort(arr,mid+1,right,temp);

           //到合并
           merge(arr,left,mid,right,temp);
      }
    }
    /**
    * 归并排序合并的方法
    * @param arr   待排序的数组
    * @param left 左边有序序列的初始索引
    * @param mid   中间索引
    * @param right 右边索引
    * @param temp 中转临时数组
    */
    public static void merge(int[] arr,int left,int mid,int right,int[] temp){
       //初始化i,j
       int i = left; //左边序列的初始索引
       int j = mid + 1; //右边有序序列的初始索引
       int t = 0; //temp的索引

       //(一)
       //先把左右两边(有序)的数据按规则填充到temp数组
       //直到左右两边有一边的有序序列处理完毕
       while (i <= mid && j <= right){
           //左边有序序列的当前元素小于或等于右边有序序列的当前元素
           if (arr[i] <= arr[j]){
               //将左边当前数据加入到temp中
               // 并且将temp索引t和右边索引i,分别+=1
               temp[t] = arr[i];
               t += 1;
               i += 1;
          }else {//左边有序序列的当前元素大于右边有序序列的当前元素
               //将右边当前数据加入到temp中
               // 并且将temp索引t和右边索引j,分别+=1
               temp[t] = arr[j];
               t += 1;
               j += 1;
          }
      }

       //(二)
       //把有剩余的一边的数据一次全部填充到temp
       while (i <= mid){//左边还有剩余元素,全部填充到temp
           temp[t] = arr[i];
           t +=  1;
           i += 1;
      }
       while (j <= right){//右边还有剩余元素,全部填充到temp
           temp[t] = arr[j];
           t +=  1;
           j += 1;
      }

       //(三)
       //将temp数组的元素拷贝到arr
       //注意并不是每次都拷贝所有数据
       t = 0;
       int tempLeft = left;
       System.out.println("tempLeft = "+tempLeft+" , "+right);
       while (tempLeft <= right){
           arr[tempLeft] = temp[t];
           t += 1;
           tempLeft += 1;
      }
    }

    基数排序(Radix Sort)【桶排序】

    基本介绍

    • 基数排序属于分配式排序,通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用

    • 基数排序法是属于稳定性的排序,基数排序法是效率高的稳定性排序法

    • 基数排序是桶排序的扩展

    • 是1887年赫尔曼·何乐礼发明的,实现方法:将整数按位数切割成不同的数字,然后按每个位数分别比较

    基本思想

    • 将所有待比较的数值统一为同样的位数长度,数位较短的数前面补0

    • 然后从最低位开始,依次进行一次排序。

    • 这样从最低位排序一直到最高位排序完成后,数列就变成一个有序序列

    第一轮示意图

    按个位

    image-20201031154755329

    后面按照十位,百位......依次排序

    代码实现

    /**
    * 基数排序
    * @param arr
    */
    public static void radixSort(int[] arr){
       //定义一个二维数组表示10个桶
       //二维数组包含10个一维数组
       //为防止溢出,每隔一维数组大小只能定为arr.length
       //空间换时间
       int[][] bucket = new int[10][arr.length];

       //为记录每个桶中实际存放了多少个数据
       // 定义一个一维数组记录各桶每次放入的数据个数
       int[] bucketElementCount = new int[10];

       //得到数组中最大数
       int max = arr[0];
       for (int i = 1; i < arr.length; i++) {
           if (arr[i] > max){
               max = arr[i];
          }
      }
       //得到最大数的位数
       int maxLength = (max + "").length();

       for (int i = 0,n = 1; i < maxLength; i++,n *= 10) {
           for (int j = 0; j < arr.length; j++) {
               //取出每个元素的位,第一次是个位,第二次是是十位...
               int digitOfElement = arr[j] / n % 10;
               //放入到对应的桶中
               bucket[digitOfElement][bucketElementCount[digitOfElement]] = arr[j];
               bucketElementCount[digitOfElement]++;
          }

           //依照桶中的顺序放入原数组
           int index = 0;
           for (int j = 0; j < bucketElementCount.length; j++) {
               //如果桶中有数据,才放入到原数组
               if (bucketElementCount[j] != 0){//对应的桶中有数据
                   //循环第i个桶,即dii个一维数组
                   for (int k = 0; k < bucketElementCount[j]; k++) {
                       //取出元素放入到arr中
                       arr[index] = bucket[j][k];
                       index++;
                  }
              }
               //每一轮处理后需要将bucketElementCount[i]置零(各桶中数据个数)
               bucketElementCount[j] = 0;
          }
      }
       System.out.println(Arrays.toString(arr));
    }

    基数排序说明

    1. 基数排序是对传统桶排序的扩展,速度很快

    2. 基数排序是经典的空间换时间的方式,占用内存很大,容易造成OutOfMemoryError

    3. 基数排序是稳定的

    4. 稳定:若序列中存在多个相同的数据,排序后这些数据的前后顺序不变,则排序算法稳定

    内排序和外排序

    • 内排序:所有排序在内存中完成

    • 外排序:由于数据量较大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行

    稳定:排序后相等的元素前后位置不变

    在位:排序时不借助外部空间

     

    所有源码都可在gitee仓库中下载:https://gitee.com/vvwhyyy/java_algorithm

  • 相关阅读:
    进程池,线程池,协程,gevent模块,协程实现单线程服务端与多线程客户端通信,IO模型
    线程相关 GIL queue event 死锁与递归锁 信号量l
    生产者消费者模型 线程相关
    进程的开启方式 进程的join方法 进程间的内存隔离 其他相关方法 守护进程 互斥锁
    udp协议 及相关 利用tcp上传文件 socketserver服务
    socket套接字 tcp协议下的粘包处理
    常用模块的完善 random shutil shevle 三流 logging
    day 29 元类
    Django入门
    MySQL多表查询
  • 原文地址:https://www.cnblogs.com/whystudyjava/p/14043366.html
Copyright © 2011-2022 走看看