排序算法总结(你想要的排序,这里都有)

排序算法的总结

首先,对于所有的排序做一个总结,随后是所有排序的实现方式

排序算法的稳定性

稳定性是指同样大小的样本再排序不会改变次序
对于基础类型的排序
对于非基础类型的排序来说,稳定性有重要的意义(比如结果需要年龄有序并且年龄相等身高有序)
有些排序算法可以实现稳定性,有的排序算法无论如何都无法实现稳定

速记时间复杂度额外空间复杂度稳定性
选择排序最小最前O(N²)O(1)
冒泡排序最大最后O(N²)O(1)
插入排序逐步有序O(N²)O(1)
归并排序二分递归O(logN*N)O(N)
随机快排确认中点O(logN*N)O(logN)
堆排序建立堆结构O(logN*N)O(1)
桶排序实现
计数排序几个元素几个桶O(N)O(M)
基数排序O(N)O(1)
  1. 不基于比较的排序(桶排序),对样本的数据有严格的要求,不易改写
  2. 基于比较的排序,只要规定两个样本怎么比较大小就能够复用
  3. 基于比较的排序,算法的时间复杂度的极限是N*logN
  4. 时间复杂度为O(logN*N),空间复杂度低于O(N),且具有稳定性的算法是不存在的
  5. 为了绝对的速度,选择快速排序,为了省空间(常数时间操作长),选择堆排序,为了稳定性,选择归并排序

选择排序

  1. arr[0-N-1]范围上,找到最小值所在的位置,然后把最小值交换到0位置
  2. arr[1-N-1]范围上,找到最小值所在的位置,然后把最小值交换到1位置
  3. arr[N-1~N-1]范围上,找到最小值位置,然后把最小值交换到N-1位置。
    时间复杂度估计
    在这里插入图片描述
    总操作数为等差数列
    算出快排的时间复杂度为 n²/2 +n/2 其中忽略低阶项和高阶项系数得出时间负载度为O(N²)
代码实现
    public static void solution(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            int index = i;
            for (int j = i; j < arr.length; j++) {
                if (arr[j] < arr[index]) {
                    index = j;
                }
            }
            swap(arr, i, index);
        }
    }

冒泡排序

在arr[0~N-1]范围上:

  1. arr[0]和arr[1],谁大谁来到1位置;arr[1]和arr[2],谁大谁来到2位置…arr[N-2]和arr[N-1],谁大谁来到N-1位置

  2. 在arr[0~N-2]范围上,重复上面的过程,但最后一步是arr[N-3]和arr[N-2],谁大谁来到N-2位置

  3. 在arr[0~N-3]范围上,重复上面的过程,但最后一步是arr[N-4]和arr[N-3],谁大谁来到N-3位置

  4. 最后在arr[0~1]范围上,重复上面的过程,但最后一步是arr[0]和arr[1],谁大谁来到1位置

    public static void solution(int[] arr) {
        //在 0 -> N-1 看自己和下一个数那个小,大的往后面移动
        //0 ->N-2
        //0 ->N-3.....
        for (int i = arr.length - 1; i >= 0; i--) {
            for (int j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                }
            }
        }
    }
   	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
    

插入排序

  1. 想让arr[0~0]上有序,这个范围只有一个数,当然是有序的。
  2. 想让arr[0~1]上有序,所以从arr[1]开始往前看,如果arr[1]<arr[0],就交换。否则什么也不做。
  3. 想让arr[0~i]上有序,所以从arr[i]开始往前看,arr[i]这个数不停向左移动,一直移动到左边的数字不再比自己大,停止移动。
  4. 最后一步,想让arr[0~N-1]上有序, arr[N-1]这个数不停向左移动,一直移动到左边的数字不再比自己大,停止移动。

估算时发现这个算法流程的复杂程度,会因为数据状况的不同而不同。

如果数据是{1,2,3,4,5,6,7}这种本来就有序的数据集,那么时间复杂度是O(N),但是最差情况的时间复杂度是等差数列,所以时间复杂度为O(N²)

	public static void insertionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// 0~0 有序的
		// 0~i 想有序
		for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序
			for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
				swap(arr, j, j + 1);
			}
		}
	}
   	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

归并排序

  1. 整体是递归,左边先排好序+右边排好序+merge让整体有序
  2. 让整体有序的过程采用了排外序的方法
  3. 利用master公式来求时间复杂度
  4. 当然可以用非递归实现

时间复杂度为O(N*logN)

	// 递归方法实现
	public static void mergeSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process(arr, 0, arr.length - 1);
	}

	// arr[L...R]范围上,变成有序的
	// L...R    N    T(N) = 2*T(N/2) + O(N)  ->
	public static void process(int[] arr, int L, int R) {
		if (L == R) { // base case
			return;
		}
		int mid = L + ((R - L) >> 1);
		process(arr, L, mid);
		process(arr, mid + 1, R);
		merge(arr, L, mid, R);
	}

	public static void merge(int[] arr, int L, int M, int R) {
		int[] help = new int[R - L + 1];
		int i = 0;
		int p1 = L;
		int p2 = M + 1;
		while (p1 <= M && p2 <= R) {
			help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
		}
		// 要么p1越界了,要么p2越界了
		while (p1 <= M) {
			help[i++] = arr[p1++];
		}
		while (p2 <= R) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[L + i] = help[i];
		}
	}
   	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

快速排序

partition过程

给定一个数组arr,和一个整数num。请吧小于等于num的数放在数组的左边,大于num的数放在数组的右边。

在arr[L…R]范围上,进行快速排序的过程

  1. 在这个范围上,随机选一个数为num
  2. 用num对该范围进行partition。==num的范围为[a,b]
  3. 对arr[L…a-1]进行快速排序(递归)
  4. 对arr[b+1…R]进行快速排序(递归)
随机快排时间复杂度
  1. 通过分析知道,划分值约靠近中间,性能越好,越靠近两边,性能越差
  2. 随机选一个数划分的目的是把好和差变成概率事件
  3. 每一种情况都列出,会有每种情况的时间复杂度,概率都为1/N
  4. 所有情况都考虑,那么这种模型的长期期望就是时间复杂度

时间复杂度为O(N*logN) 额外的空间复杂度为O(logN)

	public static void quickSort3(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process3(arr, 0, arr.length - 1);
	}

	public static void process3(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
		int[] equalArea = netherlandsFlag(arr, L, R);
		process3(arr, L, equalArea[0] - 1);
		process3(arr, equalArea[1] + 1, R);
	}
	
	//  <arr[R]  ==arr[R]  > arr[R]
	public static int[] netherlandsFlag(int[] arr, int L, int R) {
		if (L > R) {
			return new int[] { -1, -1 };
		}
		if (L == R) {
			return new int[] { L, R };
		}
		int less = L - 1; // < 区 右边界
		int more = R;     // > 区 左边界
		int index = L;
		while (index < more) {
			if (arr[index] == arr[R]) {
				index++;
			} else if (arr[index] < arr[R]) {
				swap(arr, index++, ++less);
			} else { // >
				swap(arr, index, --more);
			}
		}
		swap(arr, more, R);
		return new int[] { less + 1, more };
	}
   	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

堆排序

堆结构
  1. 堆结构就是用数组实现的一颗完全二叉树
  2. 完全二叉树中如果每颗字树的最大值都在顶部就是大根堆
  3. 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
  4. 堆结构的heapInsert与heapify操作
  5. 堆结构的增大和减少
  6. 优先级队列结构就是堆结构
  public static void solution(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        //时间复杂度O(N*logN)
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }
        int heapSize = arr.length;
        swap(arr,0,--heapSize);
        //最大的数一直在数组第一个,然后把她与最后一个数交换,堆的大小减一
        while (heapSize > 0) { // O(N)
            heapify(arr, 0, heapSize); // O(logN)
            swap(arr, 0, --heapSize); // O(1)
        }

    }

    // arr[index]位置的数,能否往下移动
    public static void heapify(int[] arr, int index, int heapSize) {
        int left = index * 2 + 1; // 左孩子的下标
        while (left < heapSize) { // 下方还有孩子的时候
            // 两个孩子中,谁的值大,把下标给largest
            // 1)只有左孩子,left -> largest
            // 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest
            // 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            // 父和较大的孩子之间,谁的值大,把下标给largest
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }


    // arr[index]刚来的数,往上
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }


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

    public static void printArrat(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] +"\t");
        }
    }

桶排序

桶排序思想下的排序:计数排序和基数排序

  1. 桶排序思想下的排序都是不基于比较的排序
  2. 时间复杂度为O(N),额外空间复杂度为O(M)
  3. 应用范围有限,需要更具样本的数据状况满足桶的划分

计数排序

根据年龄排序,建立1-200 的桶,将对应的元素放入对应的桶中。通过保证放入顺序保持稳定性

    /**
     * 计数排序只适合值的范围比较小的排序
     * 根据需要排序的数组的最大值,生成一个数组,将原数组中的值放入生成数组中对应索引的位置
     * 适合场景,排序年龄,根据年龄排序(使用对应多个桶)
     *
     * @param arr
     */
    public static void solution(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int max = 0;
        for (int i = 0; i < arr.length; i++) {
            max = Math.max(max, arr[i]);
        }
        int[] help = new int[max + 1];
        for (int i = 0; i < arr.length; i++) {
            help[arr[i]] += 1;
        }
        int index = 0;
        for (int i = 0; i < help.length; i++) {
            for (int j = 0; j < help[i]; j++) {
                arr[index++] = i;
            }
        }
    } 

基数排序

对10进制的数字进行排序

 public static void solution(int[] arr, int L, int R, int dight) {
        int i = 0, j = 0;
        int[] help = new int[R - L + 1];
        //数字有多少位就使用多少次循环
        for (int d = 1; d <= dight; d++) {
            int[] cout = new int[10];
            //使用cout记录每一位是对应数字的有多少
            for (i = L; i < R + 1; i++) {
                j = getDigit(arr[i], d);
                cout[j]++;
            }
            //把cout变为小于等于该值的有多少
            for (int k = 1; k < cout.length; k++) {
                cout[k] += cout[k - 1];
            }
            //从后往前,如果该位是对应数字的话,使用cout中的值,把该数放在最大的位置上,因为是从后往前,所以保证了稳定性
            for (int k = R; k >= L; k--) {
                int a = getDigit(arr[k], d);
                help[cout[a] - 1] = arr[k];
                cout[a]--;
            }
            //把help的值赋给原数组
            for (int k = L, c = 0; k <=  R; k++, c++) {
                arr[k] = help[c];
            }
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值