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

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


本文涉及的排序算法:

1.冒泡排序(优化版)
2.简单选择排序
3.直接插入排序
4.希尔排序
5.堆排序
6.归并排序(递归版)+归并排序(递归优化版)
7.快速排序+快速排序(四度优化版)


一、冒泡排序(优化版):

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

1.思路图解:
Java实现七种【排序算法】+图解+完整代码+分析_第1张图片

2.代码实现:

public class BubbleSort {
     

    public static void sort(int[] a) {
     
        int i, j;
        boolean flag = true;
        int n = a.length - 1;
        for (i = 1; i < n && flag; i++) {
     
            flag = false;
            for (j = n - 1; j >= i; j--) {
     
                if (a[j] > a[j + 1]) {
     
                    swap(a, j, j + 1);
                    flag = true;
                }
            }
        }
    }

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

}

3.分析:

  • 时间复杂度:O(n^2)

二、简单选择排序:

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

1.思路图解:

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

2.代码实现:

public class SelectSort {
     

    public static void sort(int[] a) {
     
        int i, j;
        int n = a.length - 1;
        int min;
        for (i = 1; i < n; i++) {
     
            min = i;
            for (j = i + 1; j <= n; j++) {
     
                if (a[j] < a[min]) {
     
                    min = j;
                }
            }
            if (i != min) {
     
                swap(a, i, min);
            }
        }
    }

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

}

3.分析:

  • 时间复杂度:O(n^2)
  • 最大特点就是交换移动数据次数相当少,性能上略优于冒泡排序

三、直接插入排序:

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

1.思路图解:

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

2.代码实现:

public class InterSort {
     

    public static void sort(int[] a) {
     
        int i, j;
        int n = a.length - 1;
        for (i = 1; i < n; i++) {
     
            if (a[i] > a[i + 1]) {
     
                a[0] = a[i + 1]; // 设置哨兵
                for (j = i; a[j] > a[0]; j--) {
     
                    a[j + 1] = a[j];
                }
                a[j + 1] = a[0];
            }
        }
    }

}

3.分析:

  • 时间复杂度:O(n^2)
  • 优于冒泡和简单选择排序

四、希尔排序:

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

1.思路图解:

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

2.代码实现:

public class ShellSort {
     

    public static void sort(int[] a) {
     
        int i, j;
        int n = a.length - 1;
        int increment = a.length - 1;
        do {
     
            increment = increment / 3 + 1;
            for (i = increment + 1; i <= n; i++) {
     
                if (a[i - increment] > a[i]) {
     
                    a[0] = a[i]; // 设置哨兵
                    for (j = i - increment; j > 0 && a[0] < a[j]; j -= increment) {
     
                        a[j + increment] = a[j];
                    }
                    a[j + increment] = a[0];
                }
            }
        } while (increment > 1);
    }

}

3.分析:

  • 时间复杂度:O(n^(3/2))
  • 由于记录是跳跃式移动,所以这并不是一种稳定的排序算法

五、堆排序

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

1.思路图解:

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

2.代码实现:

public class HeapSort {
     

    public static void sort(int[] a) {
     
        int i;
        int n = a.length - 1;
        for (i = n / 2; i > 0; i--) {
      //构建大顶堆
            HeapAdjust(a, i, n);
        }
        HeapSort(a);
    }

    private static void HeapSort(int[] a) {
     
        int i;
        int n = a.length - 1;
        for (i = n; i > 1; i--) {
     
            swap(a, 1, i);
            HeapAdjust(a, 1, i - 1); //将a[1...i-1]重新调整为大顶堆
        }
    }

    private static void HeapAdjust(int[] a, int s, int m) {
     
        int j;
        int temp = a[s];
        for (j = 2 * s; j <= m; j *= 2) {
      // 从上到下,从左到右的非叶子结点
            if (j < m && a[j] < a[j + 1]) {
     
                j++; // j为关键字中较大记录的下标
            }
            if (temp >= a[j]) {
     
                break; // 不需要调整,所以a[j]本就应插入在位置s上
            }
            a[s] = a[j];
            s = j;
        }
        a[s] = temp; // 将一开始此结点的值赋值给目前的s下标结点的值,达到结点交换的目的,从而构成此局部的堆
    }

    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)
  • 由于记录的比较与交换是跳跃式进行,因此也是一种不稳定的排序算法
  • 由于初始构建堆所需比较次数较多,因此不适合待排序序列个数较少的情况

六、归并排序:

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

1.思路图解:

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

2.代码实现:

public class MergeSort {
     

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

    //将SR[s..n]归并为有序的TR1[s..n]
    private static void MSort(int[] SR, int[] TR1, int s, int n) {
     
        int m;
        int[] TR2 = new int[n + 1]; // SR, TR1, TR2等长
        if (s == n) {
     
            TR1[s] = SR[s];
        } else {
     
            m = (s + n) / 2; // 将SR[s..n]平均分为SR[s..m]和SR[m+1..n]
            MSort(SR, TR2, s, m); // 递归将SR[s..m]归并为有序的TR2[s..m]
            MSort(SR, TR2, m + 1, n); // 递归将SR[m+1..n]归并为有序的TR2[m+1..n]
            Merge(TR2, TR1, s, m, n); // 将TR2[s..m]和TR2[m+1..n]归并到TR1[s..n]
        }
    }

    // 将有序的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
            }
        }
    }

}

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) 和 O(n)
  • 需要两两比较,不存在跳跃,是一种稳定的排序算法
  • 比较占用内存,但效率高
  • 尽量用非递归版(优化版)

七、快速排序:

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

1.思路图解:

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

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) {
     
        int pivot; // 枢轴的下标,将某个数放在此位置,使得它左边的值都比它小,右边的都比它大
        if (low < high) {
     
            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(logn) 和 O(n)
  • 由于关键字的比较和交换是跳跃进行的,因此也不稳定
  • 优化后性能提升

总结:

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

你可能感兴趣的:(数据结构,数据结构,排序算法,算法,快速排序,java)