数据结构之基本概念

前言:本文为数据结构之基本概念,根据陈越姥姥的网课而整合的笔记。

一、什么是数据结构

我们以几个栗子展开解释:

栗子1:如何在书架上摆放图书?——如何在空间中储存数据?

图书的摆放需要有2个相关操作来实现

操作1:新书怎么插入? 操作2:怎么找到指定的某本书?

方法1:随便放(操作1实现简易,操作2实现困难)

方法2:按照书名的拼音字母顺序排放,二分法查找书(操作1实现困难,操作2 实现简易)

方法3:把书分成类别划分书架区域,按照书名的拼音字母顺序排放(操作1,操作2 均实现容易)

总结:解决问题方法的效率跟数据的组织方式有关。

栗子2:写程序实现一个函数PrintN,使得传入一个正整数为N的参数后,能顺序打印从1到N的全部正整数

方法一:循环实现

void PrintN(int N)
{
  int i;
  for(i=1;i<=N'i++){
    printf("%d\n",i);
  }
  return;
}

方法二:递归实现

void PrintN(int N)
{
  if(N){
    PrintN(N-1);
    printf("%d\n",N);
  }
  return;
}

当代入N为100,1000,10000,10000…时,会发现N过于大时,方法二因内存过大不执行。

总结:解决问题方法的效率,跟空间的利用效率有关。

栗子3:写程序计算给定多项式在给定点x处的值

方法一:f(x)=a(0)+a(1)x+…+a(n)x**(n)

double f(int n,double a[],double x)
{
  int i;
  double p=a[0];
  for(i=1;i<=n;i++)
  	p+=(a[i]*pow(x,i));
  return p;
}

方法二:f(x)=a(0)+x(a(1)+x(…(a(n-1)+x(a(n)))…))

double f(int n,double a[],double x)
{
  int i;
  double p=a[n];
  for(i=n;i>0;i--)
  	p=a[i-1]+x*p;
  return p;
}

调用clock()计算程序运行时间,发现方法二单次运行的时间较长。

总结:解决问题方法的效率,跟算法的巧妙程度有关。

所以什么是数据结构?

1.数据对象在计算机中的组织方式

(1)逻辑结构

  • 同属一个集合,没有关系——集合结构
  • 一对一——线性结构
  • 一对多——树型结构
  • 多对多——图形结构

(2)物理存储结构

以数组储存还是以链表储存

2.数据对象必定与一系列加在其上的操作相关联

3.完成这些操作所用的方法就是算法

怎么描述数据结构?

抽象数据类型

数据类型

  • 数据对象集
  • 数据集合相关联的操作集

抽象:描述数据类型的方法不依赖于具体实现

  • 与存放数据的机器无关
  • 与数据存储的物理结构无关
  • 与实现操作的算法和编程语言均无关

只描述数据对象集合相关操作机”是什么“,并不涉及“如何做到”的问题
总结:没有完美的数据结构,只有最合适的数据结构。应该因地制宜,不同的用途(查找、插入…)采用不同的数据结构来提高效率

二、什么是算法

算法

1.一个有限指令集

2.接受一些输入(有些情况下不需要输入)

3.产生输出

4.一定在有限步骤之后终止

5.每一条指令必须

  • 有充分明确的目标,不可以有歧义
  • 计算机能处理的范围之内
  • 描述应不依赖于任何一种计算机语言以及具体的实现手段
什么是好的算法?
  • 空间复杂度S(n)——根据算法写成的程序在执行时占用存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致内存超限,造成程序非正常中断,如栗子二。
  • 时间复杂度T(n)——根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果,如栗子三。
    总结:好的算法应该占用内存小,耗费时间短。
复杂度分析小窍门
  • 若两段算法分别有复杂度T1(n)=O(f1(n))和T2(n)=O(f2(n)),则
    1. T1(n)+T2(n)=max(O(f1(n),O(f2(n)))
    2. T1(n)xT2(n)=O(f1(n)x(f2(n))
  • 若T(n)是关于n的k阶多项式,那么T(n)=o(n**k)
  • 一个for循环的时间复杂度等于循环次数乘以循环体代码的复杂度
  • if-else结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大的

三、应用实例

题目:给定N个整数的序列{A1,A2,…,AN},求函数f(i,j)=max{0,{Ai+A(i+1)+…+Aj}}的最大值。

算法1:

int MaxSubseqSum1(int A[],int N)
{
  int ThisSum, MaxSum = 0;
  int i,j,k;
  for(i=0;i<N;i++){   /*i是子列左端位置*/
    for(j=i;j<N;j++){   /*j是子列右端位置*/
      ThisSum=0;   /*ThisSum是从A[i]到A[j]的子列和*/
      for(k=i;k<=j;k++)
      	ThisSum+=A[k];
      if(ThisSum>MaxSum)   /*如果刚得到的这个子列和更大*/
      	MaxSum=ThisSum;   /*则更新结果*/
    }/*j循环结束*/
  }/*i循环结束*/
  return MaxSum;
}                                                         

T(N)=O(N**3)

算法2:

int MaxSubseqSum2(int A[],int N)
{
  int ThisSum, MaxSum = 0;
  int i,j;
  for(i=0;i<N;i++){   /*i是子列左端位置*/
  	ThisSum=0;   /*ThisSum是从A[i]到A[j]的子列和*/
    for(j=i;j<N;j++){   /*j是子列右端位置*/
      ThisSum+=A[j];   /*对于相同的i,不同的j,只要在j-1次循环的基础上累加1项即可*/
      if(ThisSum>MaxSum)   /*如果刚得到的这个子列和更大*/
      	MaxSum=ThisSum;   /*则更新结果*/
    }/*j循环结束*/
  }/*i循环结束*/
  return MaxSum;

T(N)=O(N**2)

算法3:分而治之

把一个比较大的复杂的问题切分成小的块,然后分头去解决它们,最后再把结果合并起来,这就是“分而治之”

对于此问题,假设该问题是放在一个数组里面,首先第一步是”分“,也就是把这个数组从中间一分为二,然后递归地去解决左右两边的问题。递归地去解决左边的问题,我们会得到左边的一个最大子列和;递归地去解决右边的问题,我们会得到右边的一个最大子列和。还有一种情况时跨越边界的最大子列和。把这三个结果合成起来就叫做“治”。

我们来用个图举个栗子以供理解:
在这里插入图片描述

算法4:在线处理

int MaxSubseqSum4(int A[],int N)
{
  int ThisSum, MaxSum;
  int i;
  for(i=0;i<N;i++){
      ThisSum+=A[i];   /*向右累加*/
      if(ThisSum>MaxSum)
      	MaxSum=ThisSum;   /*发现更大和则更新当前结果*/
      else if(ThisSum<0)   /*如果当前子列和为负*/
      	ThisSum=0;   /*则不可能使后面的部分和增大,抛弃之*/
   }
  return MaxSum;
 }

T(N)=O(N)

"在线"的意思是指每输入一个数据就进行即时处理,在任何一个地方中止输入,算法都能正确给出当前的解。

四种算法运行时间相比较如下:
在这里插入图片描述

可知第四种算法更为优解。

声明:部分资料源自网络,如有侵权,请联系我删除!
文中如存在谬误、混淆等不足,欢迎批评指正!

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值