数据结构.docx
- 文档编号:7677030
- 上传时间:2023-01-25
- 格式:DOCX
- 页数:24
- 大小:34.49KB
数据结构.docx
《数据结构.docx》由会员分享,可在线阅读,更多相关《数据结构.docx(24页珍藏版)》请在冰豆网上搜索。
数据结构
第二章线性表
2.1.1线性表的定义
特性:
设A=(a1,a2,...,ai-1,ai,ai+1,…,an)是一线性表
Ø线性表的数据元素可以是各种各样的,但同一线性表中的元素必须是同一类型的;
Ø在表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱,ai+1是ai的直接后继;
Ø在线性表中,除第一个元素和最后一个元素之外,其他元素都有且仅有一个直接前驱,有且仅有一个直接后继,具有这种结构特征的数据结构称为线性结构。
线性表是一种线性数据结构;
Ø线性表中元素的个数n称为线性表的长度,n=0时称为空表;
Øai是线性表的第i个元素,称i为数据元素ai的序号,每一个元素在线性表中的位置,仅取决于它的序号;
【例2-3】已知一个非纯集合B(即集合B中可能有相同元素),试构造一个纯集合A,使A中只包含B中所有值各不相同的成员。
【分析】假设仍以线性表表示集合,则此问题和例2-2类似,即构造线性表La,使其只包含线性表Lb中所有值不相同的数据元素。
所不同之处是,操作实施之前,线性表La不存在,则操作的第一步首先应该构造一个空的线性表,之后的操作步骤和例2-2相同。
【具体步骤】
(1)构造一个空的线性表La;
(2)从线性表Lb中取得一个数据元素;
(3)依该数据元素的值在线性表La中进行查访;
(4)若线性表La中不存在和其值相同的数据元素,则从Lb中删除的这个数据元素插入到线性表La中。
(5)重复
(2)至(4)的操作直至Lb为空表为止。
【具体算法】___运行后Lb仍保持原先的值
voidpurge(List&La,List&Lb)
{
InitList(La);La_len=0;//创建一个空的线性表La
Lb_len=ListLength(Lb);//求线性表Lb的长度
for(i=1;i<=Lb_len;i++)//Lb表中的元素尚未处理完
{
GetElem(Lb,i,&e);//从Lb中取出第i个元素,并将其值赋给e
if(!
LocateElem(La,e)) //La中不存在值为e的数据元素
ListInsert(La,++La_len,e);//将该元素e插入到La中最后一个元素后
}
}
2.2线性表的顺序表示和实现
线性表的顺序存储结构,就是用一组连续的内存单元依次存放线性表的数据元素。
说明:
1.在顺序存储结构下,线性表元素之间的逻辑关系,通过元素的存储顺序反映(表示)出来;
·2.假设线性表中每个数据元素占用t个存储单元,那么,在顺序存储结构中,线性表的第i个元素的存储位置与第1个元素的存储位置的关系是:
Loc(ai)=Loc(a1)+(i–1)t
SqList:
类型名,
SqList类型的变量是结构变量,它的四个域分别是:
*elem:
存放线性表元素的一维数组基址;其存储空间在初始化操作(建空表)时动态分配;
length:
存放线性表的表长;
listsize:
用于存放当前分配(存放线性表元素)的存储空间的大小。
incrementsize:
约定增补空间量(当线性表空间不够时)
1.初始化操作
功能:
构造一个空的顺序表。
方法:
首先要按需为其动态分配一个存储区域,然后设其当前长度为0。
2.查找元素操作
功能:
在顺序表L中查找其值与给定值e相等的数据元素的位序,如果未找到,则返回0。
方法:
从第一个元素起,依次和e相比较,直到找到一个其值与e相等的数据元素,则返回它在线性表中的“位序”;或者查遍整个顺序表都没有找到其值和e相等的元素后返回0。
3.插入元素操作
功能:
在顺序表L中的第i(1≦i≦L.length+1)个数据元素之前插入一个新元素x。
插入前线性表为:
(a1,a2,a3,…,ai-1,ai,,…an)
插入后,线性表长度为L.length+1,线性表为:
(a1,a2,a3,…,ai-1,x,ai,,…an)
一般情况下,在顺序表L中第i个元素之前插入一个新的元素时,首先需将L.elem[L.length-1]至L.elem[i-1]依次往后移动一个位置。
显然,此时顺序表的长度应该小于数组的最大容量;否则,在移动元素之前,必须先为顺序表“扩大数组容量”。
一般情况下,当插入位置i=L.length+1时,for循环的执行次数为0,即不需要移动元素;反之,若i=1,则需将顺序表中全部(n个)元素依次向后移动。
然而,当顺序表中数据元素已占满空间时,不论插入位置在何处,为了扩大当前的数组容量,都必须移动全部数据元素,因此,从最坏的情况考虑,顺序表插入算法的时间复杂度为O(n),其中n为线性表的长度。
4.删除元素操作
功能:
在顺序表L中删除第i(1≦i≦L.length)个数据元素。
删除前线性表为:
(a1,a2,a3,…,ai-1,ai,ai+1,…an)
删除后,线性表长度为L.length-1,线性表为:
(a1,a2,a3,…,ai-1,ai+1,,…an)
一般情况下,从顺序表L中删除第i个元素时,需将L.elem[i]至L.elem[L.length-1]的元素依次往前移动一个位置。
删除算法的主要步骤:
1)若i不合法或表L空,算法结束,并返回0;否则转2)
2)将第i个元素之后的元素(不包括第i个元素)依次向前移动一个位置;
3)表长-1
和插入的情况相类似,当删除的位置i=L.length时,算法中for循环的执行次数为0,即不需要移动元素;反之,若i=1,则需将顺序表中从第2个元素起至最后一个元素(共n个元素)依次向前移动一个位置。
因此,顺序表删除元素算法的时间复杂度也为O(n),其中n为线性表的长度。
5.销毁结构操作
功能:
和结构创建相对应,当程序中的数据结构不再需要时,应该及时进行“销毁”,并释放它所占的全部空间,以便使存储空间得到充分的利用
6.插入和删除操作的时间分析
(1)插入算法时间复杂度分析
算法时间复杂度取决于移动元素的个数,移动元素的个数不仅与表长有关,而且与插入位置有关。
2.3线性表的链式表示和实现
线性表的链式存储结构是用一组任意的存储单元存储线性表的各个数据元素。
为了表示线性表中元素的先后关系,每个元素除了需要存储自身的信息外还需保存直接前趋元素或直接后继元素的存储位置。
2.3.1单链表和指针
用一组任意的存储单元存储线性表中的数据元素,对每个数据元素除了保存自身信息外,还保存了直接后继元素的存储位置。
线性链表有关术语
结点:
数据元素及直接后继的存储位置(地址)组成一个数据元素的存储结构,称为一个结点;
结点的数据域:
结点中用于保存数据元素的部分;
结点的指针域:
结点中用于保存数据元素直接后继存储地址的部分;
头指针:
用于存放线性链表中第一个结点的存储地址;
空指针:
不指向任何结点,线性链表最后一个结点的指针通常是指空指针,用NULL表示;
头结点是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息;
首元结点是指链表中存储线性表第一个数据元素a1的结点。
单链表的结点类型定义及指向结点的指针类型定义
LNode:
结构类型名;
LNode类型结构变量有两个域:
data:
用于存放线性表的数据元素,
next:
用于存放元素直接后继结点的地址;
·该类型结构变量用于表示线性链表中的一个结点;
LNode*p:
p为指向结点(结构)的指针变量;
LinkListH:
头指针为H的单链表;
◆单链表中结点的创建
方法一:
LNode*p;
p=newLNode;
方法二:
p=(LNode*)malloc(sizeof(LNode));
deletep或free(p)
功能:
将指针变量p所指示的存储空间,回收到系统内存空间中去
使用方法:
...
LNode*p;
p=(LNode*)malloc(sizeof(LNode));
//一旦p所指示的内存空间不再使用,
//调用free()回收之
free(p);
1.求线性表的长度
【分析】当以单链表表示线性表时,整个链表由一个“头指针”来表示,线性表的长度即为链表中的结点个数,只能通过“遍历”链表来实现。
【具体算法】
intListLength_L(LinkListL)
{
p=L;k=0;
while(p)
{
k++;p=p->next;
}
return(k);
}
算法的时间复杂度:
O(n),其中n为表长。
2.查找元素操作
【分析】在单链表L中查找和给定值e相等的数据元素的过程和顺序表类似,从第一个结点起,依次和e相比较,直到找到一个其值和e相等的元素,则返回它在链表中的“位置”;或者查遍整个链表都不存在这样的一个元素后,返回“NULL”。
【具体算法】
LNode*LocateElem_L(LinkListL,ElemTypee)
{
p=L;
while(p&&p->data!
=e)
p=p->next;
return(p);
}
算法的时间复杂度:
O(n),其中n为表长。
3.向单链表中插入一个元素
链表插入的核心语句:
Step1:
q->next=p->next;
Step2:
p->next=q;
4.删除结点操作
【分析】和插入类似,在单链表中删除一个结点时,也不需要移动数据元素,仅需修改相应的指针链接。
但由于删除结点时,需要修改的是它的“前驱”结点的指针域,因此和“前插”操作一样,首先应当找到它的前驱结点。
(1)q->next=p->next;
(2)free(p);
【具体算法】
StatusListDelete_L(LinkList&L,inti,ElemType&e)
//在带头结点的单链表L中,删除第i个元素,并由e返回真值
{p=L;j=0;
while(p->next&&j {p=p->next;++j;} if(! (p->next)||j>i-1)returnERROR; q=p->next;p->next=q->next; e=q->data; free(q); returnOK; } 算法时间复杂度: O(n),其中n为表长。 【例2-5】逆序创建链表。 【分析】链表是一种动态管理的结构,它和顺序表不同,链表中每个结点占用的存储空间不需预先分配划定,而是在运行时刻由系统根据需求即时生成的。 因此,建立链表的过程是一个动态生成的过程。 即从“空表”起,依次建立结点,并逐个插入链表。 所谓“逆序”创建链表指的是,依和线性表的逻辑顺序相“逆”的次序输入元素,逆序生成链表可以为处理头指针提供方便。 【具体算法】 voidCreateList_L(LinkList&L,intn) { L=NULL;//建立一个空的单链表 for(i=n;i>=0;i--) { s=(LinkList)malloc(sizeof(LNode));//生成新结点 scanf(&s->data);//赋元素值 s->next=L;//插入在第一个结点之前 L=s; } } 2.3.4循环链表 1循环链表的概念 循环链表是线性表的另一种链式存储结构,它的特点是将线性链表的最后一个结点的指针指向链表的第一个结点 说明: ·在解决某些实际问题时循环链表可能要比线性链表方便些。 如将一个链表链在另一个链表的后面; ·循环链表与单链表操作的主要差别是算法中循环结束的条件不是p或p->next是否为NULL,而是它们是否等于首指针; ·对循环链表,有时不给出头指针,而是给出尾指针 两循环链表合并: p=a->next; q=b->next; a->next=q->next; b->next=p; free(q); 2.3.5双向链表 双向链表中,每个结点有两个指针域,一个指向直接后继元素结点,另一个指向直接前趋元素结点。 线性表和有序表有两种存储表示: 顺序表和链表 顺序表: 需要预分配一定长度的存储空间。 太大易造成存储空间的浪费,太小又将造成频繁地进行存储空间的再分配。 顺序表是一种随机存储的结构,对顺序表中任一元素进行存取的时间相同。 顺序表对插入、删除操作需要移动近一半的数据元素。 链表: 存储分配灵活,链表中的结点可在程序执行过程中动态生成。 链表是一种顺序存储的结构,对链表中的每个结点都必须从头指针所指结点起顺链扫描。 链表对插入、删除操作不需要移动数据元素。 第三章栈 3.1栈 定义: 是限定仅在表尾进行插入或删除操作的线性表。 允许插入,删除的一端称为栈顶(top),另一端称为栈底(bottom 逻辑特征: 后进先出(LIFO 基本操作: 创建一个空栈;判断栈是否为空栈;判断栈是否为满;往栈中插入(或称推入)一个元素;从栈中删除(或称弹出)一个元素;求栈顶元素的值。 访问栈中每个元素 3.1.1栈的特点和操作 一般线性表堆栈 逻辑结构: 一对一逻辑结构: 一对一 存储结构: 顺序表、链表存储结构: 顺序栈、链栈 运算规则: 随机存取运算规则: 后进先出(LIFO) 2.存储结构顺序用栈或链栈存储均可,顺序栈更常见 3.运算规则只能在栈顶运算,且访问结点时依照后进先出(LIFO)或先进后出(FILO)的原则。 4.实现方式关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同。 基本操作有入栈、出栈、读栈顶元素值、建栈、判栈满、判栈空等。 栈是仅在表尾进行插入、删除操作的线性表。 表尾(即an端)称为栈顶top;表头(即a1端)称为栈底base 例如: 栈s=(a1,a2,a3,……,an-1,an) a1称为栈底元素an称为栈顶元素 插入元素到栈顶(即表尾)的操作,称为入栈。 从栈顶(即表尾)删除最后一个元素的操作,称为出栈。 强调: 插入和删除都只能在表的一端(栈顶)进行! “进”=压入=PUSH(x)“出”=弹出=POP(y) 问: 为什么要设计堆栈? 它有什么独特用途? 1.调用函数或子程序非它莫属; 2.递归运算的有力工具; 3.用于保护现场和恢复现场; 4.简化了程序设计的问题。 问: 栈是什么? 它与一般线性表有什么不同? 答: 栈是一种特殊的线性表,它只能在表的一端(即栈顶)进行插入和删除运算。 与一般线性表的区别: 仅在于运算规则不同。 3.1.2栈的表示和操作实现 SqStack: 结构类型名; SqStack: 它的三个域分别是: base: 栈底指针,指向栈底位置; top: 栈顶指针; Stacksize: 已分配的存储空间(一元素为单位)大小; 约定栈顶指针指向栈顶元素的下(后面)一个位置 顺序栈基本操作的实现: –栈顶的初始化: S.top=S.base –栈空: S.base==S.top –栈满: S.top-S.base>=S.stacksize –入栈: *S.top++=e,出栈: e=*--S.top 约定: top指向栈顶元素的下一个位置 顺序栈算法的实现 1)初始化操作InitStack_Sq((SqStack&S) 参数: S是存放栈的结构变量; 功能: 建一个空栈S; voidInitStack_Sq(SqStack&S, intmaxsize=STACK_INIT_SIZE, intincresize=SRACKINCREMENTSIZE) {S.base=(SElemType*)malloc(STACK_INIT_SIZE*sizeof(SElemType); if(! S.base)exit(OVERFLOW); S.top=S.base; S.stacksize=maxsize; S.incrementsize=incresize; }//InitStack_Sq 2) 取栈顶元素操作GetTop_Sq(SqStackS,SElemType&e) 功能: 取栈顶元素,并用e返回; StatusGetTop_Sq(SqStackS,SelemType&e) { if(S.top==S.base)returnERROR; e=*(S.top-1); returnTRUE; } 3)入栈操作Push(SqStack&S,SElemTypee) 功能: 将e入栈; 1)S是否已满,若栈满,重新分配存储空间; 2)将元素e写入栈顶; 3)修改栈顶指针,使栈顶指针指向栈顶元素的下(后面)一个位置; 4)出栈操作Pop(SqStack&S,SElemType&e) 功能: 栈顶元素退栈,并用e返回; 2.栈的表示与实现──链栈 1.链式栈无栈满问题,空间可扩充2.插入与删除仅在栈顶处执行3.链式栈的栈顶在链头4。 适合于多栈操作可以不必引入头结点 约定: top指向栈顶元素所在的结点 链栈的定义顺序栈算法 typedefintbool;初始化 #defineTRUE1;voidInitStack_L(LinkStack*S) #defineFALSE0;{S=NULL; typedefintElemType;} ElemTypedata;入栈 structnode*next;voidPush_L(LinkStack*S,ElemTypee) }LNode;{LNode*p=newLNode; p->data=e; TypedefLinkListLinkStackp->next=S; S=p;} 例三表达式求值 限于二元运算符的表达式定义: 表达式: : =(操作数)(运算符)(操作数) 操作数: : =简单变量|表达式 简单变量: : =标识符|无符号整数 表达式的三种标识方法: 设Exp=S1OPS2 则称OPS1S2为前缀表示法 S1OPS2为中缀表示法 S1S2OP为后缀表示法 例如: Exp=ab+(cd/e)f 前缀式: +abc/def 中缀式: ab+cd/ef 后缀式: abcde/f+ 结论: 1)操作数之间的相对次序不变;2)运算符的相对次序不同;3)中缀式丢失了括弧信息,致使运算的次序不确定;4)前缀式的运算规则为: 连续出现的两个操作数和在它们前且紧靠它们的运算符构成一个最小表达式;5)后缀式的运算规则为: 运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式; 例四实现递归 递归的对象: 一个对象部分地包含它自己,或用它自己给自己定义。 (如某些数据结构的定义) 线性表的另一种定义: 若元素个数为0,则称为空表 若元素个数大于0,则有且仅有一个第一个元素(即表头),剩余元素形成一个表(即表尾)。 递归的过程: 一个过程直接或间接地调用自己如: 0的阶乘是1,n(n>0)的阶乘等于n乘上(n-1)的阶乘 当在一个函数的运行期间调用另一个函数时,在运行该被调用函数之前,需先完成三项任务: •将所有的实在参数、返回地址等信息传递给被调用函数保存; •为被调用函数的局部变量分配存储区; •将控制转移到被调用函数的入口。 从被调用函数返回调用函数之前,应该完成三项任务: •保存被调函数的计算结果; •释放被调函数的数据区; •依照被调函数保存的返回地址将控制转移到调用函数。 多个函数嵌套调用的规则是: 后调用先返回! 递归函数执行过程可视为同一函数进行嵌套调用,例如: 递归工作栈: 递归过程执行过程中占用的数据区。 递归工作记录: 每一层的递归参数合成一个记录。 当前活动记录: 栈顶记录指示当前层的执行情况。 当前环境指针: 递归工作栈的栈顶指针。 栈与递归的实现 求解阶乘函数的递归算法 longfact(longn) { if(n==0)return1;//递归结束条件 elsereturnn*fact(n-1);//递归的规则 } 3.3队列(Queue) 定义队列是只允许在一端删除,在另一端插入的顺序表,允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear)。 特性先进先出(FIFO,FirstInFirstOut) 常用操作初始化空队、入队、出队、判断队空、判断队满、取队头 1.定义只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表(头删尾插) 2.逻辑结构与同线性表相同,仍为一对一关系。 3.存储结构顺序队或链队,以循环顺序队更常见。 4.运算规则只能在队首和队尾运算,且访问结点时依照先进先出(FIFO)的原则。 5.实现方式关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同。 基本操作有入队或出队,建空队列,判队空或队满等操作 队列的进队和出队原则 ⏹进队时队尾指针先进一rear=rear+1, 再将新元素按rear指示位置加入。 ⏹出队时队头指针先进一front=front+1, 再将下标为front的元素取出。 ⏹队满时再进队将溢出出错; ⏹队空时再出队将队空处理。 ⏹解决办法之一: 将队列元素存放数组首尾相接,形成循环(环形)队列。 3.3.2队列的表示和操作实现 链队列: 基本操作的实现 无队满问题(除非分配不出内存),空间可扩充引入头结点(一定需要吗? ) 1.队头在链头,队尾在链尾。 链式队列在进队时无队满问题,但有队空问题。 2、队空条件为: front==NULL •链式栈无栈满问题,空间可扩充 •插入与删除仅在栈顶处执行 •链式栈的栈顶在链头 •适合于多栈操作 讨论: ②队列会满吗? 一般不会,因为删除时有free动作。 除非内存不足! ③怎样实现入队和出队操作? 入队(尾部插入): rear->next=S;rear=S; 出队(头部删除): front->next=p->next; 循环队列 队列的顺序存储 约定与类型定义: Q.front和Q.rear的含义 删除: 避免大量的移动->头指针增1 问题: 假上溢⇒首尾相接的环(
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构