一个北大学生学习数据结构的经验文档格式.docx
《一个北大学生学习数据结构的经验文档格式.docx》由会员分享,可在线阅读,更多相关《一个北大学生学习数据结构的经验文档格式.docx(22页珍藏版)》请在冰豆网上搜索。
其中D是数据元素的有限集,S是D上关系的有限集。
所以在设计数据结构时,首要的任务就是找出要操作的数据,其次是挖掘出数据间的关系。
这两步完成以后,数据的逻辑结构就定下来了。
其中数据间的结构有以下几种:
集合,这和数学中的集合概念是一致的;
线性结构,即数据元素之间一对一的关系;
树形结构,即数据元素之间一对多的关系;
图状结构或网状结构,即数据元素之间多对多的关系。
然而只有逻辑结构是不够的,程序要能够运行,必须把数据的逻辑结构在计算机中表示出来,也就是设计物理结构。
大多数高级语言都对数据的物理结构有较好支持,如各种数据类型。
作者在解释数据类型的概念时说到:
“引入数据类型的目的,从硬件的角度看,是作为解释计算机内存中信息含义的一种手段,而对使用数据类型的用户来说,实现了信息的隐蔽,即将一切用户不必了解的细节都封装在类型中。
”这个概括非常精辟,从中可以看出以后的OOP只是在更高层次上对信息的封装和隐蔽。
对数据类型进一步扩展,作者引出了抽象数据类型的概念。
抽象数据类型(ADT)是指一个数学模型以及定义在该模型上的一组操作。
在引入抽象数据类型后,使逻辑结构更加独立,从而让程序员可以更加专注于逻辑结构的设计。
把抽象数据类型用公式表示出来,就是(D,S,P),其中D是数据对象,S是D上的关系集,P是对D的基本操作集。
如果计算机解题一定要遵循一个通用的模式的话,上面这个式子就给出了答案。
读《数据结构(C语言版)》
(2)
本节谈一谈算法分析和大O估算法(big-Onotation)。
算法效率的度量一般采用事前分析估算的方法,通常的做法是,“从算法中选取一种对于所研究的问题(或算法类型)来说是基本操作的原操作,以该基本操作重复执行的次数作为算法的时间量度”。
谈到这里时,作者引出了大O估算法。
在本书中,作者对大O估算法的介绍显得有些草率。
一开始就冒出一个式子T(n)=O(n3),然后在本页最底下用小字介绍了所谓的“"
O"
的形式定义”:
若f(n)是正整数n的一个函数,则xn=O(f(n))表示存在一个正的常数M,使得当n≥n0时都满足|xn|≤M|f(n)|。
也许是我数学基础太差,总之看到这个定义时我一头雾水。
不知道为什么作者没有花一点篇幅介绍大O估算法的由来和定义。
我google了一下,发现了这样的介绍:
Definition:
Atheoreticalmeasureoftheexecutionofanalgorithm,usuallythetimeormemoryneeded,giventheproblemsizen,whichisusuallythenumberofitems.Informally,sayingsomeequationf(n)=O(g(n))meansitislessthansomeconstantmultipleofg(n).Thenotationisread,"
fofnisbigohofgofn"
.
FormalDefinition:
f(n)=O(g(n))meanstherearepositiveconstantscandk,suchthat0≤f(n)≤cg(n)foralln≥k.Thevaluesofcandkmustbefixedforthefunctionfandmustnotdependonn.
Note:
Asanexample,n2+3n+4isO(n2),sincen2+3n+4<
2n2foralln>
10.Strictlyspeaking,3n+4isO(n2),too,butbig-Onotationisoftenmisusedtomeanequaltoratherthanlessthan.Thenotionof"
equalto"
isexpressedbyΘ(n).
Theimportanceofthismeasurecanbeseenintryingtodecidewhetheranalgorithmisadequate,butmayjustneedabetterimplementation,orthealgorithmwillalwaysbetooslowonabigenoughinput.Forinstance,quicksort,whichisO(nlogn)onaverage,runningonasmalldesktopcomputercanbeatbubblesort,whichisO(n2),runningonasupercomputeriftherearealotofnumberstosort.Tosort1,000,000numbers,thequicksorttakes20,000,000stepsonaverage,whilethebubblesorttakes1,000,000,000,000steps!
Anymeasureofexecutionmustimplicitlyorexplicitlyrefertosomecomputationmodel.Usuallythisissomenotionofthelimitingfactor.Foroneproblemormachine,thenumberoffloatingpointmultiplicationsmaybethelimitingfactor,whileforanother,itmaybethenumberofmessagespassedacrossanetwork.Othermeasureswhichmaybeimportantarecompares,itemmoves,diskaccesses,memoryused,orelapsed("
wallclock"
)time.
(以上介绍来自:
PaulE.Black,"
big-Onotation"
fromDictionaryofAlgorithmsandDataStructures,PaulE.Black,ed.,NIST.)
另外,这个帖子也讨论了算法的时间复杂度估计,说得非常通俗易懂。
说明:
因我上传附件受到限制,只能这样发帖。
读《数据结构(C语言版)》(3)
【问题描述】
设计一个可进行复数运算的演示程序。
【基本要求】
实现下列六种基本运算:
由输入的实部和虚部生成一个复数;
两个复数求和;
两个复数求差;
两个复数求积;
从已知复数中分离出实部;
从已知复数中分离出虚部。
运算结果以相应的复数或实数的表示形式显示。
【测试数据】
对下列各对数据实现求和:
0;
应输出“0”
3.1,0;
4.22,8.9;
应输出“7.32+i8.9”
-1.33,2.34;
0.1,-6.5;
应输出“-1.23-i4.16”
0,9.7;
-2.1,-9.7;
应输出“-2.1”
7.7,-8;
-7.7,0;
应输出“-i8”
【实现提示】
定义复数为由两个相互之间存在次序关系的实数构成的抽象数据类型,则可以利用实数的操作来实现复数的操作。
【我的源码】
#include<
stdio.h>
stdlib.h>
#defineMAXLINE100
typedefstruct
{
doublereal;
doubleimag;
}Complex;
ComplexInitComplex(doublereal,doubleimag)
Complexc;
c.real=real;
c.imag=imag;
returnc;
}
ComplexAdd(Complexc1,Complexc2)
c.real=c1.real+c2.real;
c.imag=c1.imag+c2.imag;
ComplexSubtract(Complexc1,Complexc2)
c.real=c1.real-c2.real;
c.imag=c1.imag-c2.imag;
ComplexMultiply(Complexc1,Complexc2)
c.real=c1.real*c2.real-c1.imag*c2.imag;
c.imag=c1.real*c2.imag+c1.imag*c2.real;
doubleGetReal(Complexc)
returnc.real;
doubleGetImag(Complexc)
returnc.imag;
voidRead(Complex*pc)
chara[MAXLINE];
inti,c;
for(i=0;
(c=getchar())!
='
'
&
&
c!
;
'
i++)
a[i]=c;
a[i]='
\0'
pc->
real=atof(a);
if(c=='
)
{
imag=0;
return;
}
imag=atof(a);
voidPrint(Complexc)
if(c.real!
=0&
c.imag!
=0)
printf("
%g"
c.real);
if(c.imag>
0)
+i%g"
c.imag);
else
-i%g"
-c.imag);
elseif(c.real==0&
i%g"
main()
intc;
Complexc1,c2,result;
clrscr();
while
(1)
Selectanoperation(press1,2,3or4):
\n\n"
);
--------------------------------------------\n\n"
1.Addacomplextoanother\n\n"
2.Subtractacomplexfromanother\n\n"
3.Multiplyacomplextoanother\n\n"
4.Quit\n\n"
while((c=getch())!
1'
2'
3'
4'
;
Inputtwocomplexes(e.g.:
1.3,2.4;
6;
):
Read(&
c1);
c2);
\nTheresultis:
\t"
switch(c)
case'
:
result=Add(c1,c2);
break;
result=Subtract(c1,c2);
result=Multiply(c1,c2);
Print(result);
\nTherealis:
\t%g"
GetReal(result));
\nTheimagis:
\t%g\n\n"
GetImag(result));
读《数据结构(C语言版)》(4)
从本节开始讨论线性表,这次先讨论线性表的顺序实现。
一提到线性表,我们脑子很可能会出现数组、链表这样的概念。
没错,数组和链表都是线性表,但它们只是线性表的两种实现,强调的是线性表的物理结构。
我们研究一个数据结构时,一般先从它的逻辑结构入手,等研究清楚了逻辑结构再考虑具体的物理实现。
在写程序时,思路也是一样的,先要分清哪些问题是逻辑的,哪些问题是物理的,先逻辑后物理是计算机解题的一般步骤。
如果开始不想清楚逻辑,而一头扎到物理细节中,就容易理不清思路或者作出有缺陷的设计。
当然这不是绝对的,很多情况下物理结构也会影响逻辑结构的设计。
简单来说,一个线性表是n个相同特性的数据元素的有限序列。
这里“n个相同特性的数据元素”指的是数据对象,“序列”指的是数据关系,每个数据元素都有一个确定的位置。
从数据对象和数据关系入手,就很容易看清一个数据结构的本质。
就线性表来说,只要某些数据存在次序关系,并且各个元素特性相同,就可以认为是一个线性表。
下面我们来考虑线性表的顺序实现。
在很多人包括我自己的眼里,线性表的顺序实现已经和数组画上了等号。
虽然用数组实现线性表的顺序存储结构天经地义,但不应该把顺序实现局限在数组上。
如果有一天你必须用汇编语言实现一个顺序存储的线性表,没有了数组你岂不是哭了。
如作者所说,线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素。
用C语言实现时,由于线性表的长度可变,且所需最大存储空间随问题不同而不同,所以通常用动态分配的一维数组来实现。
#defineLIST_INIT_SIZE100
//初始长度
#defineLISTINCREMENT
10
//分配增量
typedefstruct{
ElemType*elem;
//存储基址
intlength;
//当前长度
intlistsize;
//存储容量
}SqList;
用上述结构在分配存储空间时,初始分配可以用malloc,空间不够再分配时可以用realloc,这个函数保证在原分配基础上扩大容量时不影响其内容。
读《数据结构(C语言版)》(5)
考研终于尘埃落定,这个系列也得以继续。
查看上篇文章的发表日期,已一月有余。
回想这一个月中的种种经历,仍然心有余悸,听到看到的种种现象,更是让人触目惊心。
还好,一切都过去了,我又可以静静地写文章了。
上篇谈到线性表的顺序表示,这次接着谈线性表的链式表示。
顺序表示的优势很明显,它在数据的物理位置中隐含了数据的逻辑关系,简单直接又威力无穷。
但缺点也很明显,在做插入或删除操作时,需要移动大量数据。
为了克服这个缺点,可以使用链式存储。
链式存储不用物理位置隐含表示数据关系,它增加了一个指针域专门用来描述数据间的先后次序关系。
在一个节点中,数据域存储数据信息,而指针域存储数据关系信息,结构真是“相当的”清晰。
这样虽然克服了顺序表示的缺点,但同时带来了两个弊端。
一是要存储的数据量变大,二是不能随机访问某一个节点。
所以线性表的两种物理表示没有好坏之分,只有适用不适用的区别。
作者在介绍链式表示时,还介绍了一种静态单链表。
它用数组来存储链式表示中的节点,其中存储节点的指针域不是C语言中的指针,而是这个数组的下标,这个下标指定了下一个数据元素在数组中的位置。
作者介绍说,这种描述方法便于在不设“指针”类型的高级程序设计语言中使用链表结构。
这种描述方法和有指针的链表区别不大,除了要程序员自己实现malloc和free两个函数。
其实说白了,这种方法就是要程序员管理一大片连续的内存,在这篇内存中实现链式存储,其中主要就是实现分配和回收。
说到这里你会发现,线性表是一种逻辑结构,顺序表示和链式表示是这种逻辑结构的两种物理表示。
而在链式表示中,具体的实现又可以分为动态和静态链表。
在这个层次上,链式表示是一种逻辑结构,而动态链表和静态链表是这种逻辑结构的两种物理表示。
这么说好像有点唐僧,他明白了,你明白了吗?
总之逻辑和物理并不是绝对的,但先逻辑后物理是一般的思考方法。
其实学计算机对数学要求高,也和先逻辑后物理的思考方法相关。
数学是用在逻辑这一层的,逻辑上想清楚了,大的方向就对了,至于物理实现,只是用计算机把逻辑思维表示出来,真正的挑战还在逻辑层面。
读《数据结构(C语言版)》(6)
本来上一节介绍链式表示时,还应提到循环链表和双向链表,但我决定还是不提为好。
如果将学习一门课程的方法比作遍历算法的话,我觉得广度优先算法要比深度优先算法好。
一门全新的课程,如果一开始就进入具体的细节的话,很容易有挫折感,进而丧失兴趣。
所以如果能对这门课程有一个大局观,了解这门课程是讲什么的,涉及那些知识,学了有什么用,然后再由浅入深、循序渐进的逐步深入应该效果更好。
但是很可惜,学校授课时从来都是按照深度优先算法,把一章的细节讲得清清楚楚再进入下一章。
结果常常是,上第一节课时还很有兴趣的同学在听完一章后很多都打消了再听下去的念头。
既然我是自学,自然是按照广度优先算法,不关心每一个例程是怎么实现的,先在脑子中建立各种数据结构的概念和模型,更多的细节写程序时自然会考虑到。
顺便提一下,我觉得数据结构这门课一定要写大量的程序才可以真正学透,光是看懂书上的例程不会有太大帮助。
套用前面提到的先逻辑后物理的思路,看书是逻辑层面的,只在脑子中弄清概念、建立模型,而写程序是物理层面的,把脑子中的概念和模型用编程语言描述出来。
回到开头提到的循环链表和双向链表,我觉得只要理解这两个概念就可以了,真正写程序时如果有这种需要,能想起来用它们就可以了,至于实现细节我相信对于一个熟练程序员来说不是什么问题。