java实现各种排序算法(比较排序)

准备

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
posted @ 2021-01-21 19:21  strongmore  阅读(196)  评论(0编辑  收藏  举报