数据结构:【学习笔记】01 基本概念

1 数据结构

1.1 查询方法

实例:二分法查询。

1.1.1 二分法定义

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

1.1.2 查找过程

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

1.1.3 代码

Python代码

def bin_search(data_list, val):    
    low = 0                         # 最小数下标    
    high = len(data_list) - 1       # 最大数下标    
    while low <= high:        
        mid = (low + high) // 2     # 中间数下标        
        if data_list[mid] == val:   # 如果中间数下标等于val, 返回            
            return mid        
        elif data_list[mid] > val:  # 如果val在中间数左边, 移动high下标            
            high = mid - 1        
        else:                       # 如果val在中间数右边, 移动low下标            
            low = mid + 1    
    return # val不存在, 返回None
ret = bin_search(list(range(1, 10)), 3)
print(ret)

C/C++代码:

int bsearchWithoutRecursion(int array[],int low,int high,int target)
{
    while(low<=high)
        {
            int mid=low+(high-low)/2;//还是溢出问题
            if(array[mid]>target)
                high=mid-1;
            else if(array[mid]<target)
            low=mid+1;
            else
                return mid;
        }
    return-1;
}
int binSearch(const int *Array,int start,int end,int key)
{
        int left,right;
        int mid;
        left=start;
        right=end;
        while(left<=right)
             
        {
                    mid=left+(right-left)/2;//还是溢出问题
                    if(key==Array[mid])  return mid;
                    else if(key<Array[mid]) right=mid-1;
                    else if(key>Array[mid]) left=mid+1;
                 
        }
        return -1;
}

1.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=100000的时候,递归函数将无法实现,这是因为递归函数对空间内存占有率过大。当内存超出指定范围的时候,编译器将直接报错,程序无法运行下去。

所以,解决问题方法的效率,也是跟空间的利用效率有关。

1.3 算法效率

实例:计算x的多项式f(x)的值。

方法一:小白写法(逐一计算法)

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;
}

方法二:专业写法(因式分解法)

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;
}

方法一在时间上面远远高于方法二,故而方法二比方法一高级,我们采用模板来计算二者的时间:

# include<cstdio>
# include<ctime>

clock_t start, stop;

double duration;

int main() {
    // 开始计时
	start = clock();
	// 待测函数
	MyFunction();
	// 停止计时
	stop = clock();
	duration = ((double)(stop - start)) / CLK_TCK;

	return 0;
}

注意:如果一次时间太短无法被捕捉,则可以多次重复运行该函数,最后处以次数就可以得到运行一次函数所需要的时间。

1.4 抽象数据类型

数据类型

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

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

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

2 算法

2.1 定义

  • 一个有限的指令集

  • 接受一些输入

  • 产生输出

  • 一定在有限步骤之后终止

  • 每一条指令必需:

    1、有充分明确的目标,不可歧义
    2、计算机能处理的范围内
    3、表述不依赖于任何一种语言及实现手段

如选择排序算法的伪码描述:

void SelecttionSort(int List[], int N){
	for (i = 0;i < N;i++) {
		MinPosition = ScanForMin(List, i, N);
		swap(List[i], List[MinPosition]);
	}
}

2.2 空间复杂度S(n)

失败案例:递归函数。

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

原因:在调用printN(100000)后,执行printN(99999),再执行printN(99998),直到printN(0)执行完成后,所以函数才会return,此时空间复杂度正比于输入值N。如果用循环函数实现时,则空间占有量始终为一个常量,不随N值的变化而变化。

2.3 时间复杂度T(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;
}

原因:时间复杂度计算时加减法可以忽略不记,主要研究乘法。上述案例中,运用了pow函数,一共运用了(n + 1)* n / 2 次乘法运算,故时间复杂度为T(n) = C1 * n^2 + C2 * n 。而如果采用因式分解的话,则使用了n次乘法运算,时间复杂度为T(n) = C * n。

2.4 最坏情况复杂度和平均复杂度

我们在计算复杂度的时候经常比较最坏情况复杂度(因为在某写算法中,平均复杂度很难计算),从而来比较算法的优劣程度。

2.5 复杂度的渐进表示法

  • T(n) = O(f(n)) 表示存在常数C > 0, n0 > 0 使得当 n > n0 时有T(n) <= C * f(n)
  • T(n) = G(g(n)) 表示存在常数C > 0, n0 > 0 使得当 n > n0 时有T(n) >= C * g(n)
  • T(n) = K(h(n)) 表示同时有 T(n) = O(h(n)) 和 T(n) = G(h(n))

2.6 实例分析

例子:给定N个整数的序列{A1,A2,…,An},求函数f(i,j)= max{0,Ai +…+ Aj} 的最大值

2.6.1 算法一:
int MaxSubseqSum1(int A[], int N)
{
  int ThisSum, MaxSum = 0;
  int i, j, k;
  for(i = 0; i < N; i++){
    for(j = i; j < N; j++){
      ThisSum = 0;
      for(k = i; k <= j; k++)
        ThisSum += A[k];
      if(ThisSum > MaxSum)
        MaxSum = ThisSum;
    }
  }
  return MaxSum;
}

算法复杂度:T ( N ) = O ( N ^ 3 )

2.6.2 算法二:
int MaxSubseqSum1(int A[], int N)
{
  int ThisSum, MaxSum = 0;
  int i, j, k;
  for(i = 0; i < N; i++){
    ThisSum = 0;
    for(j = i; j < N; j++){
      ThisSum += A[j];
      if(ThisSum > MaxSum)
        MaxSum = ThisSum;
    }
  }
  return MaxSum;
}

算法复杂度:T ( N ) = O ( N ^ 2 )

2.6.3 算法三:分而治之

原理:将数列对半分,分别求出两边的最大子列和,然后再计算过中点线的最大子列和,依此递推。其算法复杂度推导如下:

T ( N ) = 2 * T ( N / 2 ) + C * N , 其中: T ( 1 ) = O ( 1 )
T ( N ) = 2 ^ k * O ( 1 ) + C * k * N , 其中: N / 2 ^ k = 1
T ( N ) = O ( N * log N )

代码略。

2.6.4 算法四:在线处理
int MaxSubseqSum4(int A[], int N) {
	int ThisSum, MaxSum;
	int i;
	ThisSum = MaxSum = 0;
	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 )

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值