数据结构—— 基本概念

Posted 大彤小忆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构—— 基本概念相关的知识,希望对你有一定的参考价值。

1. 什么是数据结构

1.1 关于数据结构的一些定义

  • 数据结构是数据对象,以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这些联系可以通过定义相关的函数来给出。”   ——Sartaj Sahni,《数据结构、算法与应用》
  • 数据结构是ADT(抽象数据类型Abstract Data Type)的物理实现。”  ——Clifford A.Shaffer,《数据结构与算法分析》
  • 数据结构(Data Structure)是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法。”  ——中文维基百科

1.2 解决问题方法的效率

  解决问题方法的效率:1. 跟数据的组织方式有关;
            2. 跟空间的利用效率有关;
            3. 跟算法的巧妙程度有关。

1.2.1 跟数据的组织方式有关

  例1:如何在书架上摆放图书?

  • 方法1: 随便放
    操作1:新书怎么插入? 哪里有空放哪里,一步到位!
    操作2:怎么找到某本指定的书? 一本一本挨着找,累死!
  • 方法2: 按照书名的拼命字母顺序查找
    操作1:新书怎么插入? 新进一本书《阿Q正传》,a开头,需要把后面的所有书往后挪
    操作2:怎么找到某本指定的书? 二分查找!
  • 方法3: 把书架划分为几块区域,每块区域指定摆放某种类别的图书,在每种类别内,按照书名的拼音字母顺序摆放
    操作1:新书怎么插入?先定类别,二分查找确定位置,移出空位
    操作2:怎么找到某本指定的书? 先定类别,再二分查找
    问题:空间如何分配?类别应该分多细?

  例1告诉我们的是,解决问题方法的效率,跟数据的组织方式有关

1.2.2 跟空间的利用效率有关

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

  用循环和递归两种方法分别实现,实现代码如下所示。

#include<iostream>
using namespace std;

//循环实现
void PrintN_Iteration(int n)
{
	for (int i = 1; i <= n; i++)
	{
		cout << i << endl;
	}
}

//递归实现
void PrintN_Recursion(int n)
{
	if(n)
	{
		PrintN_Recursion(n - 1);
		cout << n << endl;
	}
}

int main()
{
	PrintN_Iteration(10000);
	//PrintN_Recursion(10000);

	system("pause");

	return 0;
}

  依次测试n=10、100、1000、10000…,当n=10000时,循环实现和递归实现的结果如下图所示。

  • 循环实现:可以正常打印

在这里插入图片描述

  • 递归实现:出现异常,无法正常打印(将自己的所有可使用的空间用完,还不够,所以出现异常)

在这里插入图片描述
  例2告诉我们的是,解决问题方法的效率,跟空间的利用效率有关

1.2.3 跟算法的巧妙程度有关

  例3:写程序计算给定多项式 f ( x ) = a 0 + a 1 x + . . . + a n − 1 x n − 1 + a n x n f(x)=a_{0}+a_{1}x+...+a_{n-1}x^{n-1}+a_{n}x^{n} f(x)=a0+a1x+...+an1xn1+anxn在给定点x处的值。

  最直接的方法实现代码如下所示。

#include<iostream>
using namespace std;

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

int main()
{
	double a[] = { 0,1,2,3,4,5,6,7,8,9 };
	cout << "f1(1.1) = " << f1(9, a, 1.1) << endl;

	system("pause");

	return 0;
}

f1(1.1) = 84.0626

  但通常情况下,我们不会使用上述方法,简化 f ( x ) = a 0 + a 1 x + . . . + a n − 1 x n − 1 + a n x n = a 0 + x ( a 1 + x ( . . . ( a n − 1 + x ( a n ) ) . . . ) ) f(x)=a_{0}+a_{1}x+...+a_{n-1}x^{n-1}+a_{n}x^{n}=a_{0}+x(a_{1}+x(...(a_{n-1}+x(a_{n}))...)) f(x)=a0+a1x+...+an1xn1+anxn=a0+x(a1+x(...(an1+x(an))...)),编写代码如下所示。

#include<iostream>
using namespace std;

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

int main()
{
	double a[] = { 0,1,2,3,4,5,6,7,8,9 };
	cout << "f2(1.1) = " << f2(9, a, 1.1) << endl;

	system("pause");

	return 0;
}

f2(1.1) = 84.0626

  常用第二种方法计算给定多项式 f ( x ) = a 0 + a 1 x + . . . + a n − 1 x n − 1 + a n x n f(x)=a_{0}+a_{1}x+...+a_{n-1}x^{n-1}+a_{n}x^{n} f(x)=a0+a1x+...+an1xn1+anxn在给定点x处的值,是因为第二种方法相比于第一种方法运行更快。
  使用clock()计时函数记录程序运行时间,这个时间单位是clock tick,即“时间打点”。计算上述两种方法编写的程序运行时间的代码如下所示。

#include<iostream>
using namespace std;
#include<ctime>

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

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

int main()
{
	double a[] = { 0,1,2,3,4,5,6,7,8,9 };

	clock_t startTime, endTime;
	startTime = clock();//计时开始
	f1(a, 9, 1.1);
	endTime = clock();//计时结束
	cout << "The run time of f1 is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC  << "s" << endl;  //常数CLOCKS_PER_SEC为机器时钟每秒所走的时钟打点数
	cout << "f1(1.1) = " << f1(a, 9, 1.1) << endl;

	startTime = clock();//计时开始
    f2(a, 9, 1.1);
	endTime = clock();//计时结束
	cout << "The run time of f2 is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC  << "s" << endl;
	cout << "f2(1.1) = " << f2(a, 9, 1.1) << endl;

	system("pause");

	return 0;
}

  代码运行结果如下图所示。

在这里插入图片描述
  因为运行速度太快,所以结果均显示为0,无法区分快慢。解决方案: 让被测函数重复运行充分多次,使得测出的总的时钟打点间隔充分长,最后计算被测函数平均次数运行的时间即可。修改main函数部分的代码为如下所示。

int main()
{
	double a[] = { 0,1,2,3,4,5,6,7,8,9 };

	clock_t startTime, endTime;
	startTime = clock();//计时开始
	for (int i = 0; i < 1e7; i++)
	{
		f1(a, 9, 1.1);
	}
	//f1(a, 9, 1.1);
	endTime = clock();//计时结束
	//cout << "The run time of f1 is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC  << "s" << endl;  //常数CLOCKS_PER_SEC为机器时钟每秒所走的时钟打点数
	cout << "The run time of f1 is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC / 1e7 << "s" << endl;
	cout << "f1(1.1) = " << f1(a, 9, 1.1) << endl;

	startTime = clock();//计时开始
	for (int i = 0; i < 1e7; i++)  //重复调用函数以获得充分多的时钟打点数
	{
		f2(a, 9, 1.1);
	}
	//f2(a, 9, 1.1);
	endTime = clock();//计时结束
	//cout << "The run time of f2 is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC  << "s" << endl;
	cout << "The run time of f2 is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC / 1e7 << "s" << endl;  //计算函数单次运行时间
	cout << "f2(1.1) = " << f2(a, 9, 1.1) << endl;

	system("pause");

	return 0;
}

  代码运行结果如下图所示。

在这里插入图片描述
  由上述结果可以看出,第二种方法编写的程序运行时间更快。

  例3告诉我们的是,解决问题方法的效率,跟算法的巧妙程度有关

1.3 到底什么是数据结构

  • 数据结构是关于数据对象在计算机中的组织方式
    ⋄ \\diamond 逻辑结构
    ⋄ \\diamond 物理存储结构
  • 数据对象必定与一系列加在其上的操作相关联
  • 完成这些操作所用的方法就是算法

1.4 抽象数据类型

  抽象数据类型(Abstract Data Type,ADT)是计算机科学中具有类似行为的特定类别的数据结构的数学模型;或者具有类似语义的一种或多种程序设计语言的数据类型。抽象数据类型是描述数据结构的一种理论工具,其目的是使人们能够独立于程序的实现细节来理解数据结构的特性。抽象数据类型的定义取决于它的一组逻辑特性,而与计算机内部如何表示无关。

  • 数据类型
    ⋄ \\diamond 数据对象集
    ⋄ \\diamond 数据集合相关联的操作集
  • 抽象: 描述数据类型的方法不依赖于具体实现
    ⋄ \\diamond 与存放数据的机器无关
    ⋄ \\diamond 与数据存储的物理结构无关
    ⋄ \\diamond 与实现操作的算法和编程语言均无关

  只描述数据对象集和相关操作集“是什么”,并不涉及“如何做到”的问题。

  例4:“矩阵”的抽象数据类型定义。

   ∙ \\bullet C# Marshal类基本概念和入门示例程序

第五篇:模块

c_cpp Robolution基本代码片段

html PHP代码片段: - AJAX基本示例:此代码演示了使用PHP和JavaScript实现的基本AJAX功能。

Python的基本库与第三方库

初学者 CodeIgniter 概念 - 可重用的视图代码,该去哪里? (帮手?)