week 3 Algorithms
回顾前面所用到的算法耗费的时间:在电话本一次一页、一次两页、半分法
搜索算法
-
线性搜索 O ( n l o g n ) O(nlogn) O(nlogn)
通过 7 个箱子里面放的纸币,志愿者查找需要的面额进行演示
伪代码: For each dorr from left to right If 50 is behind the door Return Ture Return False 转化: For i from 0 to n-1 if 50 is behind door[i] Return Ture Return False 错误的伪代码: For each dorr from left to right If 50 is behind the door Return Turn Else Return False 查找到第一个不符合的时候会直接返回 False
-
二分算法(前提:数组是有序的) O ( l o g n ) O(logn) O(logn)
伪代码: If no doors left Return false If 50 is behind the door Return ture Else if 50 is < middle door Search left half Else if 50 is > middle door Search right half 转化: If no doors left Return false If 50 is behind doors[middle] Return ture Else if 50 < doors[middle] Search doors[0] to doors[middle - 1] Else if 50 > doors[middle] Search doors[middle + 1] to doors[n - 1] 注意:doors[middle] 已经比较过了,所以从这个数的两边开始;n 是数组的长度,数组最后一个是 n-1
算法所用的时间
算法所用的大致时间(最糟糕的情况 upper bound),从慢到快:
- $ O (n^{2} )$
- O ( n l o g n ) O(nlogn) O(nlogn)
- O ( n ) O(n) O(n):线性搜索
- O ( l o g n ) O(logn) O(logn):二分算法
- O ( 1 ) O(1) O(1)
算法所用的大致时间(最好的情况 lower bound),从慢到快:
- $\Omega (n^{2}) $
- $\Omega (nlogn) $
- $\Omega (n) $
- $\Omega (logn) $
- $\Omega (1) $:线性搜索、二分算法
当 upper bound = lower bound,用 $\Theta $ 表示,大小同上
将上述的线性搜索伪代码转换成实际代码
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int numbers[] = {20, 1, 100, 15, 50, 10, 200};
int num = get_int("Number: ");
for (int i = 0; i < 7; i++)
{
if (numbers[i] == num)
{
printf("Found\n");
return 0; // 是必须的,不返回会使得循环继续执行
// break 使用这个可以停止循环,但还是会继续执行后面的打印操作
}
}
printf("Not found\n");
return 1;
}
实际应用的代码:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string names[] = {"Cater", "David"};
string nums[] = {"138-111-1256", "153-256-4859"};
string name = get_string("Name: ");
for (int i = 0; i < 2; i++)
{
if (strcmp(names[i], name) == 0)
{
printf("%s\n", nums[i]);
return 0;
}
}
printf("Not found\n");
return 1;
}
这个代码中可能存在的问题:
- 数字 2 ,如果添加或删除了数据,需要手动改变,容易漏掉
- 可能会出现输入了名字,但是忘记输入号码的情况,这时代码会出错
- 目前是假设名字和号码的顺序是相对应的,如果号码的顺序改变,也会出错
构造数据结构
解决办法:C 语言可以自行构造数据结构
typedef struct // 标准写法
{
// 中间的内容可以自行定义
string name;
string number;
}
person; // 新的数据类型的名字
修改后的代码如下:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
string name;
string number;
}
person;
int main(void)
{
person people[2];
people[0].name = "Cater";
people[0].number = "138-111-1256";
people[1].name = "David";
people[1].number = "153-256-4859";
string name = get_string("Name: ");
for (int i = 0; i < 2; i++)
{
if (strcmp(people[i].name, name) == 0)
{
printf("%s\n", people[i].number);
return 0;
}
}
printf("Not found\n");
return 1;
}
排序方法
-
排序——选择排序 $ \Theta (n^{2} )$
用一个变量来记录最小的数,并和需要去的位置进行交换
伪代码如下:
For i from 0 to n-1 Find smallest number between numbers[i] and numbers[n-1] Swap smallest number with numbers[i]
-
排序——冒泡排序 $ O (n^{2} )$ $\Omega (n) $
当需要排序的数组已经是处于大部分已排序的状态,这个方法更节省时间
每次比较两个数的大小,较大的那个数往后移动,一直到没有需要交换为止
伪代码如下:
Repeat n times For i from 0 to n-2 // 为什么是 n-2 ,因为最后一个是 n-1 而这个需要两个数字比较,如果到 n-1 ,那么会超出数组的边界 If numbers[i] and numbers[i+1] out of order Swap them If no swap Quit
递归 recursion
迭代方式:
#include <cs50.h>
#include <stdio.h>
void draw(int n);
int main(void)
{
int height = get_int("Height: ");
draw(height);
}
void draw(int n)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < i + 1; j++)
{
printf("#");
}
printf("\n");
}
}
递归方式:
#include <cs50.h>
#include <stdio.h>
void draw(int n);
int main(void)
{
int height = get_int("Height: ");
draw(height);
}
void draw(int n)
{
// 传入的参数小于等于 0 时,返回
if (n <= 0)
{
return;
}
// 画 n 层 = 画 n-1 层 + 自身的一层
// 画 n-1 层
draw(n - 1);
// 自身的一层
for (int i = 0; i < n; i++)
{
printf("#");
}
printf("\n");
}
利用递归的特性产生的排序方法——归并排序
这个方法会占用更多的内存,速度会更快 $\Theta(n logn) $
伪代码:
If only one number
Quit
Else
Sort left half of number
Sort right half of number
Merge sorted halves