常用的排序方法
排序方法 |
平均时间复杂度 | 时间复杂度(最好) | 时间复杂度(最坏) | 空间复杂度 | 稳定性 |
冒泡排序(BubbleSort) | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
直接选择排序(SectionSort) | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
直接插入排序(InsertSort) | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
希尔排序(ShellSort) | O(nlog₂n) | O(n) | O(n²) | O(1) | 不稳定 |
快速排序(QuickSort) | O(nlog₂n) | O(nlog₂n) | O(n²) | O(nlog₂n) | 不稳定 |
归并排序(MergeSort) | O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(n) | 稳定 |
堆排序(HeapSort) | O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(1) | 不稳定 |
桶排序(BucketSort) /基数排序(RadixSort)/ 计数排序(CountSort) |
O(d(n+m)) |
O(d(n+m)) | O(d(n+m)) | O(n+m) | 稳定 |
Tips: d(n+m)=n+n*(logN-logM);
其中,算法的稳定性是指,当数组中存在两个相同的数据,经过排序之后两个数据的位置是否发生相对改变。
1、冒泡排序(BubbleSort):
1:将每个数与下一个数进行比较,如果大于,则两者交换位置;(经过1次循环之后,最大数位于数组末端)
2:对剩余的 n - 1个数,重复第一步操作,经过n次循环之后,数组达到有序。
Tips:1. 可以将整个过程理解成泡泡的上升,最大的泡泡浮得最快,位于数组末端,
2.最好的情况是数组基本有序,最坏的情况是数组基本逆序。
public class BubbleSort {
public static void bubbleSort(int[] arr){
if(arr == null || arr.length <2){
return;
}
for(int i = arr.length - 1; i > 0; i--){//循环N次
for(int j = 0; j < i; j++){
if(arr[j] > arr[j+1]){
SwapArr.swap(arr, j, j+1);
}
}
}
}
}
2、直接选择排序(SectionSort)
1:遍历整个数组,找到其中最小的数,并与数组最前端的数交换位置(初始为0,经过一次遍历,最小数位于最前端);
2:从1位置继续遍历,重复第1步操作,循环n次,最终数组达到有序。
Tips: 每次都在数组里找最小的数放到前面(已经排好序的数组后面)
public class SectionSort {
public static void sectionSort(int[] arr){
if(arr == null || arr.length <2){
return;
}
for(int i = 0; i < arr.length - 1; i++){
int minIndex = i;
for(int j = i + 1; j < arr.length; j++){
if(arr[minIndex] > arr[j]){
minIndex = j;
}
}
SwapArr.swap(arr, i, minIndex);
}
}
}
3、直接插入排序(InsertSort)
1:从1位置开始,与前面的数进行比较,如果小于前面的数则交换位置,直到不再小于前面的数;
2:从2位置开始,重复第1步,直到达到最后一个位置。
Tips:1.从第二个数开始,做插入操作(插入的位置在小于自己的数之后,大于自己的数之前),最终达到有序,
2.最好的情况是数组基本有序,最坏的情况是数组基本逆序。
public class InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
SwapArr.swap(arr, j, j+1);
}
}
}
}
4、希尔排序(ShellSort)
1:首先确定一个步长 k,根据步长把数组分为(n/k)个部分进行排序;
2:缩短步长,继续细分待排序的数组;
3:直到步长缩短为1,此时数组达到有序。
Tips:1.过程类似于插入排序,但是通过将待排数组分为若干个子序列,减少移动次数,是插入排序的一种优化,
2.常用的初始步长是 (n / 2)。
3.最好的情况是数据基本有序。
public class ShellSort {
public static void shellSort(int[] arr) {
if (arr == null || arr.length < 2)
return;
int gap = arr.length / 2;
for (; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
SwapArr.swap(arr, j, j+gap);
}
}
}
}
public static void shellSort(int[] arr, int n) {
if (arr == null || arr.length < 2)
return;
int gap = n / 2;
for (; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
SwapArr.swap(arr, j, j+gap);
}
}
}
}
}
5、快速排序(QuickSort)
1:随机选出一个哨兵partition(枢轴 pivot),把大于partition的值放在右边,小于partition的值放在左边;
(partition一般取最左边的数,也可随机生成)
2:从partition左右两边分割,递归调用自身;
3:当达到递归结束条件是,数组达到有序状态。
Tips:1.为防止出现最坏情况(即所取的哨兵(枢轴)为最大数或最小数),可以随机生成哨兵(枢轴),但会增加开销
2.当数组基本有序时,快排将退化为冒泡排序。
public class QuickSort {
public static void quickSort(int[] arr){
if(arr.length>0)
quickSort(arr, 0, arr.length -1);
}
public static void quickSort(int[] arr, int left,int right){
if(left > right){
return;
}
int i = left;
int j = right;
int partition = arr[left];
while(i < j){
while(j > i && arr[j] > partition){
j--;
}
while(j > i && arr[i] <= partition){
i++;
}
if(i<j){
SwapArr.swap(arr, i, j);
}
}
SwapArr.swap(arr, i, left);
quickSort(arr, left, i-1);
quickSort(arr,i+1, right);
}
}
6、归并排序(MergeSort)
1:把数据分为两个部分,分别进行排序,最后合并;
2:递归调用自身,以实现排序。
Tips:1.将待排数组分为若干个有序的子序列,再进行合并操作,使其达到有序状态。
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int left, int right) {
if (left == right) {
return;
}
int mid = left + ((right - left) / 2);
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int [right - left + 1];
int l = left;
int r = mid + 1;
int index = 0;
while(l <= mid && r <= right){
temp[index++] = arr[l] < arr[r] ? arr[l++] :arr[r++];
}
while(l <= mid){
temp[index++] = arr[l++];
}
while(r <= right){
temp[index++] = arr[r++];
}
for(int i = 0; i < temp.length; i++){
arr[left+i] = temp[i];
}
}
}
7、堆排序(HeapSort)
1:将数组构造成一个大顶堆;
2:将堆顶的数据与最后一个数进行交换,然后调整剩下的(n - 1)个数据,使其仍为大顶堆;
3:最后实现排序。
Tips:1.大顶堆指根结点大于叶子结点的完全二叉树,整个算法的实现在于如何构建大顶堆和如何调整,
2.完全二叉树中,{(n - 1)/ 2 } 可以找到叶子结点的根结点在数组中的索引,(n * 2 + 1)为根结点的左子树结点在数组中的结点位置,再加 1 则为右结点。
public class HeapSort {
private static void insertHeap(int[] arr, int i) {//构建大顶堆
while (arr[i] > arr[(i - 1) / 2]) {
SwapArr.swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
private static void heapAdjust(int[] arr,int index, int size){
int left = index * 2 + 1;
while(left < size){
int largest = (left+1) < size && arr[left] < arr[left+1] ? left+1: left;
if(arr[index] > arr[largest]){
return;
}
SwapArr.swap(arr, index, largest);
index = largest;
left = index * 2 +1;
}
}
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return ;
}
for(int i = 1; i < arr.length; i++){ //将数组整理为大顶堆
insertHeap(arr, i);
}
int size = arr.length - 1;
while (size > 0){
SwapArr.swap(arr, 0, size);//交换堆顶和最后一位
heapAdjust(arr, 0, size--);//重新整理数组形成大顶堆
}
}
}
8、桶排序(BucketSort)/基数排序(RadixSort)/计数排序(CountSort)
1:桶排序的实现方法为:找到数组中的最大数max,创建(max + 1) 个“桶”(数组),遍历一遍待排序的数组,将数据放入对应的“桶”,最后按顺序遍历一遍所有“桶”,将有数据的桶的编号按顺序放入数组中,最后数组达到有序。
2:基数排序的实现方法有两种:
MSD(最高位优先 Most Significant Digit first),找到数组中数据最高位,对最高位进行排序,依次进行到个位数。
LSD(最低位优先 Least Significant Digit first),从个位数开始排序,直到最高位结束。
桶排序:
public class BucketSort {
public static void bucketSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
for (int i : arr) {
max = Math.max(max, i);
}
int[] bucket = new int[max + 1];
for(int i : arr){
bucket[i] ++;
}
int i = 0;
for(int j = 0; j < bucket.length;j++){
while(bucket[j]-- >0){
arr[i++] = j;
}
}
}
}
基数排序:
public class RadixSort {
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxbit(arr));
}
private static void radixSort(int[] arr, int begin, int end, int digit) {
int[] bucket = new int[end - begin + 1];
final int radix = 10;
int i, j = 0;
int[] count = new int[radix];
for (int d = 1; d <= digit; d++) {
for(i = 0; i < radix ; i++){
count[i] = 0;
}
for (i = begin; i <= end; i++) {
j = getDigit(arr[i], d);
count[j]++;
}
for (i = 1; i < radix; i++) {//count用来归位数组,此处加上前一个是往后增加
count[i] += count[i - 1];
}
for (i = end; i >= begin; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j] -- ;
}
for(i = begin ,j = 0; i<= end;i++,j++){
arr[i] = bucket[j];
}
}
}
private static int maxbit(int[] arr) {
int max = Integer.MIN_VALUE;
int res = 0;
for (int i : arr) {
max = Math.max(max, i);
}
while (max != 0) {
res++;
max /= 10;
}
return res;
}
private static int getDigit(int num, int d) {
return (num / (int) Math.pow(10, d - 1) % 10);
}
}
本文章借鉴博主ChaosofStars的博文“常用排序算法整理”,如有侵权请联系本人删除。