冒泡排序
图解
代码
import java.util.Arrays;
/**
* 冒泡排序(也叫下沉排序)
* 从前往后,两两比较,大的往后走(像石头沉底),小的往前走(像气泡浮出水面)
* 时间复杂度:O(N^2) (两层for循环)
* 空间复杂度:O(1) (tmp)
* 稳定性:稳定
* (1 2 1' 3 5 在排序前后1和1'相对位置不发生变化)
* 优化:
* 当本身有序:1 2 3 4 5
* 第一趟,不会进入if1 2 3 4 5
* 1 2 3 4
* 1 2 3
* 1 2
*
* 0 3 6 8 10
*/
public class Bubble {
//注意方法的修饰符
public static void BubbleSort(int[] array){
if(array == null || array.length == 0){
return;
}
//趟数
for(int i=0; i<array.length-1; i++){
//一趟冒泡的过程 ii
// j是要处理的元素下标
// array.length-1是为了j+1不越界,
// i是因为第i趟有(0到i-1共)i个已到最终位置
for(int j=0; j<array.length-1-i; j++){
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
// 优化(当序列本身有序: 1 2 3 4 5 不用移动)
public static void optimizedBubbleSort(int[] array){
if(array == null || array.length == 0){
return;
}
//趟数
boolean needNextSort = true;//必须初始化,先为真,才能进入到下面的for
for(int i=0; i<array.length-1 && needNextSort; i++){
//一趟冒泡的过程
needNextSort = false;//先假定不需要下一次排序
for(int j=0; j<array.length-1-i; j++){
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
needNextSort = true;
}
}
}
}
public static void main(String[] args) {
int[] array = {10, 6, 8, 2, 3, 0, 10, 15, 20, 7, 4};
//BubbleSort(array);
optimizedBubbleSort(array);
System.out.println(Arrays.toString(array));
}
}
选择排序
分析具体的所有情况,进行基本的代码实现;
进一步的总结统一规律,进行逻辑优化
图解
代码
import java.util.Arrays;
/**
* 选择排序
* (默认都是从小到大的顺序)
* 从当前 待排序列 中选择最 小 值(或最大值)与第 一 个值(或最后一个值进行交换)(等于时不交换)
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:不稳定
* 2 3 2' 0 4 => 0 3 2' 2 4 =>。。。=>0 2' 2 3 4
*/
public class Select {
public static void selectSort(int[] array){
if(array == null || array.length == 0){
return;
}
//最后一趟剩一个就不用比了
for(int i=0; i<array.length-1; i++){
int minIndex = i;//指向最小的下标
// 第i趟时,前面的0...i-1都已经就位了
for(int j=i+1; j<array.length; j++){
if(array[j] < array[minIndex]){ //等于时不交换
minIndex = j;//更新当前最小
}
}
if(minIndex != i){
int tmp = array[minIndex];
array[minIndex] = array[i];
array[i] = tmp;
}
}
}
// O(log N) 每一趟即选择最小与第一个交换,又选择最大与在最后一个交换
public static void optimizedselectSort(int[] array){
if(array == null || array.length == 0){
return;
}
//次数
for(int i=0; i<array.length/2; i++){
int minIndex = i;//(因前面0.。。i-1已就位)
int maxIndex = i;
//选择排序的过程
for(int j=i+1; j<=array.length-1-i; j++){
if(array[j] > array[maxIndex]){
maxIndex = j;//维护指针,先不移动
continue; //比max大时,肯定比min小不成立,所以进入到下一轮
}
if(array[j] < array[minIndex]){
minIndex = j;
}
}
//minIndex保存当前待排序列的最小值 maxIndex保存当前待排序列最大值
//i保存当前最小值要放的位置 array.length-1-i保存当前最大值要放的位置
int tmp = array[i];
array[i] =array[minIndex];
array[minIndex] = tmp;
//因为max原本和min指向同一个位置i,有可能i位置是当前最大值,此时若把i位置元素和min交换,应该把max指向当前最大值的新位置
if(maxIndex == i){
maxIndex = minIndex;
}
tmp = array[array.length-1-i];//i==maxIndex, 当前最大
array[array.length-1-i] = array[maxIndex];
array[maxIndex] = tmp;
}
}
public static void main(String[] args) {
int[] array = {10, 5, 3, 1, 0, 7, 9, 8, 2};
// selectSort(array);
optimizedselectSort(array);
System.out.println(Arrays.toString(array));
}
}
直接插入排序
图解
代码
import java.util.Arrays;
/**
* 直接插入排序
* 模拟打扑克,从 待排序列 中拿数据(接牌)tmp,
* 从 手中数据(当前已经排好的牌) 中从前往后找 第一个大于tmp的,插到其前面(放牌);如果前面都比他小,就直接追加到最后面
*
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
public class Insert {
public static void insertSort(int[] array){
if(array == null || array.length == 0){
return;
}
//拿(数据)牌
//第i趟,有0...i-1个已就位
for(int i=0; i<array.length; i++){
int tmp = array[i];//要插入的元素
int j=0;//要插入的位置(写到这里,为了考虑他的作用域!!!)
for(; j<i; j++){ // 在已经有序的共前i个元素里,找第一个 大于 的位置
if(array[j] > tmp){
//挪数据,插数据 这一条件有可能进入有可能不进入
break;
}
}
//挪数据过程(用了同一个数组 而且 从后往前移 )
for(int k=i-1; k>=j; k--){
array[k+1] = array[k];
}
//放数据
array[j] = tmp;
}
}
public static void optimizedInsertSort(int[] array){
if(array == null || array.length == 0){
return;
}
//拿(数据)牌
for(int i=0; i<array.length; i++){
int tmp = array[i];//要放的元素
//从后往前 边找边移(合并上面算法的两个for循环)
int j;
for(j=i-1; j>=0; j--){
if(array[j] > tmp){ //
array[j+1] = array[j];//j位置元素后移一个;
}else{
//第一次 发现要插入位置的元素 比 要插入的元素大,放到他后面
break;//不执行j--了
}
}
//放数据
array[j+1] = tmp;
}
}
public static void main(String[] args) {
int[] array = {10, 2, 9, 0, 5, 3, 8, 7, 12, 6};
//insertSort(array);
optimizedInsertSort(array);
System.out.println(Arrays.toString(array));
}
}
归并排序
图解
代码
非递归
import java.util.Arrays;
/**
* 归并排序
* 思想: 把两个有序的序列合并为一个有序的序列
* 如何保证两个序列是有序的? 两个序列中元素个数为1
* 时间复杂度:O(N*log2N) (log2 N趟,每趟遍历所有元素)
* 空间复杂度:O(N) (tmp)
* 稳定性:稳定
*/
public class Merge {
public static void merge(int[] array, int gap){
//左归并段首末
int left1 = 0;
int left2 = left1 + gap -1;//比如长为2,就是0..1
//右归并段首末
int right1 = left2+1;
int right2 = (right1 + gap -1 < array.length-1) ? right1 + gap -1 : array.length-1;
// 存放每一轮的结果
int[] tmp = new int[array.length];
int index = 0;
//不断两两归并,把所有元素都过一遍
//情况1:只要右边归并段 存在 就可以
while(right1 < array.length){
//合并两个有序的归并段(当两个归并段 中的任一段都没合并完时)
while(left1 <= left2 && right1 <= right2){
//左段的最小 和 右段的最小比
if(array[left1] <= array[right1]){
tmp[index++] = array[left1++];
}else{
tmp[index++] = array[right1++];
}
}
//退出上面的while条件说明已经有一个归并段已经提前一步归并完成
while(left1 <= left2){
tmp[index++] = array[left1++];
}
while(right1 <= right2){
tmp[index++] = array[right1++];
}
//index 在每一轮merge里都是一直++,往右走
//更新 归并段的起始和结束位置,准备下一轮while
left1 = right2+1;
left2 = left1 + gap - 1;
right1 = left2+1;
right2 = (right1 + gap -1 < array.length-1) ? right1 + gap -1 : array.length-1;
}
//情况2:只有一个归并段(可能它还不够一个段的长度 ; 相当于最右边的一个段 落单了,不能配对),特殊处理
while(left1 < array.length){
tmp[index++] = array[left1++];
}
//把临时数组结果 转移给array
for(int i=0; i<array.length; i++){
array[i] = tmp[i];
}
// array=tmp;// 发现array没得到tmp的值,只可应用,不可修改
}
public static void mergeSort(int[] array){
if(array == null || array.length == 0){
return;
}
//log2N趟,可保证 有序序列长度 > 元素总个数
for(int i=1; i<array.length; i*=2){
merge(array, i);//i即 有序段长度
}
}
public static void main(String[] args) {
int[] array = {10, 15, 2, 10, 18, 3, 5, 7, 20, 19, 35, 23, 1};
mergeSort(array);
System.out.println(Arrays.toString(array));
}
}
递归
import java.util.Arrays;
public class Merge {
//==================mergesort=================
private static void mergeSort(int[] arr){
int[] temp = new int[arr.length]; //存放临时结果
mergeSort(arr, temp, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
private static void mergeSort(int[] arr, int[] temp, int l, int r){
if (l == r) return;
int mid = l + (r-l)/2;//如 0 +(3-0)/2=1
mergeSort(arr, temp, l, mid); //递归调自己,层层拆分 左闭 右闭
mergeSort(arr, temp, mid + 1, r);
merge(arr, temp, l ,mid, r);// 合并
}
private static void merge(int[] arr, int[] temp, int l, int mid, int r){
for (int i = 0 ; i < arr.length; i ++){
temp[i] = arr[i];
}
int p1 = l;
int p2 = mid + 1;
int k = l;// 用于 遍历 这两个小段的每个元素 以进行 有序合并
// 直到p1 或 p2 到边界了
while(p1 <= mid && p2 <= r){ //将两个段 中 当前比较位置的 较小元素 先放到array
if (temp[p1] < temp[p2]){
arr[k] = temp[p1];
p1++;
}else{
arr[k] = temp[p2];
p2++;
}
k++;
}
while(p1 <= mid){
arr[k] = temp[p1];
p1++;
k++;
}
while(p2 <= r){
arr[k] = temp[p2];
p2++;
k++;
}
}
public static void main(String[] args) {
int[] nums={0,3,2,6,5,8,9,4,7};
mergeSort(nums);
}
}
快速排序(重要,不稳定)
图示
一次划分:
代码
import java.util.Arrays;
import java.util.Stack;
/**
* 快速排序
* 思想:
* 在待排序列中确定一个 基准mar,一般来说array[0], 然后确定指针low,high分别指向待排序列的第一个元素和最后一个元素,
* 1.从后往前比较array[high]和mar,
* 如果high位置元素大于基准,high继续往前(左)走;
* 如果high位置元素小于基准,high表示的元素 移动 至前面位置;
* 2.从前往后比较array[low]和mar,
* 如果low位置元素小于基准,low继续往后(右)走;
* 如果low位置元素大于基准,low表示的元素 移动 至后面位置,
* 重复1.2操作,直到low high相等,然后就把基准放在这里
* 以上为一次划分,最终的结果为:基准的左边都是比它小的元素,基准的右边都是比它大的元素;
* 针对基准左右两边的序列 继续 进行一次更细的划分,最终即可得到一个完全有序的序列
*
* 时间复杂度:O(N * log2N) (n次(指数量级)quick调自己,二分(log2N)子序列然后处理partition)
* 空间复杂度:O(log2N) (将当前序列一分为二,然后递归调自己(quick) 主要是递归造成的栈空间的使用,平均情况,递归树的深度为log2N)
* 稳定性:不稳定
* 2 1 1’=》1' 1 2
*
* 先写代码框架,然后实现细节
*/
public class Quick {
// 上面的函数被下面的函数调用
// 划分
public static int partition(int[] array, int low, int high){
int mar = array[low];
while(low < high){
//从后往前找比基准小的
//如果比基准大 high--
while(low < high && array[high] >= mar){
high--;
}
if(low == high){
break;
}
if(array[high] < mar){
array[low] = array[high];
}
//从前往后找比基准大的
//如果比基准小 low++
while(low < high && array[low] <= mar){
low++;
}
if(low == high){
break;
}
if(array[low] > mar){
array[high] = array[low];
}
}
//此时low 和 high 相等
array[low] = mar;// 此时 基准值位置 左边的全都比他小,右边的都比他大
return low;//虽然没有传出array,但却真实的改变了数组元素位置
}
public static void quick(int[] array, int low, int high){
if(low >= high){
return;
}
int mar = partition(array, low, high);
//重复逻辑
//mar是位置 基准左边序列个数>=2 才用继续划分
if(mar-1 > low){
quick(array, low, mar-1);//递归调自己
}
//基准右边序列个数>=2 继续划分
if(mar+1 < high){
quick(array, mar+1, high);
}
}
//实现1(马甲函数)
public static void quickSort(int[] array){
//合法性检查
if(array == null || array.length == 0){
return;
}
//真正主体
quick(array, 0, array.length-1);
}
//实现2(合并了quick和quicksort,没有了quick递归调用自己)
public static void quickByWhile(int[] array){
if(array == null || array.length == 0){
return;
}
Stack<Integer> stack = new Stack<>();
stack.push(0);//压入low的下标
stack.push(array.length-1);//high
//左右子序列不push时,栈就为空了
while(!stack.isEmpty()){
//初始
int high = stack.pop();
int low = stack.pop();
int mar = partition(array, low, high);
//判断左边序列是否需要进行划分 进行划分序列的起始结束位置继续入栈
if(mar-1 > low){
//重新指定界限
stack.push(low);
stack.push(mar-1);
}
//判断右边序列是否需要进行划分 进行划分序列的起始结束位置继续入栈
if(mar+1 < high){
stack.push(mar+1);
stack.push(high);
}
}
}
public static void main(String[] args) {
int[] array = {8, 9, 2, 2, 10, 15, 8, 7, 19,0, 2, 13};
//quickSort(array);
quickByWhile(array);
System.out.println(Arrays.toString(array));
}
}
优化基准
https://blog.csdn.net/qq_41571459/article/details/117711828
堆排序(重要 ,不稳定)
概念
二叉树:
有且只能有左右两个节点
完全二叉树:
若设二叉树的高度为h,则共有h+1层。除第h层外,
其它各层(0 ~ h-1)的结点数都达到最大个数,第h层从右向左连续缺若干结点
满二叉树:
所有的层都是满(看作是特殊的完全二叉树)
根节点和子节点的关系
图解
代码
import java.util.Arrays;
/**
* 堆排序
* <p>
* 时间复杂度: 建 堆的O(N*log2N) (从最后一个子树根,--到第一个根,是O(n) 调整子树(每次将待排序序列,一分为二,)(log2(N))
* 空间复杂度:O(1)
* 稳定性:不稳定
*
*
* 规律: 一般只要涉及到跳跃性的交换顺序 就是不稳定的
* 3
* 27 36
* 27'
* 如果堆顶3先输出,则第三层的27'(跑到堆顶,然后堆稳定,继续输出堆顶(27'),...=>输出顺序:3 27' 27 36。
*/
public class Heap {
public static void adjust(int[] array, int start, int end) {
//start是作为当前调整子树的根节点
int tmp = array[start];
//有可能调整后,她的子树不成立了,需要将原根节点值 层层下沉,放到最终位置,从而腾出了根节点位置,放最大值
for (int i = 2 * start + 1; i <= end; i = 2 * i + 1) {
//判断 是否存在 右孩子 并找 左右孩子的最大值
if (i + 1 <= end && array[i] < array[i + 1]) {
i++; //即若右孩子较大,就使i为右孩子下标;再和tmp比
}
if (array[i] > tmp) {
array[start] = array[i];//让根为最大值(即最大值往上走)
start = i;//即 最大值原位置 是 新的待调整子树的根节点
} else {
break;//直到下面的子树也满足条件
}
}
array[start] = tmp;//原根节点值 能满足所有条件,最终可以放的位置
}
public static void heapSort(int[] array) {
if (array == null || array.length == 0) {
return;
}
//第一步:(从下往上 从右往左)建立大根堆 (那建立小根堆,由大到小排序怎么写呢??????)
// 0...array.length - =>array.length - 1 - 1)/2是最后一个子树的根节点序号
for (int i = ((array.length - 1 - 1) / 2); i >= 0; i--) {
adjust(array, i, array.length - 1);
}
//第二步:将根节点与待排序列最后一个节点进行互换,互换之后需要继续调整
// i趟,就有i个元素已就位
for (int i = 0; i < array.length; i++) {
// 交换首尾(即将最大值 放在 最后面)
int tmp = array[0];
array[0] = array[array.length - 1 - i];//0...array.length - 1,而后面i个已就位
array[array.length - 1 - i] = tmp;
//调整 余下的
adjust(array, 0, array.length - 1 - i - 1);
}
}
public static void main(String[] args) {
int[] array = {5, 10, 8, 1, 19, 20, 29, 28, 15, 3, 6, 17, 0};
heapSort(array);
System.out.println(Arrays.toString(array));
}
}