排序算法不管是在面试中,还是在平时的开发中,都经常用到,本文对十种排序算法进行总结,比较它们的优劣!!!
中文名称 | 英文名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|---|
选择排序 | Selection | n2 | n2 | n2 | 1 | 不稳 |
冒泡排序 | Bubble | n2 | n2 | n | 1 | 稳 |
插入排序 | Insertion | n2 | n2 | n | 1 | 稳 |
堆排序 | Heap | nlog2n | nlog2n | nlog2n | 1 | 不稳 |
希尔排序 | Shell | n1.3 | n2 | n | 1 | 不稳 |
归并排序 | Merge | nlog2n | nlog2n | nlog2n | n | 稳 |
快速排序 | Quick | nlog2n | n2 | nlog2n | log2n | 不稳 |
桶排序 | Bucket | n+k | n2 | n | n+k | 稳 |
计数排序 | Counting | n+k | n+k | n+k | n+k | 稳 |
基数排序 | Radix | n*k | n*k | n*k | n+k | 稳 |
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。
选择排序有两步,代码实现步骤比较简单。
/**
* 选择排序
*/
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {5, 3, 1, 4, 2};//对该数组排序
//从数组的第一个元素开始,从前往后,总共需要比较N-1趟
for (int i = 0; i < arr.length-1; i++) {
//记录每一趟最小值处的索引
int minPos = i;
//这层循环进行比较、交换
for(int j=i+1; j<arr.length; j++) {
//比较、更新最小值索引
minPos = arr[j] < arr[minPos] ? j : minPos;
}
//交换
swap(arr,i,minPos);
}
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
冒泡排序就是利用循环,将当前最大的元素移到靠后的位置
下面数组arr = {9, 3, 1, 4, 6, 8, 7, 5, 2},通过比较将最大的元素9移到最后
arr1 = {3, 1, 4, 6, 8, 7, 5, 2, 9};
依次类推,加一层外循环,得到排序好的数组
/**
* 冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {9, 3, 1, 4, 6, 8, 7, 5, 2};
//第一层循环表示趟数,有arr.length个数,那么仅需要arr.length-1趟
//仅剩两个数的时候,只需要1趟即可
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]) swap(arr, j, j+1);
}
}
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
插排的核心思想就是原始数组分成两个子数组,左数组Left是有序的,右数组Right是无序的。
初始:把数组的第一个元素当成Left,其余元素当成Right,Right数组的第一个元素开始向Left当中插入。插入的原则是,将该元素插入到第一个比该元素大的位置的左侧!!!
如何找到合适的位置,循环比较(类似冒泡排序的思想),一直比较、交换找到合适的位置。
/**
* 插入排序
*/
public class InsertionSort {
public static void main(String[] args) {
int[] arr = {6, 2, 5, 4, 8};
//从Right的第一个元素开始{2, 5, 4, 8}
for (int i = 1; i < arr.length; i++) {
//从Left数组的最后一个元素开始,往前进行比较
//j--表示Left数组从后往前
for (int j = i; j > 0; j--) {
//若插入元素小于Left数组的arr[j-1],则交换
if(arr[j] < arr[j-1]) {
swap(arr,j,j-1);
}
}
}
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
选择排序、冒泡排序、插入排序三种排序算法比较简单,容易实现,使用场景如下:
public class HeapSort {
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int len = arr.length;
buildMaxHeap(arr, len);
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len);
}
return arr;
}
private void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
private void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
选定一个间隔,按照这个间隔进行分组,分组之后按照插入排序排序
/**
* 希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
int h = 1;
while (h <= arr.length / 3) {
h = h*3 + 1;
}
for (int gap = h; gap > 0; gap=(gap-1)/3) {
for (int i = gap; i <arr.length; i++) {
for (int j = i; j > gap-1; j-=gap) {
if(arr[j] < arr[j-gap]){
swap(arr, j, j-gap);
}
}
}
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
将原始数组分解为多个子序列,然后对每个子序列进行排序,最后将排好序的子序列合并起来。
/**
* 归并排序
*/
public class MergeSort {
public static void main(String[] args) {
int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
sort(arr, 0, arr.length-1);
}
//递归的思想,大数组分为左、右两个子数组
static void sort(int[] arr, int left, int right) {
if (left == right) return;
//分两成半
int mid = left + ((right - left) >> 1);
//左边排序
sort(arr, left, mid);
//右边排序
sort(arr, mid + 1, right);
merge(arr, left, mid+1, right);
}
//merge()方法,默认两个子数组是排好序的,进行合并
static void merge(int[] arr, int leftPtr, int rightPtr, int rightBound) {
int i = leftPtr;
int j = rightPtr;
int k = 0;
int[] temp = new int[rightBound-leftPtr+1];
int mid = rightPtr - 1;
while (i <= mid && j <= rightBound) {
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i<=mid) temp[k++] = arr[i++];
while (j<=rightBound) temp[k++] = arr[j++];
for (int l = 0; l < temp.length; l++) {
arr[leftPtr+l] = temp[l];
}
}
}
以数组右边界的元素为轴key,作为基准值进行快排。
/**
* 快速排序
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr = {7, 3, 2, 8, 1, 9, 5, 4, 6};
sort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + " ");
}
}
static void sort(int[] arr, int leftBound, int rightBound) {
if(leftBound >= rightBound) return;
int mid = partition(arr, leftBound, rightBound);
sort(arr, leftBound, mid-1);
sort(arr, mid+1, rightBound);
}
static int partition(int[] arr, int leftBound, int rightBound) {
int key = arr[rightBound];
int left = leftBound;
int right = rightBound-1;
while(left <= right) {
while(left <= right&&arr[left] <= key) left++;
while(left <= right&&arr[right] > key) right--;
if(left < right) swap(arr, left, right);
}
swap(arr, left, rightBound);
return left;
}
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
桶排序用的比较少,将原数组按照范围分为几个有限的桶,对每个桶进行桶内排序,排序好之后在合并。
使用的比较少,常用于数量大且范围小的数据排序
比如,0~9之间的数字有几百个(元素大量重复),那么就可以用计数排序。
/**
* 计数排序
*/
public class CountSort {
public static void main(String[] args) {
int[] arr = {2, 4, 2, 3, 7, 1, 1, 0, 0, 5, 6, 9, 8, 5, 7, 4, 0, 9};
int[] result = sort(arr);
}
static int[] sort(int[] arr) {
int[] result = new int[arr.length];
int[] count = new int[10];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
for (int i=0, j=0; i < count.length; i++) {
//当前计数位不为0时
while(count[i]-- > 0) {
result[j++] = i;
}
}
return result;
}
}
还可以进一步优化,实现计数排序的稳定性,用的是累加数组。
static int[] sort(int[] arr) {
int[] result = new int[arr.length];
int[] count = new int[10];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
//累加数组
for (int i=1; i < count.length; i++) {
count[i] = count[i] + count[i-1];
}
//倒序遍历原数组,根据累加数组的索引位置,得到result
for (int i = arr.length-1; i >= 0; i--) {
result[--count[arr[i]]] = arr[i];
}
return result;
}
桶排序的一种,按照个位、十位、百位进行排序,用到了计数排序
/**
* 基数排序
*/
public class RadixSort {
public static void main(String[] args) {
int[] arr = {421, 240, 115, 532, 305, 430, 124};
int[] result = sort(arr);
System.out.println(Arrays.toString(result));
}
static int[] sort(int[] arr) {
int[] result = new int[arr.length];
int[] count = new int[10];
//判断数组元素的最高位数,这里用的最高3位
//比较3趟,分别是个、十、百
for (int i = 0; i < 3; i++) {
int division = (int)Math.pow(10, i);
System.out.println(division);
for (int j = 0; j < arr.length; j++) {
int num = arr[j]/division % 10;
System.out.println(num + " ");
count[num]++;
}
for (int m = 1; m < count.length; m++) {
count[m] = count[m] + count[m-1];
}
for (int n = arr.length-1; n >= 0; n--) {
int num = arr[n]/division % 10;
result[--count[num]] = arr[n];
}
//将result复制到arr中,重复这个操作
System.arraycopy(result, 0, arr, 0, arr.length);
//数组元素没有高位,那么补0
Arrays.fill(count, 0);
}
return result;
}
}