Java实现七种【排序算法】+图解+完整代码+分析

参考书目:《大话数据结构》

一、冒泡排序(优化版)

0.基本思想:一种交换排序,两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录位置。

1.思路图解:
在这里插入图片描述

2.代码实现:

public class BubbleSort {
    public static int[] sort(int[] array) {
        int i, j;
        int len = array.length;
        int[] copyArray = Arrays.copyOf(array, len);
        boolean flag = true;

        for (i = 0; i < len - 1 && flag; i++) {
            flag = false;
            for (j = len - 2; j >= i; j--) {
                if (copyArray[j] > copyArray[j + 1]) {
                    swap(copyArray, j, j + 1);
                    flag = true;
                }
            }
        }

        return copyArray;
    }

    private static void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

3.分析:

  • 平均时间:O (n^2)
  • 最差时间:O (n^2)
  • 稳定度:稳定
  • 额外空间:O (1)
  • 最佳实践:n 小时好

二、简单选择排序

0.基本思想:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换。

1.思路图解:

在这里插入图片描述

2.代码实现:

public class SelectSort {

    public static int[] sort(int[] array) {
        int i, j;
        int len = array.length;
        int[] copyArray = Arrays.copyOf(array, len);
        int minIndex = 0;

        for (i = 0; i < len - 1; i++) {
            minIndex = i;
            for (j = i + 1; j < len; j++) {
                if (copyArray[j] < copyArray[minIndex]) {
                    minIndex = j;
                }
            }
            if (minIndex != i) {
                swap(copyArray, i, minIndex);
            }
        }

        return copyArray;
    }

    private static void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

3.分析:

  • 平均时间:O (n^2)
  • 最差时间:O (n^2)
  • 稳定度:不稳定
  • 额外空间:O (1)
  • 最佳实践:n 小时好
  • 最大特点就是交换移动数据次数相当少,性能上略优于冒泡排序

三、直接插入排序

0.基本思想:将一个记录插入到以及排好序的有序表中,从而得到一个新的、记录数增1的有序表。

1.思路图解:

在这里插入图片描述

2.代码实现:

public class InsertSort {
    public static int[] sort(int[] array) {
        int i, j;
        int len = array.length;
        int[] copyArray = Arrays.copyOf(array, len);
        int tmp = 0;

        for (i = 0; i < len - 1; i++) {
            if (copyArray[i] > copyArray[i + 1]) {
                tmp = copyArray[i + 1];
                for (j = i; j >= 0 && copyArray[j] > tmp; j--) {
                    copyArray[j + 1] = copyArray[j];
                }
                copyArray[j + 1] = tmp;
            }
        }

        return copyArray;
    }
}

3.分析:

  • 平均时间:O (n^2)
  • 最差时间:O (n^2)
  • 稳定度:稳定
  • 额外空间:O (1)
  • 最佳实践:序列中基本有序时较好
  • 优于冒泡和简单选择排序

四、希尔排序

0.基本思想:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,所有记录恰被分成一组,算法便终止。因此,将关键字较小的记录,不是一步一步地往前挪动,而是跳跃式地往前移,从而使得每完成一轮循环后,整个序列就朝着有序坚实地迈进一步。

1.思路图解:

在这里插入图片描述

2.代码实现:

public class ShellSort {
    public static int[] sort(int[] array) {
        int i, j;
        int len = array.length;
        int[] copyArray = Arrays.copyOf(array, len);
        int increment = len - 1;
        int tmp = 0;

        //用 do-while 是为了 len = 2 时这种特殊情况
        do {
            increment = increment / 3 + 1;
            for (i = 0; i < len - increment; i++) {
                if (copyArray[i] > copyArray[i + increment]) {
                    tmp = copyArray[i + increment];
                    for (j = i; j >= 0 && copyArray[j] > tmp; j -= increment) {
                        copyArray[j + increment] = copyArray[j];
                    }
                    copyArray[j + increment] = tmp;
                }
            }
        } while (increment > 1);

        return copyArray;
    }
}

3.分析:

  • 平均时间:O (nlogn)
  • 最差时间:O (n^s, 1 < s < 2)
  • 稳定度:不稳定
  • 额外空间:O (1)
  • 由于记录是跳跃式移动,所以这并不是一种稳定的排序算法

五、堆排序

0.基本思想:将待排序的序列构造成一个大顶锥(适用于从小到大排序),此时整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值。如此反复执行,便能得到一个有序序列。

1.思路图解:

在这里插入图片描述

2.代码实现:

public class HeapSort {

    public static int[] sort(int[] array) {
        int i;
        int n = array.length;

        //构建大顶堆
        for (i = (n - 1) / 2; i >= 0; i--) {
            heapAdjust(array, i, n - 1);
        }

        //对大顶堆进行堆排序
        for (i = n - 1; i > 0; i--) {
            swap(array, 0, i);
            heapAdjust(array, 0, i - 1);
        }

        return array;
    }

    private static void heapAdjust(int[] array, int start, int end) {
        int j;
        int tmp = array[start];

        for (j = 2 * start; j <= end; j *= 2) {
            if (j < end && array[j] < array[j + 1]) {
                j++; //j为当前start结点,和此结点的左右孩子直接的较大值的下标
            }
            if (tmp >= array[j]) {
                break; //不需要调整,这时a[start]本来就是三角形的较大值
            }
            array[start] = array[j];
            start = j;
        }

        array[start] = tmp; //若经历了for构造,则应为最初的array[start]找一个归宿
    }

    private static void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }
}

3.分析:

  • 平均时间:O (nlogn)
  • 最差时间:O (nlogn)
  • 稳定度:不稳定
  • 额外空间:O (1)
  • 最佳实践:n 大时好
  • 由于记录的比较与交换是跳跃式进行,因此也是一种不稳定的排序算法
  • 由于初始构建堆所需比较次数较多,因此不适合待排序序列个数较少的情况

六、归并排序(递归版)+归并排序(递归优化版)

0.基本思想:初始序列含有n个元素,则可看成是n个有序子序列,每个子序列的长度为1,然后两两归并,得到Math.ceil(n/2)个长度为2或1的有序子序列;再两两归并,…,如此重复,直到得到一个长度为n的有序序列位置,这种排序方法称为2路归并排序。

1.思路图解:

在这里插入图片描述

2.代码实现:

//递归实现
public class MergeSort {
    public static int[] sort(int[] a, int low, int high) {
        if (low < high) {
            int mid = (low + high) / 2;
            sort(a, low, mid);
            sort(a, mid + 1, high);
            //左右归并
            merge(a, low, mid, high);
        }
        return a;
    }

    public static void merge(int[] a, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];
        int i = low;
        int j = mid + 1;
        int k = 0;
        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (a[i] < a[j]) {
                temp[k++] = a[i++];
            } else {
                temp[k++] = a[j++];
            }
        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = a[i++];
        }
        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = a[j++];
        }
        // 把新数组中的数覆盖nums数组
        for (int index = 0; index < temp.length; index++) {
            a[low + index] = temp[index];
        }
    }
}

2.5.代码实现(优化版):

public class MergeSort2 {

    public static void sort(int[] a) {
        int n = a.length - 1;
        MSort(a);
    }

    private static void MSort(int[] SR) {
        int n = SR.length - 1;
        int[] TR = new int[n + 1];
        int k = 1;
        while (k < n) {
            MergePass(SR, TR, k, n);
            k = 2 * k;
            MergePass(TR, SR, k, n);
            k = 2 * k;
        }
    }

    // 将SR[s..n]归并为有序的TR[s..n]
    private static void MergePass(int[] SR, int[] TR, int s, int n) {
        int i = 1;
        int j;
        while (i <= n - 2 * s + 1) {
            Merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
            i = i + 2 * s;
        }
        if (i < n - s + 1) {
            Merge(SR, TR, i, i + s - 1, n);
        } else {
            for (j = i; j <= n; j++) {
                TR[j] = SR[j];
            }
        }
    }

    // 将有序的SR[s..m]和SR[m+1..n]归并为有序的TR[i..n]
    private static void Merge(int[] SR, int[] TR, int s, int m, int n) {
        int j, k, l; // k为左块的起始下标,j为右块的起始下标
        for (k = s, j = m + 1; s <= m && j <= n; k++) { // SR中记录由小到大归并入TR
            if (SR[s] < SR[j]) {
                TR[k] = SR[s++];
            } else {
                TR[k] = SR[j++];
            }
        }
        if (s <= m) {
            for (l = 0; l <= m - s; l++) {
                TR[k + l] = SR[s + l]; // 将剩余的SR[s..m]复制到TR
            }
        }
        if (j <= n) {
            for (l = 0; l <= n - j; l++) {
                TR[k + l] = SR[j + l]; // 将剩余的SR[j..m]复制到TR
            }
        }
    }

}

3.分析:

  • 平均时间:O (nlogn)

  • 最差时间:O (nlogn)

  • 稳定度:稳定

  • 最佳实践:n 大时好

  • 空间复杂度:O(nlogn) 和 O(n)

  • 需要两两比较,不存在跳跃,是一种稳定的排序算法

  • 比较占用内存,但效率高

  • 尽量用非递归版(优化版)


七、快速排序+快速排序(四度优化版)

0.基本思想:通过一趟排序将待排序记录分割成独立的两部分,其中每一部分记录的关键字均比另一部分记录的关键字小,则可以分别对着两部分记录继续进行排序,以达到整个序列有序的目的。

1.思路图解:

在这里插入图片描述

2.代码实现:

public class QuickSort {
    public static void sort(int[] a) {
        int n = a.length - 1;
        QSort(a, 1, n);
    }

    private static void QSort(int[] a, int low, int high) {
        if (low < high) {
            int pivot = Partition(a, low, high); // 将a[low..high]一分为二,算出枢轴下标pivot
            QSort(a, low, pivot - 1); // 对低子表递归排序
            QSort(a, pivot + 1, high); // 对高子表递归排序
        }
    }

    // 交换顺序表a中子表的记录,使枢轴记录到为,并返回其位置
    private static int Partition(int[] a, int low, int high) {
        int pivotkey = a[low]; // 用子表的第一个记录作枢轴记录
        while (low < high) { // 从表的两端交替向中间扫描
            while (low < high && a[high] >= pivotkey) {
                high--;
            }
            swap(a, low, high); // 将比枢轴值小的记录交换到低端
            while (low < high && a[low] <= pivotkey) {
                low++;
            }
            swap(a, low, high); // 将比枢轴值大的记录交换到高端
        }
        return low; // 最终low == high,所有返回枢轴所在位置
    }

    private static void swap(int[] a, int x, int y) {
        int temp;
        temp = a[x];
        a[x] = a[y];
        a[y] = temp;
    }

}

2.代码实现(优化版):

public class QuickSortUp {

    // 1.优化选取枢轴(三数取中),选取更较为合适的枢轴值,使得左小右大更均匀;
    // 2.优化交换操作为替换,分析得知,swap交换操作本身不必要;
    // 3.优化小数组时的排序方案为直接插入排序,而对于大数组则采用快排;
    // 4.优化递归操作,将对高子表的递归转为只对低子表的迭代版递归

    public static void sort(int[] a) {
        int n = a.length - 1;
        QSort(a, 1, n);
    }

    private static void QSort(int[] a, int low, int high) {
        int ORDINARY_LEN = 7; // 数组长度阈值,当小于时,可用直接插入排序
        int pivot; // 枢轴的下标,将某个数放在此位置,使得它左边的值都比它小,右边的都比它大
        if ((high - low) > ORDINARY_LEN) {
            while (low < high) {
                pivot = Partition(a, low, high); // 将a[low..high]一分为二,算出枢轴下标pivot
                QSort(a, low, pivot - 1); // 对低子表递归排序
                low = pivot + 1; // 尾递归
            }
        } else {
            InterSort.sort(a);
        }
    }

    // 交换顺序表a中子表的记录,使枢轴记录到为,并返回其位置
    private static int Partition(int[] a, int low, int high) {
        int pivotkey;
        int m = low + (high - low) / 2;
        if (a[low] > a[high]) {
            swap(a, low, high);
        }
        if (a[m] > a[high]) {
            swap(a, high, m);
        }
        if (a[m] > a[low]) {
            swap(a, m, low);
        }
        pivotkey = a[low]; // 此时a[low]为三数取中得到的中间值
        a[0] = pivotkey; // 哨兵
        while (low < high) { // 从表的两端交替向中间扫描
            while (low < high && a[high] >= pivotkey) {
                high--;
            }
            a[low] = a[high]; //改交换为替换
            while (low < high && a[low] <= pivotkey) {
                low++;
            }
            a[high] = a[low];
        }
        a[low] = a[0];
        return low; // 最终low == high,所有返回枢轴所在位置
    }

    private static void swap(int[] a, int x, int y) {
        int temp;
        temp = a[x];
        a[x] = a[y];
        a[y] = temp;
    }

}

3.分析:

  • 平均时间:O (nlogn)
  • 最差时间:O (n^2)
  • 稳定度:不稳定
  • 最佳实践:n 大时好
  • 空间复杂度:O(logn) 和 O(n)
  • 由于关键字的比较和交换是跳跃进行的,因此也不稳定
  • 优化后性能提升

End.总结

在这里插入图片描述

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超周到的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值