Java算法——排序算法

1 冒泡排序

以升序为例,冒泡排序的思想是通过比较临近两个值的大小,如果左边值大于右边值,则交换两个值,这样一趟比较下来,最大值就被冒泡到了数组的末尾。
所以对于存在长度为n的数组,需要冒泡n-1次。
第一次冒泡,需要比较n-1次,第二次为n-2次,最后一次为1次。
所以冒泡排序的时间复杂度是O(n^2)。
由于没有使用额外的空间,所以空间复杂度为O(1)。
冒泡排序由于只交换相邻两个值,所以是稳定的排序。
代码示例:
升序:

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

2 插入排序

以升序为例,插入排序就是从第二个数组元素开始将其逐个插入到合适的位置。
如果前面的一个数比当前值大,则前一个数往后挪一位,直到找到正确的位置,将当前元素插入。
插入排序由于是一个个的放入合适的位置,所以是稳定的排序。
插入排序由于没有使用额外的空间,所以空间复杂度是O(1)。
由于每次要插入的对象不知道要比较多少次,但需要插入n-1个元素,每个元素最长可能比较n-1次,所以时间复杂度为O(n^2)。
插入排序在大部分都已经按照当前所要求的排序方式排列的数组排序中占优势,因为每个元素插入时比较的次数较少。
代码示例:
升序:

public static void insertSort(int[] arr) {
    int length = arr.length;
    for (int i = 1; i < length; i++) {
        int index = i - 1;
        int tmp = arr[i];
        while (index >=0 && arr[index] > tmp) {
            arr[index + 1] = arr[index];
            index--;
        }
        arr[index + 1] = tmp;
    }
}

3 折半插入排序

由于插入排序中,处于前面的值已经排好了序,如果要插入的值在很前面的位置的话,会经过很多次的比较。所以可以使用折半查找先在前面有序的集合中找到要插入的位置,然后整体移动后面的元素,并将元素插入。
代码示例:

public static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int tmp = arr[i];
        int l = 0, r = i - 1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if (arr[mid] < tmp) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        for (int j = i - 1; j > r; j--) {
            arr[j + 1] = arr[j];
        }
        arr[r + 1] = tmp;
    }
}

4 选择排序

选择排序的思想是每次遍历选择一个最小值放置到合适配置
所以需要遍历n-1次,第一次n个数,第二次n-1个数,最后一次2个数。
所以选择排序的时间复杂度为O(n^2)。
没有使用额外的空间,空间复杂度为O(1)。
由于找到一个元素后就会和当前位置元素进行交换,所以会打乱顺序,是不稳定的。
代码示例:

public static void selectSort(int[] arr) {
    int minIndex;
    for (int i = 0; i < arr.length - 1; i++) {
        minIndex = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            int tmp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = tmp;
        }
    }
}

5 希尔排序

由于插入排序,在数组趋向于有序的时候更加效率。
希尔排序的思想就是将数组元素进行分组,分组内进行插入排序,然后再合成一个更大的组,此时组内的元素已经相对有序了,再次执行插入排序,能够提高性能。
由于采用对元素分组排序后在排序,后面的数组都是接近于有序的。所以时间复杂度在O(n)和O(n^2)之间。
没有使用额外的空间,空间复杂度为O(1)。
由于是根据分组之后的插入排序,分组后元素顺序会变动,所以不是稳定的。
代码示例:

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

6 桶排序

算法思路就是按个十百千万的顺序依次比较,然后放入对应的桶中。
然后再取出来放到原来的数组中,当所有位数都比较过之后,就得到了有序的数组。
需要注意的是对于负数的处理,由于是根据余数来判断放入哪个桶的,所以只能是正数才行,可以将所有元素减去最小数,然后排序完之后再加上。
桶排需要额外的空间,所以空间复杂度为O(n)
桶排的时间复杂度为O(n)
代码示例:

public static void bucketSort(int[] arr) {
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
        if (arr[i] < min) {
            min = arr[i];
        }
    }
    if (min < 0) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] -= min;
            max -= min;
        }
    }
    int maxLength = (max + "").length();
    int[][] bucket = new int[10][arr.length];
    int[] bucketCount = new int[10];

    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        for (int j = 0; j < arr.length; j++) {
            int div = (arr[j] / n) % 10;
            bucket[div][bucketCount[div]++] = arr[j];
        }

        int index = 0;
        for (int j = 0; j < 10; j++) {
            if (bucketCount[j] > 0) {
                for (int k = 0; k < bucketCount[j]; k++) {
                    arr[index++] = bucket[j][k];
                }
            }
            bucketCount[j] = 0;
        }
    }
    for (int i = 0; i < arr.length; i++) {
        arr[i] += min;
    }
}

7 快排

快排的思想就是选定一个基准值,然后从左遍历一个大于基准值的数,从右遍历一个小于基准值的数,然后两个交换。一次循环下来,基准值左侧都是小于基准值的数,右侧都是大于基准值的数。然后以基准值为界,将其左右两边看做子问题解决。
快排空间复杂度为O(nlogn),主要是递归栈空间的使用。
快排的时间复杂度为O(nlogn)。
代码示例:

public static void quickSort(int[] arr, int left, int right) {
    if (left > right) {
        return;
    }
    int base = arr[left];
    int l = left, r = right;
    while (l < r) {
        while (r > l && arr[r] >= base) {
            r--;
        }
        while (l < r && arr[l] <= base) {
            l++;
        }
        int tmp = arr[l];
        arr[l] = arr[r];
        arr[r] = tmp;
    }
    arr[left] = arr[l];
    arr[l] = base;
    quickSort(arr, left, l - 1);
    quickSort(arr, l + 1, right);
}

8 计数排序

计数排序是计算数组中元素最大值和最小值,确定一个范围,然后定义一个范围大小加1的数组。将数组元素hash到计数数组中,数组元素减去最小值为计数数组的下标,多个相同元素,则计数数组对应下标元素加1。
将计数数组转换成累计数组,就是当前索引处前面有多少个数据。这是为了方便后面得到最终结果。
遍历原数组,根据累计数组,将原数组数据映射到新数组,完成排序。
计数排序3次遍历数组,并需要遍历数据范围,所以时间复杂度为O(n+k)(实际为3n+k,k为范围长度)。
创建了一个范围数组,所以空间复杂度为O(k)。
代码示例:

public static int[] countSort(int[] arr) {
    int min = arr[0];
    int max = arr[0];
    for (int j : arr) {
        if (j > max) {
            max = j;
        }
        if (j < min) {
            min = j;
        }
    }

    int range = max - min + 1;
    int[] count = new int[range];
    for (int j : arr) {
        count[j - min]++;
    }

    for (int i = 1; i < range; i++) {
        count[i] += count[i - 1];
    }

    int[] res = new int[arr.length];
    for (int i = arr.length - 1; i >= 0; i--) {
        int index = arr[i] - min;
        //count[index]处表示小于等于这个数的个数,减一后正好是排序后的次序减一,为排序后的数组索引
        count[index]--;
        res[count[index]] = arr[i];
    }
    return res;
}

9 堆排序

堆排序将数组看成一个堆,然后将数组调整成大顶堆或小顶堆。
大致可分为以下步骤:

  • 调整数组成为堆
  • 取堆顶元素与数组末尾元素交换,此时的末尾元素已经是最大(大顶堆)或者最小(小顶堆)
  • 重新调整堆
  • 重复取元素
    堆排序建堆时间为O(n),之后有n-1次向下调整操作,每次调整时间为O(h),最好最坏和平均下,时间复杂度都是O(nlogn)
    堆排序的空间复杂度为O(1)
    由于存在元素交换,属于选择排序的一种,所以非稳定
    代码示例:
public static void adjustHeap(int[] arr, int length, int node) {
    int tmp = arr[node];

    for (int i = node * 2 + 1; i < length; i = 2 * i + 1) {
        if (i + 1 < length && arr[i] < arr[i + 1]) {
            i++;
        }
        if (arr[i] > tmp) {
            arr[node] = arr[i];
            node = i;
        } else {
            break;
        }
    }
    arr[node] = tmp;
}

public static void heapSort(int[] arr) {
    for (int i = arr.length / 2 - 1; i >= 0 ; i--) {
        adjustHeap(arr, arr.length, i);
    }
    int tmp = 0;
    for (int i = arr.length - 1; i > 0; i--) {
        tmp = arr[0];
        arr[0] = arr[i];
        arr[i] = tmp;
        adjustHeap(arr, i, 0);
    }
}

10 归并排序

将数组细分,直至分为1个元素的,然后两个1个元素的数组排序后成为2个元素的数组,再将两个2个元素数组排序成一个4个元素的有序数组。
归并排序的时间复杂度为O(nlogn)
归并排序的空间复杂度为O(n)
归并排序是稳定的排序
代码示例:

public static void mergeSort(int[] arr, int left, int right, int[] tmp) {
    if (left >= right) {
        return;
    }
    int mid = left + (right - left) / 2;
    mergeSort(arr, left, mid, tmp);
    mergeSort(arr, mid + 1, right, tmp);
    merge(arr, left, mid, right, tmp);
}

public static void merge(int[] arr, int left, int mid, int right, int[] tmp) {
    int l = left;
    int r = mid + 1;
    int index = 0;
    while (l <= mid && r <= right) {
        if (arr[l] <= arr[r]) {
            tmp[index++] = arr[l++];
        } else {
            tmp[index++] = arr[r++];
        }
    }
    while (l <= mid) {
        tmp[index++] = arr[l++];
    }
    while (r <= right) {
        tmp[index++] = arr[r++];
    }
    index = 0;
    for (int i = left; i <= right; i++) {
        arr[i] = tmp[index++];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值