1 冒泡排序
思想:每一轮两两比较,逆序就交换,类似水泡冒出,每一轮都能找到一个当前最大的。
public static void sort(int[] a){
for(int i=0;i<a.length-1;i++){
for(int j=0;j<a.length-1-i;j++){
if(a[j]>a[j+1]){
int temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
}
}
- 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 2 ) O(n^2) O(n2)。最好本来是 O ( n 2 ) O(n^2) O(n2)但是可以加一个判断上一趟是否进行了交换,如果不交换说明是有序的,直接返回。
- 空间复杂度: O ( 1 ) O(1) O(1)
- 是否稳定:是
2 简单选择排序
思想:每一轮都需要找到当前轮的最值元素,然后把最值元素和它交换位置。
public static void sort(int[] a) {
int temp;
for (int i = 0; i < a.length; i++) {
int minPos = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[minPos]) {
minPos = j;
}
}
//交换
temp = a[minPos];
a[minPos] = a[i];
a[i] = temp;
}
}
- 时间复杂度:最好 O ( n 2 ) O(n^2) O(n2),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度: O ( 1 ) O(1) O(1)
- 是否稳定:否(可能在每一轮交换的时候打乱)
3 直接插入排序
思想:每一轮都把当前数字插入到有序数组的尾巴,然后和前面那个比较,依次两两换位,直到找到正确位置。
public static void sort(int[] a) {
for (int i = 1; i < a.length; i++) {
for (int j = i; j >= 1; j--) {
if (a[j - 1] > a[j]) {
int temp = a[j - 1];
a[j - 1] = a[j];
a[j] = temp;
} else break;
}
}
}
- 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度: O ( 1 ) O(1) O(1)
- 是否稳定:是
4 希尔排序(改进的直接插入排序)
思想:改进的直接插入排序,对数组进行分组,开始组多元素少,最后组少元素多,序列逐渐成形。
public static void sort(int[] a) {
//最外层,增量(数组分成的组数),每次减半
for (int gap = a.length / 2; gap > 0; gap /= 2) {
//对gap分的每一组都进行直接插入排序
for (int i = gap; i < a.length; i++) {
//对每一组进行插入排序,注意,对比的上一个元素是:j-gap(而直接插入排序是j--)
for (int j = i; j > 0; j -= gap) {
if (a[j - 1] > a[j]) {
int temp = a[j - 1];
a[j - 1] = a[j];
a[j] = temp;
} else break;
}
}
}
}
- 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 1.3 ) O(n^{1.3}) O(n1.3)。
- 空间复杂度: O ( 1 ) O(1) O(1)
- 是否稳定:否(可能在每一轮交换的时候打乱)
5 堆排序
借鉴:图解排序算法(三)之堆排序
堆:具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。(注意左右孩子节点值没有明确要求谁大谁小);
堆排序思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了;
注意:大(小)顶堆都是逻辑结构,其实现可以是树或者数组,由于完全二叉树的特性,采用数组实现较为方便。
数组实现完全二叉树性质:
- 第一个非叶子节点位置: a r r a y . l e n g t h / 2 − 1 array.length / 2 - 1 array.length/2−1
- 位置 i i i 的左右孩子节点位置: 2 i + 1 2i+1 2i+1、 2 i + 2 2i+2 2i+2
//堆排序
public static void sort(int[] arr) {
//1.构建大顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for (int j = arr.length - 1; j > 0; j--) {
//顶和尾元素交换
int temp = arr[0];
arr[0] = arr[j];
arr[j] = temp;
//只需要把顶元素下沉,调整顶堆
adjustHeap(arr, 0, j);
}
}
//调整大顶推,目的是找到arr[i]这个元素在新的大顶堆(length长度数组)的位置(大于左右子节点值即可)
public static void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i];//先取出当前元素i
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i结点的左子结点开始,也就是2i+1处开始
if (k + 1 < length && arr[k] < arr[k + 1]) {//如果左子结点小于右子结点,k指向右子结点
k++;
}
if (arr[k] > temp) {//如果子节点大于父节点,将子节点值赋给父节点
arr[i] = arr[k];
i = k;//k就变成了下一个父节点
} else {//两个子节点都小于等于父节点i,满足要求,break出去
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
- 时间复杂度:最好 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),最坏 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),平均 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)。
- 空间复杂度: O ( 1 ) O(1) O(1)
- 是否稳定:否(可能顶堆调整的时候打乱)
6 快速排序
思想:利用分治策略,首先找到某个元素的最终位置(元素左边比它小,右边比它大),然后再对元素左边和右边的数组进行同样的处理。
治思想,把数组low到high排序好
private static void sort(int[] arr, int low, int high) {
//注意边界条件
if (low < high) {
int index = getIndex(arr, low, high);//把arr[low]元素放到最终的位置并返回index
sort(arr, low, index - 1);//分治左边
sort(arr, index + 1, high);//分治右边
}
}
//把arr[low]元素放到最终的位置并返回index
private static int getIndex(int[] arr, int low, int high) {
// arr[low]需要处理的元素
int tmp = arr[low];
while (low < high) {
// 从数组尾开始,要是满足就继续向首部看
while (low < high && arr[high] >= tmp) {
high--;
}
// 不满足,就要把这个元素移到low
arr[low] = arr[high];
// 然后从前往后看,满足就继续向后
while (low < high && arr[low] <= tmp) {
low++;
}
// 不满足,就把这个元素移动到high
arr[high] = arr[low];
}
// 跳出循环时low和high相等,把tmp放到这个位置,并返回该位置
arr[low] = tmp;
return low;
}
- 时间复杂度:最好 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)。
- 空间复杂度: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
- 是否稳定:否(可能在获取某个元素排序时打乱)
7 归并排序
思想:标准的分治策略,把任务拆成两个排序子任务,然后合并两个有序数组。
//把从L到R的数组排好序
public static void sort(int[] arr, int L, int R) {
//递归结束条件,
if (L == R) {
return;
}
int mid = (L + R) / 2;//中间位置
sort(arr, L, mid);
sort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
//合并L到mid 和mid到R 两个排好序的数组
public static void merge(int[] arr, int L, int mid, int R) {
int[] temp = new int[R - L + 1];//开辟空间存放合并后的结果
int i = 0;//合并的index
int p1 = L;//第一个数组的首指针
int p2 = mid + 1;//第二个数组的首指正
// 比较左右两部分的元素,哪个小,把那个元素填入temp中
while (p1 <= mid && p2 <= R) {
if(arr[p1]<arr[p2]){
temp[i++]=arr[p1++];
}
else {
temp[i++]=arr[p2++];
}
}
// 上面的循环退出后,把剩余的元素依次填入到temp中
while (p1 <= mid) {
temp[i++] = arr[p1++];
}
while (p2 <= R) {
temp[i++] = arr[p2++];
}
// 把最终的排序的结果复制给原数组
for (i = 0; i < temp.length; i++) {
arr[L + i] = temp[i];
}
}
- 时间复杂度:最好 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),最坏 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),平均 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)。
- 空间复杂度: O ( n ) O(n) O(n)
- 是否稳定:是(合并的时候,可以考虑相同元素的合并原则)
8 计数排序
原理:根据数组元素的值,把这个值放到新开辟空间进行计数。(哈希思想)
public static void sort(int[] array) {
//1.得到数列的最大、最小值
int max = array[0];
int min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max)
max = array[i];
if (array[i] < min)
min = array[i];
}
//2.根据数列的最大值确定统计数组的长度
int[] coutArray = new int[max - min + 1];
//3.遍历数列,填充统计数组
for (int i = 0; i < array.length; i++)
coutArray[array[i] - min]++;
System.out.println(Arrays.toString(coutArray));
//4.根据统计表把排序后的值返回给数组
int index = 0;
for (int i = 0; i < coutArray.length; i++) {
for (int j = 0; j < coutArray[i]; j++) {
array[index] = i + min;
index++;
}
}
}
- 时间复杂度:最好 O ( n + k ) O(n+k) O(n+k),最坏 O ( n + k ) O(n+k) O(n+k),平均 O ( n + k ) O(n+k) O(n+k)。
- 空间复杂度: O ( n + k ) O(n+k) O(n+k)
- 是否稳定:是(相同的数,有先后的加入计数数组的顺序)
9 桶排序
思想:桶排序是计数排序的扩展,把数据按照某个规则进行分桶操作,然后在桶内使用其他排序算法,最后遍历每个桶和桶内元素,就可以得到一个有序的数组了。
static void sort(int[] arr){
// 计算最大值与最小值
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
// 计算桶的数量
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
// 将每个元素放入桶
for(int i = 0; i < arr.length; i++){
int num = (arr[i] - min) / (arr.length);
bucketArr.get(num).add(arr[i]);
}
// 对每个桶进行排序
for(int i = 0; i < bucketArr.size(); i++){
Collections.sort(bucketArr.get(i));
}
// 将桶中的元素赋值到原序列
int index = 0;
for(int i = 0; i < bucketArr.size(); i++){
for(int j = 0; j < bucketArr.get(i).size(); j++){
arr[index++] = bucketArr.get(i).get(j);
}
}
}
- 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n + k ) O(n+k) O(n+k)。
- 空间复杂度: O ( n + k ) O(n+k) O(n+k)
- 是否稳定:是(相同的数加入同一个桶,可以记录顺序)
10 基数排序
思想:也是计数排序的一种扩展,比如对一些1000以内的数进行排序,先将其按照个位分桶,然后展开,再按照十位分桶,然后展开,最后按照百位分桶,最后展开,就可以得到一个有序的数组。
public static void sort(int[] number, int d) {
int k = 0;
int n = 1;
int m = 1; //控制键值排序依据在哪一位
int[][]temp = new int[10][number.length]; //数组的第一维表示可能的余数0-9
int[]order = new int[10]; //数组orderp[i]用来表示该位是i的数的个数
while(m <= d) {
for(int i = 0; i < number.length; i++) {
int lsd = ((number[i] / n) % 10);
temp[lsd][order[lsd]] = number[i];
order[lsd]++;
}
for(int i = 0; i < 10; i++) {
if(order[i] != 0)
for(int j = 0; j < order[i]; j++)
{
number[k] = temp[i][j];
k++;
}
order[i] = 0;
}
n *= 10;
k = 0;
m++;
}
}
- 时间复杂度:最好 O ( n ∗ k ) O(n*k) O(n∗k),最坏 O ( n ∗ k ) O(n*k) O(n∗k),平均 O ( n ∗ k ) O(n*k) O(n∗k)。
- 空间复杂度: O ( n + k ) O(n+k) O(n+k)
- 是否稳定:是(相同的数加入同一个桶,可以记录顺序)
总结(图片来自网络)
sorry,忘记了是哪里粘的了,作者请见谅。