(笔记)数据结构 第一讲 基本概念

301 阅读3分钟

数据结构 陈越老师 - 中国大学MOOC

1.1 什么是数据结构

image-20200911205442403

1.2 什么是算法

image-20200911214004158

1.3 应用实例:最大子列和问题

给定N个整数的序列 {A_1, A_2, ..., A_N},求最大子列和,即如下函数的最大值:

f(i,j)=max{0,k=ijAk}f(i,j)=\max\{0,\sum_{k=i}^{j}A_k \}

算法1:暴力求解 T(N)=O(N^3)

image-20200920191218471

算法2:暴力求解,简化了一些 T(N)=O(N^2)

image-20200920191814973

算法3:分而治之 T(N)=O(Nlog(N))

该子列只有这三种可能:①在左边、②在右边、③跨过中间。

假如我有一个函数可以求出长度为K的子列的最大子列和。

那么,就可以用递归的思想,继续划分为更小的单元。

比如求①的时候变成求长度为K/2的子列的最大子列和,调用自身。

递归的终止条件:子列只有一个元素。

递归链条:

在左边、在右边,比较简单。

跨过中间的,比较麻烦。需要同时向两边扫描。

image-20200920201822191

//----------------------------方法3------------------------------//
 
int Max3( int A, int B, int C )
{ /* 返回3个整数中的最大值 */
    //return A > B ? A > C ? A : C : B > C ? B : C;
    return ( A > B ? (A > C ? A : C ) : (B > C ? B : C) ); // 加上括号好懂多了 
}
 
int DivideAndConquer( int List[], int left, int right )
{ /* 分治法求List[left]到List[right]的最大子列和 */
    int MaxLeftSum, MaxRightSum; /* 存放左右子问题的解 */
    int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界线的结果*/
 
    int LeftBorderSum, RightBorderSum;
    int center, i;
 
    if( left == right )  { /* 递归的终止条件,子列只有1个数字 */
        if( List[left] > 0 )  return List[left];
        else return 0;
    }
 
    /* 下面是"分"的过程 */
    
    /* 找到中分点 */
    center = ( left + right ) / 2; 
    
    /* 递归求得两边子列的最大和 */
    MaxLeftSum = DivideAndConquer( List, left, center );
    MaxRightSum = DivideAndConquer( List, center+1, right );
 
    /* 下面求跨分界线的最大子列和 */
    MaxLeftBorderSum = 0; LeftBorderSum = 0;
    for( i=center; i>=left; i-- ) { /* 从中线向左扫描 */
        LeftBorderSum += List[i];
        if( LeftBorderSum > MaxLeftBorderSum )
            MaxLeftBorderSum = LeftBorderSum;
    } /* 左边扫描结束 */
 
    MaxRightBorderSum = 0; RightBorderSum = 0;
    for( i=center+1; i<=right; i++ ) { /* 从中线向右扫描 */
        RightBorderSum += List[i];
        if( RightBorderSum > MaxRightBorderSum )
            MaxRightBorderSum = RightBorderSum;
    } /* 右边扫描结束 */
 
    /* 下面返回"治"的结果 */
    return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );
}
 
int MaxSubseqSum3( int List[], int N )
{ /* 保持与前2种算法相同的函数接口 */
    return DivideAndConquer( List, 0, N-1 );
}

算法4:T(N)=O(N)

image-20200920202404904

加强版:返回最大子列的起点、终点对应的数字,并处理特殊情况

01-复杂度2 Maximum Subsequence Sum (25分)

image-20200920221355506

#include<stdio.h>

//----------------------------方法4 补充------------------------------//

int MaxStart = 0; //最大子列和的起点 
int MaxEnd = 0; //最大子列和的终点 
int flag = -1; //-1表示全是负数,0表示有正数,正数表示仅负数和0情况下第一次遇到0的位置 

int MaxSubseqSum4p( int A[], int N )
{
	int ThisSum = 0, MaxSum = 0;
	int ThisStart = 0; // 当前子列的起点 
	int i;
	for( i = 0; i < N; i++ ) {
		if(A[i] > 0)
			flag = 0; //正常情况,有正数 
		else if (A[i] == 0 && flag < 0)
		{
			ThisStart = i+1;
			flag = i; //仅负数、0的情况,记录第一次遇到0的位置
		} 
			
		ThisSum += A[i]; /* 向右累加 */

		if( ThisSum > MaxSum )
		{
			MaxSum = ThisSum; /* 发现更大和则更新当前结果 */
			MaxStart = ThisStart;
			MaxEnd = i;
		}
		else if( ThisSum < 0 ) /* 如果当前子列和为负数*/ 
		{
			ThisSum = 0; /* 则不可能使后面的部分和增大,抛弃之 */ 
			              //但是 ThisSum == 0 的情况不能直接抛弃。因为如 1 -1 2 -2,根据要求最大子列是1 -1 2
			ThisStart = i+1;
		}
	}
	return MaxSum;
}
//----------------------------main函数------------------------------// 

int main()
{
	int K = 0, i = 0;
	//printf("请输入正整数 K = ");
	scanf("%d", &K);
	
	//printf("请输入K个数,用空格分隔:");
	int a[K];
	for(i=0; i<K; i++){
		scanf("%d", &a[i]);
	}

	printf("%d ", MaxSubseqSum4p(a, K));
	if(flag == 0) //正常情况,有正数 
		printf("%d %d", a[MaxStart], a[MaxEnd]); // 注意是返回起点、终点对应的数字,而不是起点、终点 
	else if(flag == -1) //全是负数 
		printf("%d %d", a[0], a[K-1]);
	else //仅负数、0
		printf("%d %d", a[flag], a[flag]);
}

image-20200920221517727

各种特殊情况,真是锻炼……哈哈哈