数据结构与算法拾遗七

归并排序(递归与非递归方式)

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

	// arr[L...R]范围上,请让这个范围上的数,有序!
	public static void process(int[] arr, int L, int R) {
		if (L == R) {
			return;
		}
		// int mid = (L + R) / 2
		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) {
		//L到R上有多少个数
		int[] help = new int[R - L + 1];
		int i = 0;
		int p1 = L;
		int p2 = M + 1;
		//p1和p2都没有越界的时候
		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++];
		}
		//将顺序拷贝到arr
		for (i = 0; i < help.length; i++) {
			arr[L + i] = help[i];
		}
	}

说明:
process(arr, L, R)指:把arr[L…R]上排好序,定义如此!

merge(arr, L, M, R)指:如果L…M已经有序,并且M+1…R上已经有序。那么通过merge过程,让L…R上整体有序。定义如此!

举个例子:

数组:3 1 4 2

位置:0 1 2 3

主函数调用process(arr, 0, 3)意思是让0…3范围有序

首先调用process(arr, 0, 1) :arr[0…1]变成:1,3

其次调用process(arr, 2, 3) :arr[2…3]变成:2,4

最后调用merge(arr, 0, 1, 3): 1,3 和 2,4 merge:1,2,3,4

搞定

至于怎么搞定的?那你需要把所有的递归图画出来,追代码一层一层去看看arr到底怎么变化的

去追process(arr, 0, 1):

首先调用process(arr, 0, 0) :arr[0…0]本身就是:3

其次调用process(arr, 1, 1) :arr[1…1]本身就是:1

最后调用merge(arr, 0, 0, 1): 3 和 1 merge:1,3

搞定

去追process(arr, 2, 3):

首先调用process(arr, 2, 2) :arr[2…2]本身就是:4

其次调用process(arr, 3, 3) :arr[3…3]本身就是:2

最后调用merge(arr, 2, 2, 3): 4 和 2 merge:2,4

搞定

以上展示了,递归调用的所有细节。你刚开始学,肯定需要多画递归图。自己多拆几次。

然后你熟悉了递归这件事情以后,就可以相对“宏观”一点的来用递归实现过程。

只需要关注递归的含义,它能完成的工作。然后组合起来。

也就是所谓“黑盒”。

至于到底怎么实现的,画递归图,去拆解黑盒。

归并排序非递归写法

说明非递归的逻辑:设置一个步长初始化步长为1
有以下数组
3,1,5,6,9,10,7,8,6
首先步长为1
1,3,5,6,9,10,7,8,6
再更新步长为2
1,3,5,6,7,8,9,10,6
再更新步长为4
1,3,5,6,7,8,9,10,6
再更新步长为8:
1,3,5,6,6,7,8,9,10
得到最终的结果。
代码如下:

	public static void mergeSort2(int[] arr) {

		if (arr == null || arr.length < 2) {
			return;

		}
		int step = 1;
		int N = arr.length;
		while (step < N) {
			int L = 0;
			while (L < N) {
				//此方式会造成M越界,如果凑不齐的话
				//  int M = L + step - 1;
				//同样的此种写法会造成L+step-1越界(如数组凑不满的情况,或者数组下标很大的情况)
				// int M = Math.min(N - 1, L + step - 1);
				int M = 0;
				//L...N-1 length = N-L
				if (N - L >= step) {
					M = L + step - 1;
				} else {
					M = N - 1;
				}
				//没有右组
				if (M == N - 1) {
					break;
				}

				int R = 0;
				//右组的个数 M+1-R的个数
				if (N - 1 - M >= step) {
					//  R = M + 1 + step - 1;
					R = M + step;
				} else {
					R = N - 1;
				}
				// L..M M..R
				merge(arr, L, M, R);
				//下一个左组
				if (R == N - 1) {
					break;
				} else {

					L = R + 1;
				}
			}

			//此处是为了防止step溢出,假如当数组的长度趋近于2的30次方的时候
			//如果step乘以2肯定会导致step的int溢出的,所以此处当step*2>=n
			//的时候则结束当前循环
			if (step > (N / 2)) {
				break;
			} else {
				step *= 2;
			}

		}
	}

时间复杂度估算:
由于步长每次都是乘以2,所以首先估算出一个logn,由于每次merge的长度为step长度,
所以最后的时间复杂度为n*logn

快速排序

荷兰国旗问题

给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度 O(N)。
思路:
首先划分区域:
1、当前数<=划分值**,当前数和小于等于区域的下一个数做交换**,然后小于等于区域向右扩,当前数跳下一个
2、如果当前数>划分值,当前数直接跳下一个
以越界为结束条件
在这里插入图片描述
代码如下:

  public static void splitNum(int[] arr) {
        //小于等于区域初始位置
        int lessEqualR = -1;
        int index = 0;
        int mostR = arr.length - 1;
        while (index < arr.length) {
            if (arr[index] <= arr[mostR]) {
                //当前数和小于等于区域的下一个位置做交换,小于等于区域往右阔
//                swap(arr,lessEqualR+1,index);
//                lessEqualR++;
//                index++;
                swap(arr, ++lessEqualR, index++);
            } else {
                index++;
            }
        }
    }

    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、当前数和大于区域撞上的时候,则将最后一个数和大于区域的第一个数做交换
在这里插入图片描述

  public static void splitNum2(int[] arr) {
        int N = arr.length;
        //小于区域的起始值
        int lessR = -1;
        //大于区域的起始值
        int moreL = N - 1;
        int index = 0;
        while (index < moreL) {
            if (arr[index] < arr[N - 1]) {
                swap(arr, ++lessR, index++);
            } else if (arr[index] > arr[N - 1]) {
                swap(arr, --moreL, index);
            } else {
                index++;
            }
            swap(arr, moreL, N - 1);
        }
        
    }

    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
快排递归方式
    //arr[L...R]范围上,拿arr[R]做划分,让小于的在左边,等于的在中间,大于的在右边,
    //返回等于区域的左边界和右边界(index)
    public static int[] partition(int[] arr, int L, int R) {
        int lessR = L - 1;
        int moreL = R;
        int index = L;
        while (index < moreL) {
            if (arr[index] < arr[R]) {
                swap(arr, ++lessR, index++);
            } else if (arr[index] > arr[R]) {
                swap(arr, --moreL, index);
            } else {
                index++;
            }

        }
        swap(arr, moreL, R);
        return new int[]{lessR + 1, moreL};
    }

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

    public static void process(int[] arr, int L, int R) {
        if (L >= R) {
            return;
        }
        //L<R
        int[] equalE = partition(arr, L, R);
        //equalE[0] 等于区域第一个数
        //equalE[1] 等于区域最后一个数
        process(arr, L, equalE[0] - 1);
        process(arr, equalE[1] + 1, R);
    }


    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
快排非递归方式

   public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
   //arr[L...R]范围上,拿arr[R]做划分,让小于的在左边,等于的在中间,大于的在右边,
    //返回等于区域的左边界和右边界(index)
    public static int[] partition(int[] arr, int L, int R) {
        int lessR = L - 1;
        int moreL = R;
        int index = L;
        while (index < moreL) {
            if (arr[index] < arr[R]) {
                swap(arr, ++lessR, index++);
            } else if (arr[index] > arr[R]) {
                swap(arr, --moreL, index);
            } else {
                index++;
            }

        }
        swap(arr, moreL, R);
        return new int[]{lessR + 1, moreL};
    }

  public static void quickSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        Stack<Job> stack = new Stack<>();
        stack.push(new Job(0,arr.length-1));
        while(!stack.isEmpty()) {
            Job cur = stack.pop();
            int L = cur.L;
            int R = cur.R;
            int[] equals = partition(arr, cur.L, cur.R);
            if(equals[0]> cur.L) {
                //有小于区域
                stack.push(new Job(cur.L, equals[0]-1));
            }
            if(equals[1]<R) {
                //有大于区域
                stack.push(new Job(equals[1]+1,cur.R));
            }
        }

    }


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值