算法注释
我正在edx.org上学习CS50:计算机科学导论。我发现,通过完成、重写和分享笔记,复习所学知识是一种很好的方式。
注意:大 O 符号可以理解为“大约”,它表示算法的运行时间。在 C 语言示例中,n
相当于 ,sizeof(arr)/sizeof(arr[0])
在 JavaScript 中则转换为arr.length
。
第三周是关于算法的。😺
目录
线性搜索
从左到右遍历数组以搜索目标元素。
伪代码示例#1:
Repeat, starting at the first element:
If the element is the target element, stop
Else, move to the next element
伪代码示例#2:
For i from 0 to n–1
If i'th element is target_element
Return true
Return false
C 语言示例:
bool linearSearch(int arr[], int n, int target)
{
for (int i = 0; i < n; i++)
if (arr[i] == target) return true;
return false;
}
JavaScript 示例:
linearSearch = (arr, target) => {
for (let i = 0; i < arr.length; i++)
if (arr[i] === target) return true;
return false;
}
线性搜索算法
-
最坏情况: 如果目标元素是最后一个元素,或者它不在数组中,
则必须遍历整个数组。 用大 O 表示法,就是O(n)。n
-
最佳情况:
目标元素是第一个元素。
用大 O 表示法,它转换为Ω(1)。
二分查找
通过每次将搜索范围缩小一半来找到目标元素。确保使用二分搜索算法的数组已排序,否则无法对其内容做出任何假设。
伪代码示例#1:
Repeat until the (sub)array is of size 0:
Calculate the middle point of the current (sub)array
If the target element is the middle element, stop
Else if it's less than the middle:
End point is now just to the left of the current middle, repeat
Else if it's greater than the middle:
Start point is now just to the right of the current middle, repeat
伪代码示例#2:
If no items
Return false
If middle item is target_element
Return true
Else if target_element < middle item
Update end point
Search left half
Else if target_element > middle item
Update start point
Search right half
C 示例(递归):
int binarySearch(int arr[], int target, int start, int end)
{
if (end >= start) {
// instead of (start+end)/2 to avoid overflow
int mid = start+(end-start)/2;
if (arr[mid] == target) return mid;
else if (arr[mid] > target) return binarySearch(arr, target, start, mid-1);
else return binarySearch(arr, target, mid+1, end);
}
return -1;
}
JavaScript 示例(递归):
binarySearch = (arr, target, start, end) => {
if (end >= start) {
let mid = Math.floor((start+end)/2);
if (arr[mid] === target) return mid;
else if(arr[mid] > target) return binarySearch(arr, target, start, mid-1);
else return binarySearch(arr, target, mid+1, end);
}
return false;
}
二分查找算法
-
最坏情况:
为了找到目标元素,不得不反复将列表中的n
元素一分为二,因为目标元素出现在最后一次除法的末尾,或者不在数组中。
用大 O 表示法,它相当于O(log n)。 -
最佳情况:
目标元素位于数组的中点,因此我们可以在开始搜索后立即停止。
用大 O 表示法,它等于Ω(1)。
冒泡排序
以冒泡方式排序:将较高的值移向数组的右侧,将较低的值移向左侧。
伪代码示例#1:
Set swap counter to a non-zero value
Repeat until the swap counter is equal to 0:
Reset swap counter to 0
Look at each adjacent pair:
If two adjacent elements are not in order:
Swap them
Add one to the swap counter
伪代码示例#2:
Repeat until no swaps
For i from 0 to n–2
If i'th and i+1'th elements out of order
Swap them
C 语言示例:
void bubbleSort(int arr[], int n)
{
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-i-1; j++)
if (arr[j] > arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
JavaScript 示例:
bubbleSort = arr => {
for (let i = 0; i < arr.length-1; i++)
for (let j = 0; j < arr.length-i-1; j++)
if (arr[j] > arr[j+1]) {
let temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
return arr;
}
因为比较i
第 th 个元素和i+1
第 th 个元素,排序只需进行到 ,n-2
然后i
如果两个元素顺序错误则交换它们。由于已知最大的n-1
元素会冒泡到右侧,排序可以在n-1
遍历之后停止。
重新遍历数组时,只需考虑未排序的元素。
当交换计数器保持在 时0
,表示没有其他元素可交换。
冒泡排序算法
-
最坏情况:
由于数组是逆序的,所以必须将每个元素冒泡到整个数组中。由于每次只能将一个元素完全冒泡到其排序位置,因此排序必须进行n
多次。
用大 O 表示法,它转换为O(n²)。 -
最佳情况:
数组已经完美排序,因此第一次遍历时无需交换元素。
用大 O 表示法,它等于Ω(n)。
选择排序
找到最小的未排序元素并将其添加到排序列表的末尾。
伪代码示例#1:
Repeat until there is no unsorted elements remaining:
Search unsorted part of data to find the smallest value
Swap the found value with the first element of the unsorted part
伪代码示例#2:
For i from 0 to n–1
Find smallest item between i'th item and last item
Swap smallest item with i'th item
C 语言示例:
void selectionSort(int arr[], int n)
{
for (int i = 0; i < n-1; i++)
{
int min = i;
for (int j = i+1; j < n; j++)
if (arr[j] < arr[min]) min = j;
int temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
JavaScript 示例:
selectionSort = arr => {
for (let i = 0; i < arr.length-1; i++) {
let min = i;
for (let j = i+1; j < arr.length; j++)
if (arr[j] < arr[min]) min = j;
let temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
return arr;
}
选择排序算法
-
最坏情况:
必须重复排序过程n
100 次,迭代n
数组中的每个元素,找到最小的未排序元素并对其进行排序。每次迭代只能对一个元素进行排序。
用大 O 表示法,它相当于O(n²)。 -
最佳情况:
与最坏情况相同,因为在排序过程迭代遍历数组的所有元素之前,无法保证数组已排序。
用大 O 表示法,它等于Ω(n²)。
插入排序
就地构建一个排序数组;在构建数组时,如果需要,可以移动元素以腾出空间。
伪代码示例#1:
Call the first element of the array sorted
Repeat until all elements are sorted:
Insert next unsorted item into sorted part shifting the required number of items
伪代码示例#2:
For i from 1 to n–1
Insert next unsorted item into sorted part shifting i items
C 语言示例:
void insertionSort(int arr[], int n)
{
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i-1;
while (j >= 0 && arr[j] > key) {
arr[j+1] = arr[j];
j = j-1;
}
arr[j+1] = key;
}
}
JavaScript 示例:
insertionSort = arr => {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i-1;
while (j >= 0 && arr[j] > key) {
arr[j+1] = arr[j];
j = j-1;
}
arr[j+1] = key;
}
return arr;
}
插入排序算法
-
最坏情况: 由于数组是逆序的,每次插入时
都必须将元素n
从原位置移位。 用大 O 表示法,就是O(n²)。n
-
最佳情况:
数组已经排序。只需在迭代每个元素时,在未排序元素和已排序元素之间不断切换即可。
用大 O 表示法,它等于Ω(n)。
递归
优雅地编写代码。🌹
递归与算法或函数的实现方式有关,它本身并不是算法。
递归函数在执行过程中调用自身。
使用阶乘函数的详细示例:
- n!在所有正整数上定义
- n!等于所有小于或等于n 的正整数相乘
- n!为
fact(n)
:
伪代码示例#1:
fact(1) = 1
fact(2) = 2 * 1
fact(3) = 3 * 2 * 1
…
伪代码示例#2:
fact(1) = 1
fact(2) = 2 * fact(1)
fact(3) = 3 * fact(2)
…
阶乘函数的递归定义的基础:
fact(n) = n * fact(n-1)
递归函数有两种情况可以应用于任何输入:
- 基本情况:触发时终止递归过程
- 递归情况:递归发生的地方
int fact(int n)
{
// base case
if (n == 1)
return 1;
// recursive case
else
return n * fact(n-1);
}
可以有多个基准情况。
例如斐波那契数列,其中:
- 第一个元素是
0
- 第二个元素是
1
n
第个元素是(n-1)+(n-2)
递归情况可能存在多种。
例如,collatz 猜想。
接下来的 C 和 JavaScript 示例定义了一个collatz
函数,用于计算“回到 1”需要多少步:
C 语言示例:
int collatz(int steps)
{
// base case
if (steps == 1) return 0;
// recursive case: even numbers
else if ((steps % 2) == 0) return 1+collatz(steps/2);
// recursive case: odd numbers
else return 1+collatz(3*steps+1);
}
JavaScript 示例:
collatz = steps => {
// base case
if (steps == 1) return 0;
// recursive case: even numbers
else if ((steps % 2) == 0) return 1+collatz(steps/2);
// recursive case: odd numbers
else return 1+collatz(3*steps+1);
}
合并排序
将数组分成更小的数组进行排序,然后按排序顺序将这些排序后的数组重新组合在一起。
伪代码示例#1:
If only one element
Return
Else
Sort left half of elements
Sort right half of elements
Merge sorted halves
伪代码示例#2:
Sort the left half of the array (assuming n > 1)
Sort right half of the array (assuming n > 1)
Merge the two halves together
C 示例(递归):
// merges two subarrays of arr[]
void merge(int arr[], int leftIndex, int mid, int rightIndex)
{
int n1 = mid-leftIndex+1;
int n2 = rightIndex-mid;
// temp arrays
int Left[n1], Right[n2];
// copy data to temp arrays
for (int i = 0; i < n1; i++)
Left[i] = arr[leftIndex+i];
for (int j = 0; j < n2; j++)
Right[j] = arr[mid+1+j];
// merge the temp arrays back into arr[]
int i = 0; // init index of 1st subarray
int j = 0; // init index of 2nd subarray
int k = leftIndex; // init index of merged subarray
while (i < n1 && j < n2)
{
if (Left[i] <= Right[j])
{
arr[k] = Left[i];
i++;
}
else
{
arr[k] = Right[j];
j++;
}
k++;
}
// copy the remaining elements of Left[], if any
while (i < n1)
{
arr[k] = Left[i];
i++;
k++;
}
// copy the remaining elements of Right[], if any
while (j < n2)
{
arr[k] = Right[j];
j++;
k++;
}
}
void mergeSort(int arr[], int leftIndex, int rightIndex)
{
if (leftIndex < rightIndex)
{
// instead of (l+r)/2 to avoid overflow
int mid = leftIndex+(rightIndex-leftIndex)/2;
// sort first and second halves
mergeSort(arr, leftIndex, mid);
mergeSort(arr, mid+1, rightIndex);
// merge them back together
merge(arr, leftIndex, mid, rightIndex);
}
}
JavaScript 示例(递归):
// to merge left subarray and right subarray
merge = (left, right) => {
let resultArray = [], leftIndex = 0, rightIndex = 0;
// concat values into the resultArray in order
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
resultArray.push(left[leftIndex]);
leftIndex++;
} else {
resultArray.push(right[rightIndex]);
rightIndex++;
}
}
// concat remaining element from either left OR right
return resultArray
.concat(left.slice(leftIndex))
.concat(right.slice(rightIndex));
}
mergeSort = arr => {
// if array has one element or is empty, no need to sort
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length/2);
// divide the array into left and right
const left = arr.slice(0, mid);
const right = arr.slice(mid);
// merge back together using recursion
return merge(mergeSort(left), mergeSort(right));
}
归并排序算法
-
最坏情况:
必须先拆分n
元素,然后再有效地重新组合它们,这会导致排序后的子数组在构建时耗时加倍。
用大 O 表示法,就是O(n log n)。 -
最佳情况:
数组已经排序,但仍然需要拆分并重新组合才能确定其是否已排序。
用大 O 表示法,它等于Ω(n log n)。
资源:
文章来源:https://dev.to/emilie_gl/notes-on-algorithms-36pi