目录
我们将探索分而治之(divide and conquer, D&C) ——一种著名的递归式问题解决方法。
1 欧几里得算法
可汗学院算法 https://www.khanacademy.org/computing/computer-science/algorithms
1.1 D&C的工作原理:
(1) 找出简单的基线条件;
(2) 确定如何缩小问题的规模,使其符合基线条件。
D&C并非可用于解决问题的算法,而是一种解决问题的思路。
你需要将这些数字相加,并返回结果。使用循环很容易完成这种任务。
1.2 函数式编程一瞥 |
你可能想,既然使用循环可轻松地完成任务,为何还要使用递归方式呢?看看函数式编程 你就明白了!诸如Haskell等函数式编程语言没有循环,因此你只能使用递归来编写这样的函数。 如果你对递归有深入的认识,函数式编程语言学习起来将更容易。例如,使用Haskell时,你可 能这样编写函数sum。 sum [] = 0 sum (x:xs) = x + (sum xs) 注意,这就像是你有函数的两个定义。符合基线条件时运行第一个定义,符合递归条件时 运行第二个定义。也可以使用Haskell语言中的if语句来编写这个函数。 sum arr = if arr == [] then 0 else (head arr) + (sum (tail arr)) 但前一个版本更容易理解。 Haskell大量使用了递归,因此它提供了各种方便实现递归的语 法。如果你喜欢递归或想学习一门新语言,可以研究一下Haskell。 |
2 快速排序
快速排序是一种常用的排序算法,比选择排序快得多。例如, C语言标准库中的函数qsort实现的就是快速排序。快速排序也使用了D&C。
基线条件为数组为空或只包含一个元素。在这种情况下,只需原样返回数组——根本就不用排序。
def quicksort(array):
if len(array) < 2:
return array
这被称为分区(partitioning) 。现在你有:
一个由所有小于基准值的数字组成的子数组; 基准值;
一个由所有大于基准值的数组组成的子数组。
这里只是进行了分区,得到的两个子数组是无序的。但如果这两个数组是有序的,对整个数
组进行排序将非常容易
3 归纳证明 |
刚才你大致见识了归纳证明!归纳证明是一种证明算法行之有效的方式,它分两步:基线条件和归纳条件。是不是有点似曾相识的感觉?例如,假设我要证明我能爬到梯子的最上面。递归条件是这样的:如果我站在一个横档上,就能将脚放到下一个横档上。换言之,如果我站在第二个横档上,就能爬到第三个横档。这就是归纳条件。而基线条件是这样的,即我已经站在第一个横档上。因此,通过每次爬一个横档,我就能爬到梯子最顶端。对于快速排序,可使用类似的推理。在基线条件中,我证明这种算法对空数组或包含一个元素的数组管用。在归纳条件中,我证明如果快速排序对包含一个元素的数组管用,对包含两个元素的数组也将管用;如果它对包含两个元素的数组管用,对包含三个元素的数组也将管用,以此类推。因此,我可以说,快速排序对任何长度的数组都管用。这里不再深入讨论归纳证明,但它很有趣,并与D&C协同发挥作用。 |
4 快速排序伪代码
5 再谈大 O 表示法
5.0 《图解算法》学习之算法复杂度、运行时间
《图解算法》学习之算法复杂度、运行时间_moonlightpeng的博客-CSDN博客
快速排序的独特之处在于,其速度取决于选择的基准值。在讨论快速排序的运行时间前,我们再来看看最常见的大O运行时间。
上述图表中的时间是基于每秒执行10次操作计算得到的。这些数据并不准确,这里提供它们只是想让你对这些运行时间的差别有大致认识。实际上,计算机每秒执行的操作远不止10次。
对于每种运行时间,本书还列出了相关的算法。来看看第2章介绍的选择排序,其运行时间为O(n2),速度非常慢。
还有一种名为合并排序(merge sort) 的排序算法,其运行时间为O(n log n),比选择排序快得多!快速排序的情况比较棘手,在最糟情况下,其运行时间为O(n2)。
与选择排序一样慢!但这是最糟情况。在平均情况下,快速排序的运行时间为O(n log n)。你可能会有如下疑问。
这里说的最糟情况和平均情况是什么意思呢?
若快速排序在平均情况下的运行时间为O(n log n),而合并排序的运行时间总是O(n log n),为何不使用合并排序?它不是更快吗?
5.1 比较合并排序和快速排序
但有时候,常量的影响可能很大,对快速查找和合并查找来说就是如此。快速查找的常量比
合并查找小,因此如果它们的运行时间都为O(n log n),快速查找的速度将更快。实际上,快速查
找的速度确实更快,因为相对于遇上最糟情况,它遇上平均情况的可能性要大得多。
此时你可能会问,何为平均情况,何为最糟情况呢?
5.2 平均情况和最糟情况
6 小结
D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元
素的数组。
实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为O(n log n)。
大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。
比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时, O(log n)的速度比O(n)
快得多。
7 练习
7.1 请编写前述sum函数的代码。
7.1.1 Python 循环求数组之和
def computSum(arrays):
addSum = 0
for num in arrays:
addSum += num
return addSum
myArray = [1,2,3,4,5]
print(computSum(myArray))
7.1.2 Python 递归求和
def max_(lst):
if len(lst) == 0:
return None
if len(lst) == 1:
return lst[0]
else:
sub_max = max_(lst[1:])
return lst[0] if lst[0] > sub_max else sub_max
myArray = [1,20,3,4]
print(max_(myArray))
7.1.3 JS
/**
* Sums values in array by loop "for"
* @param {Array} arr Array of numbers
* @return {total} Sum of the numbers
*/
function sum(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
console.log(sum([1, 2, 3, 4])); // 10
/**
* Sums values in array by function "reduce"
* @param {Array} arr Array of numbers
* @return {number} Sum of the numbers
*/
function sumReduce( arr ) {
var result = newArr.reduce( ( curr, prev ) => {
return curr + prev;
} );
return result;
}
var arr = [1, 2, 3, 4];
console.log( sumReduce( arr ) ); // 10
const arr = [1, 2, 3, 4];
/**
* Sums values in array recursively
* @param {Array} arr Array of numbers
* @return {number} Sum of the numbers
*/
const sumRecursive = ( arr ) => {
if ( arr.length == 1 ) return arr[0];
return arr[0] + sumRecursive( arr.slice( 1 ) );
};
console.log( sumRecursive( arr ) ); // 10
7.1.4 C 循环
#include <stdio.h>
int sum(int *arr, int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
int main(void) {
int arr[4] = { 1,2,3,4 };
printf("%d", sum(arr, 4));
return 0;
}
7.1.5 C递归
#include <stdio.h>
int sum(int *arr, int index, int size) {
if (index == size)
return 0;
return arr[index] + sum(arr, index + 1, 4);
}
int main(void) {
int arr[4] = { 1,2,3,4 };
printf("%d", sum(arr, 0, 4));
return 0;
}
7.1.6 C ++ 11 loop sum
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
template <typename T>
T sum(const std::vector<T>& arr) {
T sum = 0;
for (T item : arr) {
sum += item;
}
return sum;
}
int main() {
std::vector<int> arr_int = {1, 2, 3, 4};
std::vector<float> arr_float = {0.1, 0.2, 0.3, 0.4, 0.5};
cout << "Sum ints: " << sum(arr_int) << endl;
cout << "Sum floats: " << sum(arr_float) << endl;
}
7.1.7 C++11 递归
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
template <typename T>
T sum(std::vector<T> arr) {
if (arr.empty()) return 0;
T last_num = arr.back(); // save last number value
arr.pop_back(); // and remove it from array for next recursive call
return first_num + sum(arr);
}
int main() {
std::vector<int> arr_int = {1, 2, 3, 4};
std::vector<float> arr_float = {0.1, 0.2, 0.3, 0.4, 0.5};
cout << "Sum ints: " << sum(arr_int) << endl;
cout << "Sum floats: " << sum(arr_float) << endl;
}
7.1.8 C# loop sum
using System;
using System.Collections.Generic;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(Sum(new[] { 1, 2, 3, 4 }));
}
private static int Sum(IEnumerable<int> arr)
{
var total = 0;
foreach (var x in arr)
{
total += x;
}
return total;
}
}
}
7.1.9 C# recursive_sum
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(Sum(new[] { 1, 2, 3, 4 }));
}
private static int Sum(IEnumerable<int> list)
{
if (!list.Any()) return 0;
return list.Take(1).First() + Sum(list.Skip(1));
}
}
}
7.1.10 Java loop sum
public class LoopSum {
private static int sum(int[] arr) {
int total = 0;
for (int x = 0; x < arr.length; x++) {
total += arr[x];
}
return total;
}
public static void main(String[] args) {
System.out.println(sum(new int[]{1, 2, 3, 4})); // 10
}
}
7.1.11 Java recursiveSUm
import java.util.Arrays;
public class RecursiveSum {
private static int sum(int[] arr) {
if (arr.length == 0) {
return 0;
} else {
return arr[0] + sum(Arrays.copyOfRange(arr, 1, arr.length));
}
}
public static void main(String[] args) {
System.out.println(sum(new int[]{1, 2, 3, 4})); // 10
}
}
7.2 编写一个递归函数来计算列表包含的元素数。
7.2.1 Python
def count(list):
if list == []:
return 0
return 1 + count(list[1:])
myArray = [1,2,3,4]
print(count(myArray))
7.2.2 JS
'use strict';
function count(list) {
if (list.length === 0) {
return 0;
}
return 1 + count(list.slice(1));
}
console.log(count([0, 1, 2, 3, 4, 5])); // 6
7.2.3 C++11
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
template <typename T>
int count(std::vector<T> arr) {
if (arr.empty()) return 0;
arr.pop_back();
return count(arr) + 1;
}
int main() {
std::vector<int> array = {0, 1, 2, 3, 4, 5};
cout << count(array) << endl;
}
7.2.4 C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(Count(new[] { 1, 2, 3, 4 }));
}
private static int Count(IEnumerable<int> list)
{
if(!list.Any()) return 0;
return 1 + Count(list.Skip(1));
}
}
}
7.2.5 Java
import java.util.Arrays;
public class RecursiveCount {
private static int count(int[] list) {
if (list.length == 0) {
return 0;
}
return 1 + count(Arrays.copyOfRange(list, 1, list.length));
}
public static void main(String[] args) {
System.out.println(count(new int[]{0, 1, 2, 3, 4, 5})); // 6
}
}
7.3 找出列表中最大的数字。
7.3.1 Python
def recursive_Sum(arrays):
if arrays == []:
return 0
else:
return arrays[0] + recursive_Sum(arrays[1:])
myArray = [1,2,3,4,5]
num = recursive_Sum(myArray)
print(num)
7.3.2 JS
'use strict';
function max(list) {
if (list.length === 2) {
return list[0] > list[1] ? list[0] : list[1];
}
let sub_max = max(list.slice(1));
return list[0] > sub_max ? list[0] : sub_max;
}
console.log(max([1, 5, 10, 25, 16, 1])); // 25
7.3.3 C++11
#include <iostream>
#include <vector>
#include <stdexcept>
using std::cout;
using std::endl;
template <typename T>
T max(std::vector<T> arr) {
if (arr.empty()) throw std::invalid_argument("Cannot select max value from empty sequence");
if (arr.size() == 1) return arr.at(0);
T back = arr.back();
arr.pop_back();
T sub_max = max(arr);
return back > sub_max ? back : sub_max;
}
int main() {
std::vector<int> array = {1, 5, 10, 25, 16, 1};
cout << max(array) << endl;
std::vector<int> negative_array = {-1, -5, -10, -25, -16};
cout << max(negative_array) << endl;
}
7.3.4 C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(Max(new[] { 1, 3, 2, 5, 9, 8 }));
}
private static int Max(IEnumerable<int> list)
{
if (!list.Any()) throw new ArgumentException(nameof(list));
if (list.Count() == 1) return list.First();
if (list.Count() == 2) return list.First() > list.Skip(1).Take(1).First() ? list.First() : list.Skip(1).Take(1).First();
var sub_max = Max(list.Skip(1));
return list.First() > sub_max ? list.First() : sub_max;
}
}
}
7.3.5 java
import java.util.Arrays;
public class RecursiveMax {
private static int max(int[] list) {
if (list.length == 2) {
return list[0] > list[1] ? list[0] : list[1];
}
int subMax = max(Arrays.copyOfRange(list, 1, list.length));
return list[0] > subMax ? list[0] : subMax;
}
public static void main(String[] args) {
System.out.println(max(new int[]{1, 5, 10, 25, 16, 1})); // 25
}
}
7.4 快速排序
7.4.1 Python
def quicksort(array):
if len(array) < 2:
# base case, arrays with 0 or 1 element are already "sorted"
return array
else:
# recursive case
pivot = array[0]
# sub-array of all the elements less than the pivot
less = [i for i in array[1:] if i <= pivot]
# sub-array of all the elements greater than the pivot
greater = [i for i in array[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater)
print(quicksort([10, 5, 2, 3]))
myArray = [1,20,300,4]
print(quicksort(myArray))
7.4.2 JS
'use strict';
function quicksort(array) {
if (array.length < 2) {
// base case, arrays with 0 or 1 element are already "sorted"
return array;
} else {
// recursive case
let pivot = array[0];
// sub-array of all the elements less than the pivot
let less = array.slice(1).filter(function(el) { return el <= pivot; });
// sub-array of all the elements greater than the pivot
let greater = array.slice(1).filter(function(el) { return el > pivot; });
return quicksort(less).concat([pivot], quicksort(greater));
}
}
console.log(quicksort([10, 5, 2, 3])); // [2, 3, 5, 10]
7.4.3 C
#include <stdio.h>
// Quick Sort
void quick_sort(int *array, int start, int end) {
if (start < end) {
int q = partition(array, start, end);
quick_sort(array, start, q - 1);
quick_sort(array, q + 1, end);
}
}
// Partition by pivot
int partition(int *array, int start, int end) {
int pivot = array[end];
int i = start - 1;
int temp = 0;
for (int j = start; j < end; j++) {
if (array[j] <= pivot) {
i++;
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
temp = array[i + 1];
array[i + 1] = array[end];
array[end] = temp;
return i + 1;
}
int main(void) {
int arr[4] = {10, 5, 2, 3};
quick_sort(arr, 0, 3);
// Print result
for (int i = 0; i < 4; i++) {
printf("%d ", arr[i]);
}
return 0;
}
7.4.4 C++11
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
template <typename T>
std::vector<T> quicksort(const std::vector<T>& arr) {
// base case, arrays with 0 or 1 element are already "sorted"
if (arr.size() < 2)
return arr;
// recursive case
const T* pivot = &arr.front() + arr.size() / 2 - 1; // set the pivot somewhere in the middle
std::vector<T> less; // vector to store all the elements less than the pivot
std::vector<T> greater; // vector to store all the elements greater than the pivot
for (const T* item = &arr.front(); item <= &arr.back(); item++) {
if (item == pivot) continue; // skip pivot element
if (*item <= *pivot) less.push_back(*item);
else greater.push_back(*item);
}
std::vector<T> sorted_less = quicksort(less);
std::vector<T> sorted_greater = quicksort(greater);
// concatenate less part, pivot and greater part
sorted_less.push_back(*pivot);
sorted_less.insert(sorted_less.end(), sorted_greater.begin(), sorted_greater.end());
return sorted_less;
}
int main() {
std::vector<int> arr = {69, 60, 38, 82, 99, 15, 8, 94, 30, 42, 35, 40, 63, 1, 49, 66, 93, 83, 20, 32, 87, 6, 78, 17, 2, 61, 91, 25, 7, 4, 97, 31, 23, 67, 95, 47, 55, 92, 37, 59, 73, 81, 74, 41, 39};
std::vector<int> sorted = quicksort(arr);
for (int num : sorted) {
cout << num << " ";
}
cout << endl;
}
7.4.5 C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
var arr = new[] { 10, 5, 2, 3 };
Console.WriteLine(string.Join(", ", QuickSort(arr)));
}
private static IEnumerable<int> QuickSort(IEnumerable<int> list)
{
if (list.Count() <= 1) return list;
var pivot = list.First();
var less = list.Skip(1).Where(i => i <= pivot);
var greater = list.Skip(1).Where(i => i > pivot);
return QuickSort(less).Union(new List<int> { pivot }).Union(QuickSort(greater));
}
}
}
7.4.6 Java
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Quicksort {
public static void main(String[] args) {
System.out.println(quicksort(Arrays.asList(10, 5, 2, 3))); // [2, 3, 5, 10]
}
private static List<Integer> quicksort(List<Integer> list) {
if (list.size() < 2) {
// base case, arrays with 0 or 1 element are already "sorted"
return list;
} else {
// recursive case
Integer pivot = list.get(0);
// sub-array of all the elements less than the pivot
List<Integer> less = list.stream().skip(1).filter(el -> el <= pivot)
.collect(Collectors.toList());
// sub-array of all the elements greater than the pivot
List<Integer> greater = list.stream().skip(1).filter(el -> el > pivot)
.collect(Collectors.toList());
return Stream.of(
quicksort(less).stream(),
Stream.of(pivot),
quicksort(greater).stream())
.flatMap(Function.identity()).collect(Collectors.toList());
}
}
}