一.快速排序
1.思想
类似于分治法,将数组中的数字分解为规模为原来一半的数组,并且该数组每次分治后,数组分为以某一个基准数字分为两部分的数组(左边比基准数字小右边比基准数字大)
2.图片演示
3.优缺点
1.一般的快速排序是不稳定的,其时间复杂度为 O(nlogn),其中n是所排序序列的大小。但是在选取的基准值为所排序序列的最值时(例如{12, 1, 2, 7, 9, 11, 4, 5, 6, 8})其时间复杂度会退化成O(n^2)
2.但如果采用随机选取基准值的方法,可以使得快速排序变得稳定许多,时间复杂度为 O(nlogn)
4.过程
首先要明白我们要做放事情:找到一个基准值,让它的左边的数比它小右边的数比它大,假设原数组
{6, 1, 2, 7, 9, 11, 4, 5, 10, 8};这里随便定一个基准值为6,我们要把他围绕基准值6数组排序成
{4, 1, 2, 5, 6, 11, 9, 7, 10, 8}
以下是过程
1.定义数组
public static void main(String[] args) { int[] arr = {6, 1, 2, 7, 9, 11, 4, 5, 10, 8}; //调用方法排序,传递需要排序的范围,这里先从数组第一个到最后一个 quickSort(arr, 0, arr.length - 1); //检查排序 System.out.println(Arrays.toString(arr)); }
while (left < right && arr[right] >= base) { //(条件:arr[right] >= base) right--; } //右指针停止后左指针开始向右移动,同理,找到比基准值大的数停止,否则一直右移(条件arr[left] <= base) while (left < right && arr[left] <= base) { // left++; } //现在已经找到左右两边开始数起,分别第一个出现的符合条件的数,也就是7和5 //将两个数交换位置 int tem = arr[right]; arr[right] = arr[left]; arr[left] = tem;
得到结果{6, 1, 2, 5, 9, 11, 4, 7, 10, 8},
此时可以交换第一次出现的一对符合条件的数,但是目标是整个数组,因此需要让这个过程继续重复执行,在这之前
有人会好奇while内left<right这个条件,这里是因为:
假设数组是{6,7,5,3}
第一次交换:{6,3,5,7}此时left指针指向3,right指向7
第二次:{6,3,5,7}中,右指针从7开始,左移到5时候,比基准值小,停止
左指针指到3以后右移到5,此时,两指针指向同一个数5,此时停止,
因此这里是left<right时候符合条件执行循环
对于arr[right] >= base左右指针是否该写=,需要考虑极端条件,比如数组:{6,6,,7,5,3} 如果写成arr[right] > base或者arr[left] <base时,当有重复值 第一次交换: 右指针指向3;左指针第二个6不复合条件,因此指向7,交换后:{6,6,3,5,7} 第二次交换: 右指针指向5,左指针指向5时两指针相遇,停止循环得到的数组{6,6,3,5,7}不符合左边小右边大的预期 因此需要写=,写==时: 第一次:{6,3,7,5,6} 第二次:{6,3,5,7,6} 将6和5交换得到预期数组{5,3,6,7,6}
这里同理,当第一次交换后,为了将每一个数排好,在外层加一个循环
while (left < right) { while (left < right && arr[right] >= base) { right--; } //右指针停止后左指针开始向右移动,同理,找到比基准值大的数停止,否则一直右移 while (left < right && arr[left] <= base) { left++; } //现在已经找到左右两边开始数,分别第一个出现的符合条件的数,也就是7和5 //将两个数交换位置 int tem = arr[right]; arr[right] = arr[left]; arr[left] = tem; } //交换两个值 arr[start] = arr[left]; //将指针指向赋值给第一个数6 arr[left] = base; // 将第一个数6 给指针指向值(base=6)
结束条件是当左右指针相遇时停止,最后将两指针停止的索引对应的值与选取的基准值交换
因为left与right此时相同,因此随意选取一个进行交换
此时运行代码
接下来只需要将6左边的数和右边的数进行一个快速排序,也就是重新调用排序方法,但是改变需要排序的范围
//对左边数进行快排 quickSort(arr,start,left-1); //对6右边进行快排 quickSort(arr,left+1,end);
并且给出递归结束条件;当不断分治到只有两个数时返回,例如数组:
{6, 1, 2, 7, 9, 11, 4, 5, 10, 8}不断进行递归:
{4, 1, 2, 5, 6, 11, 9, 7, 10, 8}
{4, 1, 2, 5, 6, 11, 9, 7, 10, 8}
{2,1,4,5,6,8,9,7,10,11} //4和11为基准
{2,1,4,5,6,8,9,7,10,11} //2和8为基准,此时2和1交换后满足条件start>=end,返回右边{8,9,7,10}继续进行快排
右边以此类推...
if(start>=end){ return; }
整个方法:
public static void quickSort(int[] arr, int start, int end) { if(start>=end){ return; } //将定义两个指针为left和right分别在数组索引的0和length-1位置 int left = start; int right = end; //随意选取一个基准值,这里选取第一个数,也就是索引为0; int base = arr[start]; //先从右指针开始寻找比基准值小的数,如果比基准值大则继续向左移动右指针,如果找到比基准值小的数停止 while (left < right) { while (left < right && arr[right] >= base) { right--; } //右指针停止后左指针开始向右移动,同理,找到比基准值大的数停止,否则一直右移 while (left < right && arr[left] <= base) { left++; } //现在已经找到左右两边开始数,分别第一个出现的符合条件的数,也就是7和5 //将两个数交换位置 int tem = arr[right]; arr[right] = arr[left]; arr[left] = tem; } arr[start] = arr[left]; arr[left] = base; //对左边数进行快排 quickSort(arr,start,left-1); //对6右边进行快排 quickSort(arr,left+1,end); }
运行
二.递归
1.概念
-
递归是一种自调用函数,也就是说函数内部调用函数本身,每次调用时传入不同的变量。
-
递归有助于编程者解决复杂的问题,同时让代码变得简洁。
2 算法
递归算法中包含:递推、回推、终止条件
-
方法内部调用方法自己的过程称为递推。
-
方法内部返回到上一次调用的过程称为回推。
-
满足方法内部不在调用方法本身的条件称为递归的终止条件
3 示例
1.题目:求n!
分析: n的阶乘可以等价于n * (n-1)! 等价于n * (n-1) * (n-2)! 等价于n * (n-1) * (n-2) * (n-3)!
直到乘1为止
public static int f(int n){ if(n==1){ //终止条件 return 1; //回推 } return n * f(n-1); //递推 } public static void main(String[] args) { int result = f(4); System.out.println(result); }
过程
4.规则
-
指向递归方法时,要创建一个新的受保护的独立空间(栈空间)
-
方法的局部变量是独立的,不会相互影响,比如变量n
-
如果方法中使用的是引用类型的变量(比如数组),就会共享该引用变量
-
递归必须向终止条件逼近,否则就是无限递归,就会出现 StackOverflowError(死龟)
-
当递归方法遇到return,就开始回推,回推的过程中遵循谁调用就将结果返回 给谁。