各类排序算法总结

235 阅读2分钟

本文总结了7种最常用的排序算法。

一、冒泡排序

基本思想:通过元素的两两比较,每次比较,将较大的元素右移,第一次循环,可以将最大的元素移动到最右端,第二次循环,可以将第二大的元素移动到右边第二个位置,重复这个过程,直到整个数组有序。算法的时间复杂度是O(n^2)。代码如下:

public static void bubbleSort(int[] a) {
    int n = a.length;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - 1; j++) {
            int temp = 0;
            if (a[j] > a[j + 1]) {
                temp = a[j + 1];
                a[j + 1] = a[j];
                a[j] = temp;
            }
        }
    }
}

二、快速排序

基本思想:找到一个基准数,一般是第一个数,将小于基准数的数“交换”到基准数的左边,将大于基准数的数“交换”到基准数的右边,这样一次循环后,基准数左边的数都小于基准数,基准数右边的数都大于基准数,之后用递归,对基准数的左边和右边进行同样的操作。需要注意的是,此处的“交换”并不是真的交换,而是将数覆盖到基准数的位置,因为基准数已经存在变量中了。时间复杂度为O(nlog2n)。

public static void quickSort(int[] a, int left, int right) {
    if (left > right) {
        return;
    }
    int base = a[left];
    int i = left;
    int j = right;
    while (i < j) {
        while (i < j && a[j] >= base) {
            j--;
        }
        if (i < j) {
            a[i] = a[j];
            i++;
        }
        while (i < j && a[i] < base) {
            i++;
        }
        if (i < j) {
            a[j] = a[i];
            j--;
        }
    }
    a[i] = base;
    quickSort(a, left, i - 1);
    quickSort(a, i + 1, right);
}

三、插入排序

基本思想:首先将第一个元素当作一个有序序列,后面的元素为无序序列,从第二个元素开始,每次先把元素插入到有序序列中,再循环,通过比较大小两两交换,将这个元素放到有序的位置上,重复这个过程,直到整个序列有序。算法的时间复杂度为O(n^2);

public static void insertSort(int[] a) {
    int n = a.length;
    for (int i = 1; i < n; i++) {
        for (int j = i; j > 0; j--) {
            if (a[j] < a[j - 1]) {
                int temp = a[j];
                a[j] = a[j - 1];
                a[j - 1] = temp;
            }
        }
    }
}

四、希尔排序

基本思想:因为插入排序在数组基本有序的时候效率最高,在数组无序的时候效率降低,因此,在插入排序的基础上,出现了希尔排序。每次取一个增量,假设数组长度为n,一般初始的增量为n/2,根据这个增量将数组分为两组,每组里分别使用插入排序,需要注意的是,这里的分组其实是不连续的。之后将增加设置为n/4,重复上一个过程,直到数组有序。算法的时间复杂度是O(n^1.3)。

public static void shellSort(int[] a) {
    int n = a.length;
    int gap = n / 2;
    while (gap != 0) {
        for (int i = 0; i + gap < n; i += gap) {
            for (int k = i + gap; k > 0; k -= gap) {
                if (a[k] < a[k - gap]) {
                    int temp = a[k];
                    a[k] = a[k - gap];
                    a[k - gap] = temp;
                }
            }
        }
        gap = gap / 2;
    }
}

五、简单选择排序

基本思想:第一次循环,找到最小的元素,将这个元素放到第一个位置,之后排除已经排好序的元素,将最小的元素放到最小的位置,以此类推。时间复杂度为O(n^2)。

int small;
for (int i = 0; i < a.length; i++) {
    small = i;
    for (int j = i + 1; j < a.length; j++) {
        if (a[j] < a[small]) {
            int temp = a[j];
            a[j] = a[small];
            a[small] = temp;
        }
    }
}

六、堆排序

基本思想:堆是一种数据结构,是一棵完全二叉树,对于其中的每个节点,其子节点都大于这个节点,是为小跟堆。大跟堆同理。进行升序排序,首先建立大顶堆,之后每次对堆进行最大堆调整。

最大堆调整:向下调整,每次将一个元素和它的子节点比较,如果子节点中比较大的那个比父节点大,就将父节点和子节点交换,然后以这个大的子节点为父节点,继续向下调整。

建立大顶堆:从最后一个非叶子结点开始,自下而上,自右而左进行最大堆调整。最后一个非叶子结点的下标为len/2-1。

整个过程:首先建立大顶堆,将堆顶和最后一个元素last交换,在对堆顶元素,以及剩下的n-1个元素进行一次最大堆调整,不断调整,直到最后一个元素last下表为0。

算法时间复杂度为O(NlogN)。

public static void swap(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

//最大堆调整
public static void adjust(int[] a, int father, int len) {
    int child = 2 * father + 1;
    while (child < len) {
        if (child + 1 < len && a[child] < a[child + 1]) {
            child++;
        }
        if (a[child] > a[father]) {
            swap(a, child, father);
            //继续向下调整
            father = child;
            child = father * 2 + 1;
        } else {
            break;
        }
    }
}

//建立最大堆
public static void buildHeap(int[] a) {
    int len = a.length;
    for (int i = len / 2 - 1; i >= 0; i--) {
        adjustHeap(a, i, len);
    }
}

public static void heapSort(int[] a) {
    buildHeap(a);
    int len = a.length - 1;
    while (len > 0) {
        //交换最后一个,这里的len是下标
        swap(a, 0, len);
        //调整,这里的len是长度
        adjustHeap(a, 0, len);
        len--;
    }
}

七、二路归并排序

基本思想:采用分治法的思想,将已有序的子序列合并,最终得到一个有序的序列。时间复杂度为O(nlogn)。首先递归地将一个数组分组,知道每个分组的长度为1;再递归地将数组合并成有序的数组。

//将两个分组合并成一个
public static void merge(int[] a, int start, int mid, int end) {
    int[] temp = new int[end - start + 1];
    int i = start, j = mid + 1;
    int index = 0;
    while (i <= mid && j <= end) {
        if (a[i] <= a[j]) {
            temp[index++] = a[i++];
        } else if (a[i] > a[j]) {
            temp[index++] = a[j++];
        }
    }

    //如果左边有剩下的
    while (i <= mid) {
        temp[index++] = a[i++];
    }

    //如果右边有剩下的
    while (j <= end) {
        temp[index++] = a[j++];
    }

    int len = 0;
    while (len < index) {
        a[start++] = temp[len++];
    }
}

//先递归分组,再合并,其中,start和end都是下标,从0开始
public static void mergeSort(int[] a, int start, int end) {
    if (start >= end) {
        return;
    }
    int mid = (start + end) / 2;
    //分组
    mergeSort(a, start, mid);
    mergeSort(a, mid + 1, end);

    //合并
    merge(a, start, mid, end);
}