zoukankan      html  css  js  c++  java
  • java实现各种排序算法1

    准备

    public interface Sort {
    
      void sort(int[] nums);
    }
    

    定义所有排序方式通用接口

    冒泡排序

    两两比较,将最大(最小值)移动到最右边。

    第一版

    public class BubbleSort implements Sort {
    
      @Override
      public void sort(int[] nums) {
        //[i,len)为已排序
        for (int i = nums.length - 1; i > 0; i--) {
          for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
              swap(nums, j, j + 1);
            }
          }
        }
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    }
    

    第二版

    相对于第一版,在已经有序的情况下直接结束,后面不用比较了。

    public class BubbleSort2 implements Sort {
    
      @Override
      public void sort(int[] nums) {
        //[i,len)为已排序
        for (int i = nums.length - 1; i > 0; i--) {
          boolean sorted = true;
          for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
              swap(nums, j, j + 1);
              sorted = false;
            }
          }
          if (sorted) {
            break;
          }
        }
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    }
    

    第三版

    和第二版类似,记录最后一次的交换位置,交换位置之后的都可以算作排好序了。

    public class BubbleSort3 implements Sort {
    
      @Override
      public void sort(int[] nums) {
        //[i,len)为已排序
        for (int i = nums.length - 1; i > 0; ) {
          //记录最后一次交换的位置
          int lastSwappedIndex = 0;
          for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
              swap(nums, j, j + 1);
              lastSwappedIndex = j + 1;
            }
          }
          i = lastSwappedIndex;
        }
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    }
    

    选择排序

    每次找到待排序数组中最小或最大的元素放到已排序数组中

    红色表示已排序

    public class SelectSort implements Sort {
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    
      @Override
      public void sort(int[] nums) {
        //[0,i)为已排序的,[i,n)为未排序的
        for (int i = 0; i < nums.length; i++) {
          int minIndex = i;
          for (int j = minIndex; j < nums.length; j++) {
            if (nums[j] < nums[minIndex]) {
              minIndex = j;
            }
          }
          swap(nums, i, minIndex);
        }
      }
    }
    

    插入排序

    每次将待排序元素插入到已排序数组中适当的位置

    public class InsertSort implements Sort {
    
      @Override
      public void sort(int[] nums) {
        //[0,i+1)为已排序的,[i+1,n)为未排序的
        for (int i = 0; i < nums.length - 1; i++) {
          int insertValue = nums[i + 1];
          int j = i;
          while (j >= 0 && insertValue < nums[j]) {
            nums[j + 1] = nums[j];
            j--;
          }
          nums[j + 1] = insertValue;
        }
      }
    }
    

    插入排序在数组元素接近有序的情况下有更好的性能。

    希尔排序

    希尔排序可以看做增强版的插入排序,每次对不同间隔的数组区间进行插入排序。

    第一版

    public class ShellSort implements Sort {
    
      @Override
      public void sort(int[] nums) {
        int h = 1;
        while (h < nums.length) {
          h = h * 3 + 1;
        }
        while (h > 0) {
          //对len/h个小数组排序
          for (int start = 0; start < h; start++) {
            //对[start,start+h,start+2h...]排序
            for (int i = start + h; i < nums.length; i += h) {
              int insertValue = nums[i];
              int j = i - h;
              while (j >= 0 && insertValue < nums[j]) {
                nums[j + h] = nums[j];
                j -= h;
              }
              nums[j + h] = insertValue;
            }
          }
          h /= 3;
        }
    
      }
    }
    

    第二版

    将4个循环优化成3个循环

    public class ShellSort2 implements Sort {
    
      @Override
      public void sort(int[] nums) {
        int h = 1;
        while (h < nums.length) {
          h = h * 3 + 1;
        }
        while (h > 0) {
          //对[h,len]排序
          for (int i = h; i < nums.length; i++) {
            int insertValue = nums[i];
            int j = i - h;
            while (j >= 0 && insertValue < nums[j]) {
              nums[j + h] = nums[j];
              j -= h;
            }
            nums[j + h] = insertValue;
          }
          h /= 3;
        }
    
      }
    }
    

    归并排序

    自顶向下递归实现

    public class MergeSort implements Sort {
    
      @Override
      public void sort(int[] nums) {
        int[] temp = new int[nums.length];
        mergeSort(nums, temp, 0, nums.length - 1);
      }
    
      private void mergeSort(int[] nums, int[] temp, int left, int right) {
        if (left >= right) {
          return;
        }
        int middle = left + ((right - left) >> 1);
        mergeSort(nums, temp, left, middle);
        mergeSort(nums, temp, middle + 1, right);
        //如果已经有序,不merge
        if (nums[middle] > nums[middle + 1]) {
          merge(nums, temp, left, middle, right);
        }
      }
    
      //数组[left,middle]和[middle+1,right]都是已排序的,合并两个区间
      private void merge(int[] nums, int[] temp, int left, int middle, int right) {
        int leftIndex = left;
        int rightIndex = middle + 1;
        int tempIndex = 0;
        while (leftIndex <= middle && rightIndex <= right) {
          if (nums[leftIndex] <= nums[rightIndex]) {
            temp[tempIndex++] = nums[leftIndex++];
          } else {
            temp[tempIndex++] = nums[rightIndex++];
          }
        }
        while (leftIndex <= middle) {
          temp[tempIndex++] = nums[leftIndex++];
        }
        while (rightIndex <= right) {
          temp[tempIndex++] = nums[rightIndex++];
        }
        System.arraycopy(temp, 0, nums, left, right - left + 1);
      }
    }
    

    自底向上循环实现

    public class MergeSort2 implements Sort {
    
      //数组[left,middle]和[middle+1,right]都是已排序的,合并两个区间
      private void merge(int[] nums, int[] temp, int left, int middle, int right) {
        int leftIndex = left;
        int rightIndex = middle + 1;
        int tempIndex = 0;
        while (leftIndex <= middle && rightIndex <= right) {
          if (nums[leftIndex] <= nums[rightIndex]) {
            temp[tempIndex++] = nums[leftIndex++];
          } else {
            temp[tempIndex++] = nums[rightIndex++];
          }
        }
        while (leftIndex <= middle) {
          temp[tempIndex++] = nums[leftIndex++];
        }
        while (rightIndex <= right) {
          temp[tempIndex++] = nums[rightIndex++];
        }
        System.arraycopy(temp, 0, nums, left, right - left + 1);
      }
    
      @Override
      public void sort(int[] nums) {
        int len = nums.length;
        int[] temp = new int[len];
        for (int sz = 1; sz < len; sz += sz) {
          for (int i = 0; i < len - sz; i += sz + sz) {
            int middle = i + sz - 1;
            int right = Math.min(i + sz + sz - 1, len - 1);
            if (nums[middle] > nums[middle + 1]) {
              merge(nums, temp, i, middle, right);
            }
          }
        }
      }
    }
    

    归并排序的一个应用

    剑指 Offer 51. 数组中的逆序对

    快速排序

    单路快排

    上图为分割的过程,基准值为左边第一个7,返回的分割点为索引下标4

    public class QuickSort implements Sort {
    
      private void quickSort(int[] nums, int left, int right) {
        if (left >= right) {
          return;
        }
        int p = partition(nums, left, right);
        quickSort(nums, left, p - 1);
        quickSort(nums, p + 1, right);
      }
    
      private int partition(int[] nums, int left, int right) {
        int pivot = nums[left];
        int leftIndex = left;
        int rightIndex = left + 1;
        //[left+1,leftIndex] < pivot [leftIndex+1,rightIndex] >= pivot
        while (rightIndex <= right) {
          if (nums[rightIndex] < pivot) {
            leftIndex++;
            swap(nums, leftIndex, rightIndex);
          }
          rightIndex++;
        }
        swap(nums, left, leftIndex);
        return leftIndex;
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    
      @Override
      public void sort(int[] nums) {
        quickSort(nums, 0, nums.length - 1);
      }
    }
    

    第一种方式在数据已经有序的情况下会退化成O(n^2)的时间复杂度。我们可以通过随机选取基准值来优化

    import java.util.Random;
    
    public class QuickSort1 implements Sort {
    
      private void quickSort(int[] nums, int left, int right, Random random) {
        if (left >= right) {
          return;
        }
        int p = partition(nums, left, right, random);
        quickSort(nums, left, p - 1, random);
        quickSort(nums, p + 1, right, random);
      }
    
      private int partition(int[] nums, int left, int right, Random random) {
        int pivotIndex = random.nextInt(right - left + 1) + left;
        swap(nums, pivotIndex, left);
        int pivot = nums[left];
        int leftIndex = left;
        int rightIndex = left;
        //[left+1,leftIndex] < pivot [leftIndex+1,rightIndex] >= pivot
        while (rightIndex <= right) {
          if (nums[rightIndex] < pivot) {
            leftIndex++;
            swap(nums, leftIndex, rightIndex);
          }
          rightIndex++;
        }
        swap(nums, left, leftIndex);
        return leftIndex;
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    
      @Override
      public void sort(int[] nums) {
        Random random = new Random();
        quickSort(nums, 0, nums.length - 1, random);
      }
    }
    

    上面这种实现在数据都相同的情况下还是会退化成O(n^2),我们可以使用双路快排优化。

    双路快排

    import java.util.Random;
    
    public class QuickSort2 implements Sort {
    
      private void quickSort(int[] nums, int left, int right, Random random) {
        if (left >= right) {
          return;
        }
        int p = partition(nums, left, right, random);
        quickSort(nums, left, p - 1, random);
        quickSort(nums, p + 1, right, random);
      }
    
      private int partition(int[] nums, int left, int right, Random random) {
        int pivotIndex = random.nextInt(right - left + 1) + left;
        swap(nums, pivotIndex, left);
        int pivot = nums[left];
        int leftIndex = left + 1;
        int rightIndex = right;
        //[left+1,leftIndex-1] <= pivot [rightIndex+1,right] >= pivot
        while (true) {
          while (leftIndex <= rightIndex && nums[leftIndex] < pivot) {
            leftIndex++;
          }
          while (leftIndex <= rightIndex && nums[rightIndex] > pivot) {
            rightIndex--;
          }
          if (leftIndex >= rightIndex) {
            break;
          }
          swap(nums, leftIndex, rightIndex);
          leftIndex++;
          rightIndex--;
        }
        swap(nums, left, rightIndex);
        return rightIndex;
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    
      @Override
      public void sort(int[] nums) {
        Random random = new Random();
        quickSort(nums, 0, nums.length - 1, random);
      }
    }
    

    从左边找到大于等于基准值的索引,右边找到小于等于基准值的索引,交换,循环这个过程直到数组结束。双路快排在随机数组,有序数组,元素相同的数组中都会有很好地性能。

    三路快排

    import java.util.Random;
    
    public class QuickSort3 implements Sort {
    
      private void quickSort(int[] nums, int left, int right, Random random) {
        if (left >= right) {
          return;
        }
        int[] p = partition(nums, left, right, random);
        quickSort(nums, left, p[0], random);
        quickSort(nums, p[1], right, random);
      }
    
      private int[] partition(int[] nums, int left, int right, Random random) {
        int pivotIndex = random.nextInt(right - left + 1) + left;
        swap(nums, pivotIndex, left);
        int pivot = nums[left];
        int leftIndex = left;
        int rightIndex = right + 1;
        int cur = left + 1;
        //[left+1,leftIndex]<pivot  [leftIndex+1,cur-1]=pivot [rightIndex,right]>pivot
        while (cur < rightIndex) {
          if (nums[cur] < pivot) {
            leftIndex++;
            swap(nums, cur, leftIndex);
            cur++;
          } else if (nums[cur] > pivot) {
            rightIndex--;
            swap(nums, cur, rightIndex);
          } else {
            cur++;
          }
        }
        swap(nums, left, leftIndex);
        //[left,leftIndex-1]<pivot  [leftIndex,rightIndex-1]=pivot [rightIndex,right]>pivot
        return new int[]{leftIndex - 1, rightIndex};
      }
    
      private void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
      }
    
      @Override
      public void sort(int[] nums) {
        Random random = new Random();
        quickSort(nums, 0, nums.length - 1, random);
      }
    }
    

    三路快排将数组分成3部分,小于基准值的,等于基准值的,大于基准值的,下一次递归时直接从小于和大于开始,相比双路快排,在数组中包含大量相同元素时有更好的性能。如果元素全部相同,时间复杂度就是O(n)。

    三路快排的一个应用

    [LeetCode]75. 颜色分类

    分割思想的一个应用

    [Leetcode]215. 数组中的第K个最大元素

    堆排序

    实现堆这种数据结构,更多关于堆请看 java实现堆数据结构

    /**
     * 实现一个最大堆
     */
    public class MaxHeap<E extends Comparable<E>> {
    
      private List<E> delegate;
    
      public MaxHeap() {
        delegate = new ArrayList<>();
      }
    
      public MaxHeap(E[] source) {
        delegate = new ArrayList<>(Arrays.asList(source));
        heapify();
      }
    
      /**
       * 添加元素
       */
      public void add(E e) {
        delegate.add(e);
        siftUp(size() - 1, e);
      }
    
      /**
       * 查看最大值元素
       */
      public E peek() {
        rangeCheck();
        return delegate.get(0);
      }
    
      /**
       * 删除最大值元素
       */
      public E poll() {
        rangeCheck();
        swap(0, size() - 1);
        E removeEle = delegate.remove(size() - 1);
        siftDown(0);
        return removeEle;
      }
    
      /**
       * 使用新元素替换最大值
       */
      public E replace(E e) {
        rangeCheck();
        E oldEle = delegate.get(0);
        delegate.set(0, e);
        siftDown(0);
        return oldEle;
      }
    
      /**
       * 将非堆的结构转换成堆结构
       */
      private void heapify() {
        int size = parent(size() - 1);
        for (int i = size; i >= 0; i--) {
          siftDown(i);
        }
      }
    
      /**
       * 堆是否为空
       */
      public boolean isEmpty() {
        return delegate.isEmpty();
      }
    
      /**
       * 堆容量
       */
      public int size() {
        return delegate.size();
      }
    
      @Override
      public String toString() {
        return delegate.toString();
      }
    
      private void siftUp(int index, E e) {
        int cur = index;
        while (cur > 0) {
          int parentIndex = parent(cur);
          E childEle = delegate.get(cur);
          E parentEle = delegate.get(parentIndex);
          //当前节点大于父节点才交换
          if (childEle.compareTo(parentEle) <= 0) {
            break;
          }
          swap(cur, parentIndex);
          cur = parentIndex;
        }
      }
    
      private void siftDown(int index) {
        int size = size();
        int cur = index;
        while (true) {
          int leftIndex = leftChild(cur);
          //没有左孩子
          if (leftIndex >= size) {
            break;
          }
          int rightIndex = rightChild(cur);
          E curEle = delegate.get(cur);
          E maxChild = delegate.get(leftIndex);
          int maxChildIndex = leftIndex;
          //存在右孩子且右孩子大于左孩子
          if (rightIndex < size) {
            E rightEle = delegate.get(rightIndex);
            if (rightEle.compareTo(maxChild) > 0) {
              maxChildIndex = rightIndex;
              maxChild = rightEle;
            }
          }
          if (maxChild.compareTo(curEle) <= 0) {
            break;
          }
          //将当前节点和左右孩子中的最大节点交换
          swap(cur, maxChildIndex);
          cur = maxChildIndex;
        }
      }
    
      private void rangeCheck() {
        if (isEmpty()) {
          throw new IllegalArgumentException("heap is empty.");
        }
      }
    
      private void swap(int left, int right) {
        E temp = delegate.get(left);
        delegate.set(left, delegate.get(right));
        delegate.set(right, temp);
      }
    
      private int parent(int index) {
        return (index - 1) / 2;
      }
    
      private int leftChild(int index) {
        return index * 2 + 1;
      }
    
      private int rightChild(int index) {
        return index * 2 + 2;
      }
    }
    

    非原地排序

    public class HeapSort implements Sort {
    
      @Override
      public void sort(int[] nums) {
        MaxHeap<Integer> heap = new MaxHeap<>();
        for (int num : nums) {
          heap.add(num);
        }
        for (int i = nums.length - 1; i >= 0; i--) {
          nums[i] = heap.poll();
        }
      }
    }
    

    原地排序

    创建一个int类型的最大堆

    import java.util.Arrays;
    
    /**
     * 实现一个最大堆
     */
    public class IntMaxHeap {
    
      private int[] data;
    
      public IntMaxHeap(int[] source) {
        data = source;
        heapify();
      }
    
      /**
       * 将非堆的结构转换成堆结构
       */
      private void heapify() {
        int size = size();
        int lastParent = parent(size - 1);
        for (int i = lastParent; i >= 0; i--) {
          siftDown(i, size);
        }
      }
    
      public void sort() {
        int size = size();
        for (int i = size - 1; i >= 0; i--) {
          swap(0, i);
          siftDown(0, i);
        }
      }
    
      /**
       * 堆容量
       */
      public int size() {
        return data.length;
      }
    
    
      public String toString() {
        return Arrays.toString(data);
      }
    
      private void siftDown(int index, int size) {
        int cur = index;
        while (true) {
          int leftIndex = leftChild(cur);
          //没有左孩子
          if (leftIndex >= size) {
            break;
          }
          int rightIndex = rightChild(cur);
          int curEle = data[cur];
          int maxChild = data[leftIndex];
          int maxChildIndex = leftIndex;
          //存在右孩子且右孩子大于左孩子
          if (rightIndex < size) {
            int rightEle = data[rightIndex];
            if (rightEle > maxChild) {
              maxChildIndex = rightIndex;
              maxChild = rightEle;
            }
          }
          if (maxChild <= curEle) {
            break;
          }
          //将当前节点和左右孩子中的最大节点交换
          swap(cur, maxChildIndex);
          cur = maxChildIndex;
        }
      }
    
      private void swap(int left, int right) {
        int temp = data[left];
        data[left] = data[right];
        data[right] = temp;
      }
    
      private int parent(int index) {
        return (index - 1) / 2;
      }
    
      private int leftChild(int index) {
        return index * 2 + 1;
      }
    
      private int rightChild(int index) {
        return index * 2 + 2;
      }
    }
    
    public class HeapSort2 implements Sort {
    
      @Override
      public void sort(int[] nums) {
        IntMaxHeap heap = new IntMaxHeap(nums);
        heap.sort();
      }
    }
    

    总结

    平均时间复杂度 最好 最坏 空间复杂度 稳定性
    冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定
    选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
    插入排序 O(n^2) O(n) O(n^2) O(1) 稳定
    希尔排序 O(nlogn)~O(n^2) O(nlogn)~O(n^2) O(nlogn)~O(n^2) O(1) 不稳定
    归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
    快速排序 O(nlogn) O(n) O(n^2) O(1) 不稳定
    堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
    • 稳定性
      排序前相等的两个元素,排序后相对位置不变

    性能测试

    import java.util.List;
    import java.util.Random;
    
    public class Main {
    
      public static void main(String[] args) {
        List<Sort> sortList = List
            .of(new BubbleSort3(), new SelectSort(), new InsertSort(),
                new ShellSort2(), new MergeSort(), new QuickSort2(), new HeapSort2());
        int n = 100000;
        int[] originalNums = generateRandomArr(n, n);
        for (Sort sort : sortList) {
          int[] nums = Arrays.copyOf(originalNums, originalNums.length);
          long startTime = System.nanoTime();
          sort.sort(nums);
          long endTime = System.nanoTime();
          System.out.println(sort.getClass().getSimpleName() + " spend time: "
              + (endTime - startTime) / 1_000_000_000f + " s");
          checkArrOrder(sort, nums);
        }
      }
    
    
      private static int[] generateRandomArr(int n, int bound) {
        Random random = new Random();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
          arr[i] = random.nextInt(bound);
        }
        return arr;
      }
    
      private static int[] generateOrderArr(int n) {
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
          arr[i] = i;
        }
        return arr;
      }
    
      private static int[] generateReverseOrderArr(int n) {
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
          arr[i] = n - i;
        }
        return arr;
      }
    
      private static void checkArrOrder(Sort sort, int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
          if (nums[i] > nums[i + 1]) {
            throw new RuntimeException(sort.getClass().getSimpleName() + " arr not order");
          }
        }
      }
    }
    

    测试结果为

    BubbleSort3 spend time: 15.002914 s
    SelectSort spend time: 5.6422544 s
    InsertSort spend time: 0.86879486 s
    ShellSort2 spend time: 0.0141172 s
    MergeSort spend time: 0.0113022 s
    QuickSort2 spend time: 0.0142096 s
    HeapSort2 spend time: 0.0150131 s
    
  • 相关阅读:
    计算机硬件
    队列、堆、栈、堆栈的区别
    操作系统与应用程序的关系
    DNS与HTTPDNS
    配置静态路由传送网络包
    django的nginx配置
    视频流和文件传输相关协议
    HTTPS 对称加密和非对称加密
    HTTP1.1/2.0与QUIC协议
    mysql join联表 + id自增
  • 原文地址:https://www.cnblogs.com/strongmore/p/14309897.html
Copyright © 2011-2022 走看看