• 7大排序


    一、直接插入排序

    往有序的数组中快速插入一个新的元素。

    基本思想:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止。

     1 // 插入排序
     2 public static void insertSort(int[] arr) {
     3     if(arr == null || arr.length <= 0)  {
     4         return;
     5     }
     6 
     7     for(int i = 1; i < arr.length; i++) {
     8         int tmp = arr[i];
     9         
    10         for(int j = i; j >= 0; j--) {
    11             if(j > 0 && arr[j-1] > tmp) {
    12                 arr[j] = arr[j-1];
    13             } else {
    14                 arr[j] = tmp;
    15                 break;
    16             }
    17         }
    18     }
    19 }

     二、希尔排序

    希尔排序又称递减增量排序算法。希尔排序是先把待排序的记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中基本有序时,再对全体记录进行直接插入排序。

    ①. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;(一般初次取数组半长,之后每次再减半,直到增量为1
    ②. 按增量序列个数k,对序列进行k 趟排序;
    ③. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

    希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。

    下面以数列{80,30,60,40,20,10,50,70}为例,演示它的希尔排序过程

    第1趟:(gap=4)

    当gap=4时,意味着将数列分为4个组: {80,20},{30,10},{60,50},{40,70}。 对应数列: {80,30,60,40,20,10,50,70}
    对这4个组分别进行排序,排序结果: {20,80},{10,30},{50,60},{40,70}。 对应数列: {20,10,50,40,80,30,60,70}

    第2趟:(gap=2)

    当gap=2时,意味着将数列分为2个组:{20,50,80,60}, {10,40,30,70}。 对应数列: {20,10,50,40,80,30,60,70}
    注意:{20,50,80,60}实际上有两个有序的数列{20,80}和{50,60}组成。
              {10,40,30,70}实际上有两个有序的数列{10,30}和{40,70}组成
    对这2个组分别进行排序,排序结果:{20,50,60,80}, {10,30,40,70}。 对应数列: {20,10,50,30,60,40,80,70}

    第3趟:(gap=1)

    当gap=1时,意味着将数列分为1个组:{20,10,50,30,60,40,80,70}
    注意:{20,10,50,30,60,40,80,70}实际上有两个有序的数列{20,50,60,80}和{10,30,40,70}组成。
    对这1个组分别进行排序,排序结果:{10,20,30,40,50,60,70,80}

     1 public static void shellSort(int[] arr){
     2     int gap = arr.length / 2;
     3 
     4     for (; gap > 0; gap /= 2) {      //不断缩小gap,直到1为止
     5 
     6         for (int j = 0; (j+gap) < arr.length; j++){     //使用当前gap进行组内插入排序
     7 
     8             for(int k = 0; (k+gap)< arr.length; k += gap){
     9                 if(arr[k] > arr[k+gap]) {
    10                     
    11                     int temp = arr[k+gap];      //交换操作
    12                     arr[k+gap] = arr[k];
    13                     arr[k] = temp;
    14                 }
    15             }
    16         }
    17     }
    18 }

    三、选择排序

    选择排序的基本思想:比较+交换

    ①、从待排序的序列中,找到关键字最小的元素;

    ②、如果最小的元素不是待排序序列的第一个元素,将其和第一个元素交换;

    ③、从余下的N-1个元素中,找出关键字最小的元素,重复1和2,直到排序结束

     1 // 选择排序
     2 public static void selectionSort(int[] arr) {
     3     if(arr == null || arr.length < 0) {
     4         return;
     5     }
     6 
     7     for(int i = 0; i < arr.length; i++) {
     8         int tmp = arr[i]; // 当前最小值
     9         int flag = i;
    10 
    11         for(int j = i + 1; j < arr.length; j++) {
    12             if(arr[j] < tmp) {
    13                 tmp = arr[j];
    14                 flag = j;
    15             }
    16         }
    17 
    18         if(flag != i) {
    19             arr[flag] = arr[i];
    20             arr[i] = tmp;
    21         }
    22     }
    23 }

    四、堆排序

    堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

    步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

      a.假设给定无序序列结构如下

    2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

    4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

    这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

    此时,我们就将一个无需序列构造成了一个大顶堆。

    步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

    a.将堆顶元素9和末尾元素4进行交换

    b.重新调整结构,使其继续满足堆定义

    c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

    后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

    再简单总结下堆排序的基本思路:

      a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

      b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

      c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

     1 public class Solution {
     2 
     3     public static void main(String[] args) {
     4         int[] arr = {9,8,7,6,5,4,3,2,1};
     5         heapSort(arr);
     6 
     7         System.out.println(Arrays.toString(arr));
     8     }
     9 
    10 
    11     public static void heapSort(int[] arr) {
    12         // 构建最大堆
    13         for(int i = arr.length/2 - 1; i >= 0; i--) {
    14             // 从第一个非叶子节点从下到上,从右到左调整
    15             adjustHeap(arr, i, arr.length);
    16         }
    17 
    18         for(int j = arr.length-1; j > 0; j--) {
    19             // 将堆顶元素和末尾元素交换
    20             swap(arr, 0, j);
    21             // 重新对堆进行调整
    22             adjustHeap(arr, 0, j);
    23         }
    24     }
    25 
    26     public static void adjustHeap(int[] arr, int i, int length) {
    27         int tmp = arr[i]; // 取出当前元素
    28         
    29         for(int k = i*2+1; k < length; k = 2*k+1) {
    30             if(k+1 < length && less(arr, k, k+1)) {
    31                 k++;
    32             }
    33 
    34             //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
    35             if(arr[k] > tmp) {
    36                 arr[i] = arr[k];
    37                 i = k;
    38             } else {
    39                 break;
    40             }
    41         }
    42 
    43         arr[i] = tmp; // 当tmp值放到最终的位置
    44     }
    45 
    46     private static void less(int[] arr, int i, int j) {
    47         if(arr[i] < arr[j]) {
    48             return true;
    49         } else {
    50             return false;
    51         }
    52     }
    53 
    54 }

    五、快速排序

    基本思想:挖坑填数+分治法

    ①. 从数列中挑出一个元素,称为”基准”(pivot)。
    ②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
    ③. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

     1 public static void quickSort(int[] arr, int low, int high) {
     2     if(arr == null || arr.length <= 0) {
     3         return;
     4     }
     5 
     6     int left = low;
     7     int right = high;
     8     int tmp = arr[left];
     9 
    10     while(left < right) {
    11         while(left < right && arr[right] >= tmp) {
    12             right--;
    13         }
    14 
    15         arr[left] = arr[right];
    16 
    17         while(left < right && arr[left] <= tmp) {
    18             left++;
    19         }
    20         
    21         arr[right] = arr[left];
    22     }
    23 
    24     arr[left] = tmp;
    25     quickSort(arr, low, left-1);
    26     quickSort(arr, left+1, high);
    27 }

    六、归并排序

    基本思想:归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

     1 public class Solution {
     2     public static void mergeSort(int[] nums) {
     3         int[] tmp = new int[nums.length];
     4 
     5         mergeSort(nums, tmp, 0, nums.length-1);
     6     }
     7 
     8     private static void mergeSort(int[] nums, int[] tmp, int left, int right) {
     9         if(left < right) {
    10             int mid = (left + right) >> 1;
    11             mergeSort(nums, tmp, left, mid);
    12             mergeSort(nums, tmp, mid+1, right);
    13             merge(nums, tmp, left, mid+1, right);
    14         }
    15     }
    16 
    17     private static void merge(int[] nums, int[] tmp, int leftPos, int rightPos, int rightEnd) {
    18         int leftEnd = rightPos-1;
    19         int tmpPos = leftPos;
    20         int numElements = rightEnd-leftPos+1;
    21 
    22         while(leftPos <= leftEnd && rightPos <= rightEnd) {
    23             if(nums[leftPos] <= nums[rightPos]) {
    24                 tmp[tmpPos++] = nums[leftPos++];
    25             } else {
    26                 tmp[tmpPos++] = nums[rightPos++];
    27             }
    28         }
    29 
    30         while(leftPos <= leftEnd) {
    31             tmp[tmpPos++] = nums[leftPos++];
    32         }
    33 
    34         while(rightPos <= rightEnd) {
    35             tmp[tmpPos++] = nums[rightPos++];
    36         }
    37 
    38         for(int i = 0; i < numElements; i++, rightEnd--) {
    39             nums[rightEnd] = tmp[rightEnd];
    40         }
    41     }
    42 }

    七、基数排序

    它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

    基数排序按照优先从高位或低位来排序有两种实现方案:

    • MSD(Most significant digital) 从最左侧高位开始进行排序。先按k1排序分组, 同一组中记录, 关键码k1相等, 再对各组按k2排序分成子组, 之后, 对后面的关键码继续这样的排序分组, 直到按最次位关键码kd对各子组排序后. 再将各组连接起来, 便得到一个有序序列。MSD方式适用于位数多的序列

    • LSD (Least significant digital)从最右侧低位开始进行排序。先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。LSD方式适用于位数少的序列

     1 public static void radixSort(int[] arr){
     2     if(arr.length <= 1) return;
     3 
     4     //取得数组中的最大数,并取得位数
     5     int max = 0;
     6     for(int i = 0; i < arr.length; i++){
     7         if(max < arr[i]){
     8             max = arr[i];
     9         }
    10     }
    11 
    12     // 最大数的位数
    13     int maxDigit = 1;
    14     while(max / 10 > 0){
    15         maxDigit++;
    16         max = max / 10;
    17     }
    18 
    19     //申请一个桶空间
    20     int[][] buckets = new int[10][arr.length];
    21 
    22     int base = 10;
    23 
    24     //从低位到高位,对每一位遍历,将所有元素分配到桶中
    25     for(int i = 0; i < maxDigit; i++){
    26 
    27         int[] bktLen = new int[10];//存储各个桶中存储元素的数量
    28         
    29         //分配:将所有元素分配到桶中
    30         for(int j = 0; j < arr.length; j++){
    31             int whichBucket = (arr[j] % base) / (base / 10);
    32             buckets[whichBucket][bktLen[whichBucket]] = arr[j];
    33             bktLen[whichBucket]++;
    34         }
    35 
    36         //收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞
    37         int k = 0;
    38         for(int b = 0; b < buckets.length; b++){
    39 
    40             for(int p = 0; p < bktLen[b]; p++){
    41                 // important here
    42                 arr[k++] = buckets[b][p];
    43             }
    44         }
    45 
    46         base *= 10;
    47     }
    48 }

    参考:

    https://www.cnblogs.com/skywang12345/p/3597597.html

    https://blog.csdn.net/hguisu/article/details/7776068

    https://www.cnblogs.com/chengxiao/p/6129630.html

  • 相关阅读:
    不舍
    java 笔记
    Javascript 行为委托
    JavaScript 函数调用的 this词法
    Javascript 闭包
    Javascript 原型链
    理解css的BFC
    多模态检索之CCA算法
    MySQL 基础概念、基础配置、密码破解
    Python的进程和线程
  • 原文地址:https://www.cnblogs.com/wylwyl/p/10497509.html
Copyright © 2020-2023  润新知