【数据结构梳理01】数据结构概论
一、算法
算法(Algothrim)是指一个能够为完成某一特定任务的提供运算序列的有穷指令集。
算法应具有以下特性:
①有输入:一个算法必须有0个或多个输入,是算法开始前赋予算法的量。
②有输出:输出的是算法运算结果。
③有穷性:算法必须在有限步内完成任务,不能陷入死循环。这一点与程序不同:程序可以进入死循环,比如操作系统在用户未使用前一直处于“等待”,直到新的事件出现。
④确定性:即算法的每一步无二义性
⑤有效性:算法中的每一步运算足够基本。
二、递归
数学以及程序设计方法中为递归下的定义是:若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
在以下三种情况下,常常要用到递归的方法:
1.定义是递归的,例如在求解n!或者斐波拉契数列时;
2.数据结构是递归的,例如树,链表等;
3.问题的解法只能是递归:最典型的为Tower of Hanio。
在使用递归的方法解决问题前,我们需要清楚三点:
(1)对于一个较为复杂的问题,如果能够分解成几个相对简单的且解法相同或类似的子问题时,只要解决子问题,那么原问题便可迎刃而解;
(2)当分解后的子问题可以解决时,就停止分解。我们可以说我们获得了原子问题,也就是递归结束条件。例如在求解n!时,当分解到0!=1时我们便可停止分解,0!=1就是这个递归的递归结束条件。
(3)递归定义的函数可以直接编程求解,递归过程直接反应函数的结构。
三、算法性能分析
(一)空间复杂度
空间复杂度涉及的空间类型有:
输入空间: 存储输入数据所需的空间大小;
暂存空间: 算法运行过程中,存储所有中间变量和对象等数据所需的空间大小;
输出空间: 算法运行返回时,存储输出数据所需的空间大小;
通常情况下,空间复杂度指在输入数据大小为 N 时,算法运行所使用的「暂存空间」+「输出空间」的总体大小。
例如我们分析以下代码的空间复杂度:
float Sum(float*a, const int n)
{
float s = 0;
for(int i = 0; i < n; i++)
s += a[i];
return s;
}
问题规模虽然为n,程序中用一个常数n来存放累加项个数,还有一个浮点数存放计算结果;另外对于数组a[]来说,只需存放a的首地址a[0]即可,空间占有量也为常数,故该程序的空间复杂度为O(1).
又比如下程序:
float Rsum (float *a, const int n)
{
if (n <=0) return 0;
else return (Rsum(a,n-1)+a[n-1]);
}
这是一个递归函数,很容易看出递归栈的高度可以达到O(n),因此空间复杂度为O(n)。
(二)时间复杂度
程序步(Program Step):在语法或语义上有意义的一段指令序列,而这段序列执行时与实例特性无关。
分析时间复杂度主要是看语句的执行是否与实例特性有关,如果与实例特性无关的语句都可以看成一个程序步,即时间复杂度为O(1)。与实例特性相关的语句要具体问题具体分析,不能笼统而论,否则分析出来误差可能较大。
对于常见的时间复杂度的分析:(以下内容来自Leetcode)
四、算法实际时间获取
两种方法:一是插入计数变量count,计算程序步
二是建立一个表,列出程序中各语句的执行步数 。
如果在实际过程中,我们所测算的实例时间太短,那么我们就要采取重复执行取平均的方法来测量其时间,且通常重复次数需要按照不同的实例来取不同的执行次数。
下面是一个利用C++自带计时函数time来计算二分查找法的运行时间的程序:
int BinarySearch(int* a, const int x, const int n)
{//Serach the sorted array a[0],...,a[n-1] for x.
int left, right;
left = 0;
right = n - 1;
while (left <= right)
{
int middle;
middle = (left + right) / 2;
if (x < a[middle])
right = middle - 1;
else if (x > a[middle])
left = middle + 1;
else
return middle;
}
return -1;
}
void ObtainRuntime()
{
int a[101], n[20];
const long r[20] = { 10000000,20000000,30000000,40000000,50000000,60000000,
70000000,80000000,90000000,100000000,110000000,120000000,130000000,
140000000,150000000,160000000,170000000,180000000,190000000,200000000};
for (int j = 1; j <= 100; j++)
a[j] = j;
for (int j = 0; j < 10; j++)
{
n[j] = j;
n[j + 10] = 10 * (j + 1);
}
cout << " n\tTotal time\trun Time" << endl;
for (int j = 0; j < 20; j++)
{
struct timeb start;
struct timeb stop;
ftime(&start);
for (long b = 1; b <= r[j]; b++)
int k = BinarySearch(a,150,n[j]);
ftime(&stop);
long total = (stop.time - start.time) * 100 + (stop.millitm - start.millitm) / 10;
double runtime = (double)(total) / (double)(r[j]);
cout << " " << n[j] << "\t" << total << "\t" <<fixed<<setprecision(10) << runtime << endl;
}
}
int main()
{
ObtainRuntime();
return 0;
}
一定要注意time(start)与time(end)的插入位置,否则程序会出错