数据结构的概念.docx
《数据结构的概念.docx》由会员分享,可在线阅读,更多相关《数据结构的概念.docx(20页珍藏版)》请在冰豆网上搜索。
![数据结构的概念.docx](https://file1.bdocx.com/fileroot1/2022-12/31/897fec08-4838-4397-a229-c05720ce1fc5/897fec08-4838-4397-a229-c05720ce1fc51.gif)
数据结构的概念
第一章概论
1·1数据结构的概念
一、基本概念和术语
● 数据(Data):
是指所有能输入到计算机中并被计算机程序处理的符号的总称。
是计算机加工的“原料”。
● 数据元素(DataElement):
是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
有些情况下,数据元素也称为元素、结点、顶点、记录。
有时数据元素可以由若干数据项(也称为字段、域、属性)组成,数据项是数据的有独立含义的最小标识单位。
● 数据类型:
(DataType):
是一个值的集合和定义在这个值集上的所有的操作。
例如,整数类型。
数据类型可分为:
原子数据类型和结构数据类型。
原子类型的值是不可分解的,结构类型的值是由若干成分按某种结构组成的。
二、 数据的三个层次
数据是计算机加工的对象,是数据元素的集合;
数据元素是数据的基本单位,常称为结点、元素、记录等,作为一个整体出现;
数据项是数据的最小不可分割的单位。
三、什么是数据结构(DataStructure):
数据之间的相互关系,即数据的组织形式。
包括以下三方面:
① 数据元素之间的逻辑关系,也称为数据的逻辑结构;
② 数据元素及其关系在计算机存储器内的表示,称为数据的存储结构;
③ 数据的运算,即对数据施加的操作。
四、数据的逻辑结构分两类:
线性结构和非线性结构。
(有的教材称四种结构:
集合、线性结构、树形结构、图形结构或网状结构)
五、数据的存储结构:
数据结构在计算机中的表示(又称映象)称为数据的物理结构,又称存储结构:
数据元素及其关系在计算机存储器的表示。
用于表示数据元素的位串称之为元素或结点,用于表示数据项的位串称之为数据域。
算法的设计取决于选定的数据逻辑结构,而算法的实现依赖于采用的存储结构。
数据有四种存储结构:
①顺序存储结构:
把逻辑上相邻的结点存储在物理位置上相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。
通常顺序存储结构是借助于语言的数组来描述的。
②链式存储结构:
不要求逻辑上相邻的结点物理上也相邻,结点间的逻辑关系是由附加的指针字段表示的,通常要借助于语言的指针类型来描述。
③索引存储方法
④散列存储方法
六、《数据结构》课程及其重要性
1968年美国克努特教授开创了数据结构的最初体系。
数据结构是一门综合性的专业课程,是一门介于数学、计算机硬件、计算机软件之间的一门核心课程。
是设计和实现编译系统、操作系统、数据库系统机其他系统程序和大型应用程序的基础。
1·2为什么要学习数据结构
一、 用计算机解题的过程
具体问题—数学模型—算法—程序。
然而在现实社会中存在着许多非数值计算问题,其数学模型难以用数学方程描述。
二、 几个例子说明数据结构研究的内容
1学生成绩表的管理
图书馆的书目检索自动化问题----计算机处理的对象之间存在着线性关系,称为线性的数据结构。
电话号码查询问题----按顺序查找和按索引表查找效率不同。
2 人机对奕问题(gameplaying)
计算机处理的对象是一个个格局。
所有可能出现的格局是一棵倒置的树。
计算机能与人对奕,是因为人们将博奕规则存入计算机,利用高速运算,早期chess4.6程序能每步查找400000——500000个结点,而心理学家认为,人类选手只能预见50步。
3 田径赛的时间安排问题----数学模型是图的数学结构。
综合以上例子,描述这类非数值计算问题的数学模型不再是数学方程,而是诸如表、树和图之类的数据结构。
因此,简单说,数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等等的学科。
1·3算法描述
一、算法(Algorithm)
算法是对特定问题求解步骤的一种描述,它是指令的有限序列。
每条指令表示一个或多个操作。
算法具有五个重要特性:
有穷性、确定性、可行性、输入、输出。
二、算法设计的要求
正确性、可读性、健壮性和效率与低存储量要求
三、算法的描述形式:
自然语言表示法;伪代码表示法;流程图表示法;结构化流程图(N—S图)表示法;PAD图表示法。
四、本书算法用C语言描述。
1 函数的形式
[函数类型]函数名([函数参数表])
[形式参数说明]
{内部语句说明;
执行语句;
}
2 条件语句的两种形式
3 循环语句的三种形式
4 情况语句
5 赋值语句
6 输入、输出语句
7 注释形式
1·4算法分析
一、算法的评价
时间度量;空间度量;易理解,易编制,易调试。
二、举例说明算法复杂度的计算
算法时间是由控制结构和原操作的决定的。
做法:
选取一种是基本操作的原操作,以该基本操作重复的次数作为算法的时间量度。
时间复杂度:
算法中基本操作重复执行的次数是问题规模n的某个函数f(n),T(n)=O(f(n))。
它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同。
语句的频度:
是该语句重复执行的次数。
例1:
交换i和j的内容。
temp=i;i=j;j=temp;
以上三条语句的频度均为1,该程序的执行时间是与问题规模n无关的常数,因此算法的时间复杂度为常数阶,记作T(n)=O
(1)。
例2:
变量计数。
(1)x=0;y=0;
(2)for(k=1;k<=n;k++)
(3)x++;
(4)for(i=1;i<=n;i++)
(5)for(j=1;j<=n;j++)
(6)y++;
以上语句中频度最大的语句是(6),其频度为f(n)=n2,所以该程序段的时间复杂度为T(n)=O(n2)
例3:
求两个n阶方阵的乘积C=A×B,其算法如下:
#definen100
voidMatrixMultiply(intA[n][n],intB[n][n],intC[n][n])
{inti,j,k
for(i=1;i<=n;++i)/*次数n+1*/
for(j=1;j<=n;++j)/*次数n*(n+1)*/
{C[i][j]=0;/*次数n2*/
for(k=1;k<=n,k++)/*次数n2(n+1)*/
C[i][j]=C[i][j]+A[i][k]*B[k][j];/*次数n3*/
}
}
T(n)=2n3+3n2+2n+1
lim(T(n)/n3)=2
T(n)=O(n3)
例4:
(1){++x;s=0;}
(2)for(i=1;i<=n;++i){++x;s+=x;}
(3)for(j=1;j<=n;++j)
(4)for(k=1;k<=n;k++){++x;s+=x;}
(5)i=1;while(i<=n)i=i*2;执行次数与n的关系是n=2
3 算法的渐进时间复杂度与算法的时间复杂度。
4 算法的最佳、最差、平均时间复杂度
5 算法的存储空间需求
6计算机内存的增大与速度的提高并不能降低选择合适算法与数据结构的重要性。
例如,百元钱买百支笔问题(钢笔3元一支,圆珠笔2元一支,铅笔0.5元一支),用三层循环,内循环100万次,在某机器上运行用了50分钟。
for(i=1,i<=100;i++)
for(j=1,j<=100;j++)
for(k=1,k<=100;k++)
if((i+j+k==100)&&(3*i+2*j+0.5*k==100))
printf(“%d,%d,%d”,i,j,k)
而经过分析,钢笔最多买20支,圆珠笔最多买30支,优化后只运行了2秒,相差1500倍。
for(i=0,i<=20;i++)
for(j=0,j<=34-i;j++)
if(3*i+2*j+0.5*(100-i-j)==100)
printf(“%d,%d,%d”,i,j,k)
第二章 线性表
2·1线性表的概念及运算
2·1·1线性表的逻辑结构
1.线性表的定义。
2.线性表的特点:
在数据元素的非空有限集中
存在唯一的一个被称做“第一个”的数据元素;
存在唯一的一个被称做“最后一个”的数据元素;
除第一个之外,每个元素都只有一个前驱;
除最后一个之外,每个元素都只有一个后继。
线性表中结点之间的逻辑关系是结点之间的邻接关系。
2·1·2线性表的运算
1 线性表的运算
⑴置空表SETNULL(L):
将线性表L置成空表。
⑵求表长LENGTH(L):
求线性表L中结点的个数。
⑶取表中的结点GET(L,i)。
⑷定位LOCATE(L,x):
当线性表L中存在一个值为x的结点时,结果是该结点的位置;若表中存在多个值为x的结点,则是首次找到的结点位置;若没有值为x的结点,结果为0。
⑸插入结点INSERT(L,x,i)。
⑹删除结点DELETE(L,i)。
2线性表运算举例
例.利用线性表的基本运算实现清除表L中多余的重复结点。
voidPURGE(List*L)
{inti=1,j,x,y;
while(i{x=GET(L,i);j=j+1;
while(j<=LENGTH(L))
{y=GET(L,j);
if(x==y)DELETE(L,j);
elsej++;
}
i++;
}
}
2·2线性表的顺序存储
2·2·1顺序表
1、线性表的顺序表示:
指的是用一组地址连续的存储单元依次存储线性表的数据元素。
用物理位置来表示逻辑结构。
LOC(ai+1)=LOC(ai)+c
LOC(ai)=LOC(a1)+(i-1)*c
2、顺序表的特点:
随机存取的存储结构。
只要确定了存储线性表的起始位置,线性表中的任一数据元素可随机存取。
3、线性表的C语言描述方式(用一维数组)
#definemaxsize1024
typedefintdatatype;
typedefstruct
{datatypedata[maxsize];
intlast;/*last表示终端结点在向量中的位置*/
}sequenlist;
2·2·2顺序表上的基本运算
顺序表容易实现访问操作,可随机存取元素。
但插入和删除操作主要是移动元素。
1插入操作
算法思想:
在第i个位置上插入一个新元素,将第n至(i+1)个元素逐一向后移动一个位置。
即(a1,…,ai-1,ai,…,an)变成(a1,…,ai-1,x,ai,…,an)。
算法:
intINSERT(sequenlist*L,datatypex,inti)
{intj;
if(((*L).last)>=maxsize-1)
{printf(“overflow”);return(NULL);}
elseif((i<1)||(i>((*L).last)+1)
{printf(“error”);return(NULL);}
else{for(j=(*L).last;j>=i-1;j--)
(*L).data[j+1]=(*L).data[j];
(*L).data[i-1]=x;
(*L).last++;
}
return
(1);
}
时间复杂度的分析:
最好情况下无移动(插入在表的最后),最坏情况下移动n次(插入在表的最前面),平均移动n/2次,时间复杂度为O(n)。
2删除操作:
算法思想:
删除第i个元素,将第(i+1)至第n个元素逐一向前移动一个位置。
即(a1,…,ai-1,ai,ai+1,…,an)变成(a1,…,ai-1,ai+1,…,an)
算法:
intDELETE(sequenlist*L,inti)
{intj;
if((i<1)||(i>(*L).last+1)){print(“error”);return(NULL);}
else{for(j=i;j<=(*L).last;j++)
(*L).data[j-1]=(*L).data[j];
(*L).last--;}
return
(1);
}
2·3线性表的链式存储结构
顺序存储结构的特点:
线性表的链式存储结构的特点:
是用一组任意的存储单元存储线性表的数据元素。
数据元素的映象用一个结点来表示。
结点的一个域表示元素本身,另一个是能指示其后继的指针,用来表示线性表数据元素的逻辑关系。
结点(Node)、数据域、指针域、指针、链、头指针
链式存储结构的特点:
插入、删除操作是不再需要移动大量的元素,但失去了顺序表的可随机存取特点。
链表可分为单链表、循环链表和双向链表。
2·3·1单链表
链表中的每一个结点中只包含一个指针域的称为单链表或线性链表。
data
next
单链表可由头指针唯一确定,在C语言中,用结构指针来描述。
typedefintdatatype;
typedefstrucnode
{datatypedata;
structnode*next}linklist;
linklist*head,*p;
两个C语言的标准函数:
malloc(),free()
p=malloc(sizeof(linklist));free(p);
2·3·2单链表上的基本运算
1建立单链表
(1)头插法建表
linklist*CREATELIST()
{charch;linklist*head,*s;
head=NULL;
ch=getchar();
while(ch!
=’$’)
{s=malloc(sizeof(linklist));
s->data=ch;s->next=head;
head=s;ch=getchar();
}
returnhead;
}
(2)尾插法建表
linklist*CREATLISTR()
{charch;linklist*head,*s,*r;
head=NULL;r=NULL;ch=getchar();
while(ch!
=’$’)
{s=malloc(sizeof(linklist));
s->data=ch;
if(head==NULL)head=s;
elser->next=s;
r=s;ch=getchar();
}
if(r!
=NULL)r->next=NULL;
returnhead;
}
2查找运算
(1)按序号查找
算法思想:
单链表是非随机存取结构。
每个元素的位置信息都包含在前驱结点的信息中,所以取得第i个元素必须从头指针出发寻找。
设置一个指针变量指向第一个结点,然后,让该指针变量逐一向后指向,直到第i个元素。
linklistGET(linklist*head,inti)
{intj;linklist*p;
p=head;j=0;
while((p->next!
=NULL)&&(j
{p=p→next;++j;}
if(i==j)returnp;
elsereturnNULL;
}
(2)按值查找
linklist*LOCATE(linklist*head,datatypekey)
{linklist*p;
p=head->next;
while(p)
if(p->data!
=key)p=p->next;
elsebreak;
returnp;
}
3插入运算
后插操作
INSERTAFTER(linklist*p,datatypex)
{linklist*s;
s=malloc(sizeof(linklist));
s->data=x;
s->next=p->next;p->next=s;
}
4删除操作:
p→next=p→next→next
DELETEAFTER(linklist*p)
{linklist*r;
r=p->next;p→next=r→next;
free(r);
}
2·3·3循环链表……首尾相接的链表
循环链表是另一种形式的链式存储结构,其特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
在实际应用中,循环链表往往只设尾指针,设rear该循环链表的尾指针,若有头结点,则rear->next->next为第一元素;若无头结点,则rear->next为第一元素。
例2.4在链表上实现两个线性表(a1,a2,…,an)和(b1,b2,…,bm)链接成一个线性表(a1,a2,…,an,b1,b2,…,bm)的运算。
解题思路:
若在设头指针的单循环链表上做此操作,则需遍历第一个链表找到an,时间复杂度为O(n),但若在设尾指针的单循环链表上做此操作,则只需修改指针,时间复杂度为O
(1)。
linklist*CONNECT(linklist*ra,*rb)
{linklist*p;
p=ra->next;ra->next=rb->next->next;
free(rb->next);rb->next=p;
returnrb;}
2·3·4双(向)链表
1、双(向)链表特点:
在双向链表的结点中有两个指针域,分别指向前驱和后继。
双向链表也可以有循环链表。
2、双(向)链表的存储结构:
typedefstructdnode
{dataypedata;
structdnode*prior,*next;
}dlinklist;
dlinklist*head;
3、双向链表的操作:
双指针使得链表的双向查找更为方便、快捷。
查找前驱和后继的执行时间为O
(1)。
仅需涉及一个方向的指针的操作和线性链表的操作相同。
插入和删除需同时修改两个方向的指针。
双链表的前插操作算法:
DINSERBEFORE(p,x)
dlinklist*p;datatypex;
{dlinklist*s;
s=malloc(sizeof(dlinklist));
s->data=x;s->prior=p->prior;s->next=p;
p->prior->next=s;p->prior=s;
}
双链表上删除*p自身的操作的算法:
DELETENODE(p)
dlinklist*p;
{p->prior->next=p->next;p->next->prior=p->prior;
free(p);
}
2·4顺序表和链表的比较
1. 基于空间的考虑
2. 基于时间的考虑
3. 基于语言的考虑
第三章 栈和队列
3·1栈
3.1.1堆栈
1 堆栈的定义堆栈是只准在一端进行插入和删除的线性表,称为LIFO表。
允许插入和删除的一端叫栈顶,另一端叫栈底。
2 栈的两种工作方式
(1)栈顶固定,栈底可动:
压入元素时依次下移,退栈时依次上移。
(2)栈底固定,栈顶可动(常见方式)
3 堆栈的基本运算
(1)置空栈SETNULL(S)将栈S置成空栈;
(2)判栈空EMPTY(S)若栈空,则结果为true,否则为false;
(3)进栈PUSH(S,x)往栈S中插入一个元素x;
(4)退栈POP(S)栈顶指针下移;
(5)取栈顶元素TOP(S),取栈顶元素;
3·1·2顺序栈
1、类型定义
#definemaxsize64
typedefintdatatype;
typedefstruct
{datatypedata[maxsize];
inttop;
}seqstack,*s;
2、在顺序栈上实现栈的操作
(1)置栈空
SETNULL(seqstack*s)
{s->top=-1;
}
(2)判栈空
intEMPTY(seqstacks)
{if(s->top>=0)returnFALSE;
elsereturnTRUE;
}
(3)进栈
seqstack*PUSH(seqstack*s,datatypex)
{if(s->top=maxsize-1){printf(“overflow”);returnNULL;}
else{s->data[++s->top]=x;returns;}
}
(4)退栈
datatypePOP(seqstack*s)
{if(EMPTY(s)){printf(“overflow”);returnNULL;}
elsereturn(s->data[s->top--);
}
(4)取栈顶元素TOP(S);
datatypeTOP(seqstack*s)
{if(EMPTY(s)){printf(“overflow”);returnNULL;}
elsereturn(s->data[s->top);
}
3、两个栈共享一个向量空间
#definemaxsize100
typedefstruct
{datatypedata[maxsize];
inttop[2];
}dstack,*s;
一个栈从左(0)向右发展,另一个栈从右向左发展,栈空时两栈指针分别为-1和maxsize,栈满的判断是两栈顶指针差的绝对值为1。
3·1·3链栈
1、 栈的单链形式
上节讲的“前插链表”就是链栈。
由于栈的操作在栈顶,所以,链栈一般不设头结点。
typedefintdatatype;
typedefstructnode
{datatypedata;
structnode*next;
}node,linkstack,*top;
2、栈的基本操作在链栈下的实现
(1) 压栈(入栈)核心语句有:
p=(linkstack*)malloc(sizeof(node));
p->data=x;
p->next=t;top=p;/*无头结点栈*/
(2) 退栈pop(s)
if(st==null)
printf(“\nunderflow”);
else
{p=s->next;
s->next=p->next;
free(p);
}
(3) 取栈顶元素top(s)
if(st==null)
{printf(“\nunderflow”);returnNULL;}
elsereturn(x=s->data)
(4) 判栈空empty(s)
if(s==null)returnTRUE;;
elsereturn(true)