七种常见的排序算法与Java实现

排序

稳定排序: 如果a=b并且a出现在b的前面,排序之后a仍然在b的前面。如冒泡排序、插入排序、归并排序、计数排序、桶排序、基数排序。

不稳定排序:如果a=b并且a出现在b的前面,排序之后b可能出现在a的前面。如选择排序、希尔排序、快速排序、堆排序。

1 插入排序

最简单的排序算法之一,由N-1趟排序组成。对于未排序数据,在已排序序列中从后向前扫描,把已排序的元素向后移,找到相应的位置并插入数据。

空间复杂度: O(1)

时间复杂度: 平均O(N2),最好O(N),最坏O(N2)。

public static > void insertionSort(AnyType[] a) 
    {
        int j;
        for (int p = 1; p < a.length; p++) {  //第p次排序,将第p个数插入已排序好的前p-1个数中
            AnyType tmp = a[p];
            for (j = p; j > 0 && tmp.compareTo(a[j-1]) < 0; j--) {
                a[j] = a[j - 1];
            }
            a[j] = tmp;
        }
    }

2 希尔排序

希尔排序是改进后的简单插入排序,也称为缩小增量排序。

希尔排序使用一个增量序列,运行时间也依赖于增量序列的选择,希尔建议的序列为{N/2, (N/2)/2, ...,1},即希尔增量。按照增量的个数,进行k趟排序,每趟排序,根据对应的增量,将待排序列分割成若干长度为m的子序列,各个子序列分别进行插入排序。

原始数组 8 9 1 7 2 3 5 4 6 0
增量5排序后 3 5 1 6 0 8 9 4 7 2
增量2排序后 0 2 1 4 3 5 7 6 9 8
增量1排序后 0 1 2 3 4 5 6 7 8 9

空间复杂度: O(1)

时间复杂度:希尔增量下最坏O(N2),Hibbard增量下最坏O(N3/2)

希尔排序开始时,元素无序度高,但是每组进行插入排序的元素少,速度快;后期元素基本有序,插入排序对于有序的序列效率高,所以希尔排序时间复杂度低于简单插入排序。

public static > void shellSort(AnyType[] a) {
        int j;
        for (int gap = a.length / 2; gap > 0; gap /= 2)
        {
            for (int i = gap; i < a.length; i++)
            {
                AnyType tmp = a[i];
                for (j = i; j >= gap && tmp.compareTo(a[j - gap]) < 0; j-= gap)
                    a[j] = a[j - gap];
                a[j] = tmp;
            }
        }
    }

3 选择排序

时间复杂度稳定为O(N^2)的简单直观排序方法。首先在未排序的序列中选出最小的元素放在排序序列的起始位置,然后在未排序的序列中继续寻找最小的元素,将其添加到已排序的序列的末尾,直到所有元素排序完毕。

空间复杂度:O(1)

时间复杂度:O(N2)

public static > void selectionSort(AnyType[] a) {
        for (int i = 0; i < a.length; i++) {
            int minIndex = i;
            for (int j = i; j < a.length; j++) {
                if (a[j].compareTo(a[minIndex]) < 0) {
                    minIndex = j;
                }
            }
            AnyType temp = a[minIndex];
            a[minIndex] = a[i];
            a[i] = temp;
        }
    }

4 堆排序

堆排序是选择排序的一种,堆分为最大堆(父结点值总是大于子结点)和最小堆,是完全二叉树。

父节点索引i,左儿子索引2i+1,右儿子索引2i+2。

将初始待排序序列构建成最大堆,将堆顶元素与最后一个元素交换,产生无序部分和有序部分,将无序部分重新调整为最大堆,并将堆顶元素与无序区最后一个元素交换。如此重复构建与交换,直到有序部分元素为n-1,则完成排序。
空间复杂度:O(1)

时间复杂度:O(NlogN)

public static void heapify(int[] tree, int n, int i) { //根结点不行,子树满足堆结构
        //i为当前需要HEAPIFY的根节点
        int c1 = 2 * i + 1;
        int c2 = 2 * i + 2;
        int max = i;
        if (c1 < n && tree[c1] > tree[max]) {
            max = c1;
        }
        if (c2 < n && tree[c2] > tree[max]) {
            max = c2;
        }
        if (max != i)  //根结点小于子结点时
        {
            swap(tree, max, i);
            heapify(tree, n, max);
        }
    }
    
    public static void buildHeap(int[] tree, int n) { //完全乱序时
        int lastNode = n - 1;
        int parent = (lastNode - 1) / 2;
        int i;
        for (i = parent; i >= 0; i--) {
            heapify(tree, n, i);
        }
    }
    public static void swap(int[] tree, int i, int j) {
        int temp = tree[i];
        tree[i] = tree[j];
        tree[j] = temp;
    }
    
    public static void heapSort(int[] tree, int n) {
        buildHeap(tree, n);
        int i;
        for (i = n - 1; i >= 0; i--) {
            swap(tree, i, 0);
            heapify(tree, i, 0);
        }
    }

5 冒泡排序

重复比较相邻的元素,如果第一个数比第二个大,则交换位置,最开始从第一对一直交换到最后一对,一趟排序后则最大数存在在最后位置。对于未排序的部分重复上述交换操作。

空间复杂度:O(1)

时间复杂度:平均O(N2),最好O(N),最坏O(N2)

代码中引入boolean didSort进行判断,才能使得最优情况下时间复杂度为O(N)

public static void bubbleSort(int[] a) {
        boolean didSort;
        for (int i = a.length - 1; i >= 0; i--) 
        {
            didSort = false;
            for (int j = 0; j < i; j++) 
            {
                if (a[j] > a[j + 1])
                {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    didSort = true;
                }
            }
            if (didSort == false)   //已经排序好,直接退出,可使最优情况下时间复杂度为O(n)
                return;
        }
    }

6 归并排序

归并排序是分治算法的一个典型应用,性能不受输入数据的影响,时间复杂度始终是O(NlogN),代价是需要额外O(N)的内存空间,但是时间受比较元素在额外空间数组中移动等速度影响。通过先对子序列排序,再将两个子序列合并到一个新的序列中。

把长度为n的输入序列分成两个长度为n/2的子序列,对这两个子序列再分别采用归并排序,将排序好的两个子序列合并成一个最终的排序序列。

时间复杂度:O(NlogN)

空间复杂度:O(N)

public class MergeSort {
    public static int[] mergeSort(int[] array) {
        if (array.length < 2)
            return array;
        int mid = array.length / 2;
        int[] left = Arrays.copyOfRange(array, 0, mid);
        int[] right = Arrays.copyOfRange(array, mid, array.length);
        return merge(mergeSort(left), mergeSort(right));
    }

    private static int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];
        for (int index = 0, l = 0, r = 0; index < result.length; index++) {
            if (l >= left.length)   //左子序列已经全部合并入新序列中
                result[index] = right[r++];
            else if (r >= right.length)
                result[index] = left[l++];
            else if (left[l] > right[r])
                result[index] = right[r++];
            else
                result[index] = left[l++];
        }
        return result;
    }

7 快速排序

首先从序列中随机选择一个基准元素,逐个比较各元素与基准元素大小,将小的元素放在基准前面,大的元素放在后面,这样一轮下来序列就被分为两个部分,左半部分比基准小,右半部分比基准大。接着再分别对这两部分进行相同的操作。

时间复杂度:平均O(NlogN),最好O(NlogN),最坏O(N2)

空间复杂度:O(logN)

public static void quickSort(int[] arr, int start, int end) {
        if (start >= end)
            return;
        //把需要排序的部分第一个元素作为基准
        int stard = arr[start];
        //记录需要排序的下标
        int low = start;
        int high = end;
        //循环找比基准小和比基准大的数
        while (low < high) {
            //右边数字比基准大
            while (low < high && arr[high] >= stard)
            {
                high--;
            }
            //使用右边的数字替换左边的数
            arr[low] = arr[high];
            //左边数字比基准小
            while (low < high && arr[low] <= stard)
            {
                low++;
            }
            //使用左边的数字替换右边的数
            arr[high] = arr[low];
        }
        //把基准数字重新赋给所在位置
        arr[low] = stard;
        quickSort(arr, start, low);
        quickSort(arr, low + 1, end);
    }

你可能感兴趣的:(七种常见的排序算法与Java实现)