数据结构的复习.docx
《数据结构的复习.docx》由会员分享,可在线阅读,更多相关《数据结构的复习.docx(14页珍藏版)》请在冰豆网上搜索。
数据结构的复习
数据结构的复习
数据:
对信息的一种符号表示。
在计算机科学中,指所有能输入到计算机中并能被计算机程序处理的符号的总称。
数据元素:
数据这个集合中的一个个体。
数据项:
数据的不可分割的最小单位。
一个数据元素可以有若干个数据项组成。
数据对象:
性质相同的数据元素的集合。
数据结构定义:
相互之间存在一种或多种关系的数据元素的集合。
⏹数据元素之间的关系——结构
⏹四种基本结构(如下图所示)
1集合
2线性结构
3树形结构
4图状结构/网状结构
数据结构的分类:
⏹逻辑结构:
数据元素之间的逻辑关系;
⏹物理结构:
数据元素在计算机中的存储方法(表现和实现)。
数据结构分类举例
⏹按逻辑结构的不同分为:
集合、线性结构、树状结构、网状结构
⏹按物理结构的不同分为:
⏹顺序结构
⏹链式结构
数据类型的定义:
一个值的集合和定义在这个值集上的一组操作的总称。
⏹ADT:
一个数学模型以及定义在该模型上的一组操作,即:
数据结构+定义在此结构上的一组操作。
⏹ADT的三元组表示(D,S,P)
⏹D:
数据;
⏹S:
D上的关系
⏹P:
D上基本操作
⏹ADT的格式定义:
ADT抽象数据类型名
{
数据对象:
数据对象的定义
数据关系:
数据关系的定义
数据操作:
基本操作的定义
}ADT抽象数据类型名
随着问题规模n的增长,算法执行时间的度量和f(n)的增长率相同,记作:
T(n)=O(f(n))
补:
若limT(n)/f(n)=c,则T(n)和f(n)是同阶无穷小,表示为T(n)=O(f(n));
其中:
n:
算法的计算量或称规模;f(n):
运算时间随n增大时的增长率;O(f(n)算法时间特征的度量。
线性表:
n个数据元素的有限序列。
⏹线性表中数据元素的个数n(n≥0)称为线性表的长度。
⏹当n=0时,称为空表。
⏹非空表的特性:
(1)有且仅有一个表头结点a1,它没有前驱,而仅有一个后继a2;
(2)有且仅有一个表尾结点an,它没有后继,而仅有一个前驱an-1;
(3)其余的结点ai(2≤i≤n1)都有且仅有一个前驱ai-1和一个后继ai+1。
这是定义线性表的结构体:
#defineLIST_INIT_SIZE100//初始分配空间
#defineLISTINCREMENT10//空间分配增量
typedefstruct{
ElemType*elem;//顺序表数组的基址
intlength;//当前长度
intlistsize;//当前分配的存储容量
}SqList
线性表的初始化:
StatusInitlist_sq(sqlist&L)
{
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*
sizeof(ElemType));
if(!
L.elem)exit(OVERFLOW);
L.length=0;
L.listsize=LIST_INIT_SIZE;
ReturnOK;
}
线性表的插入:
StatusListInsert_sq(sqlist&L,inti,ElemTypee)
{
if(i<1||i>L.length+1)returnERROR;
if(L.length>=L.listsize)exit(OVERFLOW);
for(j=L.length-1;j>=i-1;--j)
L.elem[j+1]=L.elem[j];
L.elem[i-1]=e;
++L.length;
returnOK;
}//ListInsert_sq最坏情况,其时间复杂度为O(n);
线性表的删除操作:
Statuslistdelete_sq(Sqlist&L,inti,Elemtype&e)
{if(i<1||i>L.length)returnERROR;
e=L.elem[i-1];
for(j=i;j<=L.length-1;++j)
L.elem[j-1]=L.elem[j];
--L.length;returnOK;}时间复杂度为O(n);
判断线性表L是否为空
statusListEmpty_sq(sqListL)
{
If(L.length==0)
returnTRUE;
else
returnFALSE;
}
返回线性表L的长度
statusListLength_sq(sqListL)
{
returnL.length;
}
取线性表的第i个元素算法
StatusGetElem_sq(SqListL,inti,ElemType&e)
{
if(i<1||i>L.length)returnERROR;
e=L.elem[i-1];
returnOK;
}
两个线性表的合并:
Voidmergelist_sq(sqlistLa,sqlistLb,sqlist&Lc)
{pa=La.elem;pb=Lb.elem;
Lc.listsize=Lc.length=La.length+Lb.length;
pc=Lc.elem=(Elemtype*)malloc(Lc.listsize*sizeof(elemtype));
if(!
Lc.elem)exit(OVERFLOW);
pa_last=La.elem+La.length-1;
pb_last=Lb.elem+Lb.length-1;
While(pa<=pa_last&&pb<=pb_last)
{if(*pa<=*pb)*pc++=*pa++;
else*pc++=*pb++;}
while(pa<=pa_last)*pc++=*pa++;
while(pb<=pb_last)*pc++=*pb++;
}
⏹线性链表:
用一组任意的存储单元(可以不连续)存储线性表的数据元素。
注:
不连续---可零散地分布在内存中的任何位置上。
⏹线性链表的特点:
1链表中数据元素的逻辑次序和物理次序不一定相同。
即:
逻辑上相邻未必在物理上相邻。
2数据元素在存储器中的存储位置是随意的。
数据域——存储数据元素的信息。
指针域——存储直接后继的存储地址
单链表:
每个结点只有一个指针域。
带头结点的单链表:
⏹在线性链表的第一个元素结点之前附设一个结点,称为头结点。
⏹头结点的数据域不存储任何信息,其指针域存储第一个元素结点的存储位置。
⏹头指针L指向该头结点。
开始结点---用头指针指向之。
最后一个结点(尾结点)---指针域为空(无后继),用^或NULL表示。
表中其它结点---由其前趋的指针域指向之。
⏹线性链表(单链表)的定义描述:
typedefstructLNode{
ElemTypedata;
structLNode*next;
}LNode,*Linklist;
线性链表的初始化——建一个空链表
statusInitlist_L(Linklist&L){
//建立头结点,其next为空
L=(Lnode*)malloc(sizeof(Lnode));
L->next=null;
returnOK;
}
单链表的查找操作:
StatusGetelem_L(LinklistL,inti,ElemType&e)
{p=L->next;j=1;
while(p&&j
p=p->next;++j;
}
if(!
p‖j>i)returnerror;//找不到,返回ERROR
e=p->data;
returnOK;//找到第i个结点,返回OK
}
单链表的插入操作:
Statuslistinsert_L(Linklist&L,inti,elemtypee)
{p=L;j=0;
while(p&&jnext;++j;}
if(!
p‖j>i-1)returnerror;
s=(Lnode*)malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
returnOK;}
单链表的删除操作:
Statuslistdelete_L(Linklist&L,inti,ElemType&e)
{p=L;j=0;
while(p->next&&jnext;++j;}
if(!
(p->next)‖j>i-1)returnERROR;
q=p->next;
p->next=q->next;
e=q->data;
free(q);
returnOK;
}
(1)在初值设置上,删除算法从头结点开始,删除范围为[1,表长],指针范围为[0,n-1];
注:
查找算法查找范围[1,表长],指针范围为[1,n];
插入算法插入范围[1,表长+1],指针范围为[0,n];
(2)在循环定位上,删除算法定位在i-1;
(3)在核心条件上,切记“删除算法”的书写。
建立一个带有n个元素的单链表
算法实现:
statusInitlist_L(Linklist&L,intn){
L=(Lnode*)malloc(sizeof(Lnode));
L->next=null;
for(i=n;i>0;--i){
p=(Lnode*)malloc(sizeof(Lnode));
scanf(&p->data);
p->next=L->next;
L->next=p;
returnOK;}
}
算法的时间复杂度:
for语句循环n次,为T(n)=O(n)
单链表的合并算法实现:
voidMergeList_L(LinkList&La,LinkList&Lb,LinkList&Lc){
pa=La->next;pb=Lb->next;
Lc=pc=La;//用La的头结点作为Lc的头结点
while(pa&&pb){
if(pa->data<=pb->data){
pc->next=pa;pc=pa;pa=pa->next;}
else{
pc->next=pb;pc=pb;pb=pb->next;}
}
if(pa)pc->next=pa;
elsepc->next=pb;
free(Lb);
}
循环链表:
线性表的另一种形式的链式存储结构。
令表中最后一个结点的指针域指向头结点,整个链表形成一个环。
双向链表:
每一个结点有两个指针域——一个指向直接后继,一个指向直接前驱
栈:
栈——限制仅在表尾进行插入或删除操作的线性表。
栈顶(top,表尾)——允许插入和删除的一端。
栈底(bottom,表头)——不允许插入和删除的一端。
空栈——表中没有数据元素。
⏹栈的特点:
后进先出(LIFO)或先进后出(FILO)
顺序栈的结构体定义:
定义:
采用C语言中分配的一维数组表示顺序表。
#definestack_init_size100//初始分配量
#definestackincrement10//分配增量
Typedefstruct{
selemtype*base;//存储空间基址
selemtype*top;//栈顶指针
intstacksize;//当前已分配的存储空间(元
素为单位)
}sqstack;
初始化一个栈——创建栈
StatusInitstack(sqstack&S){
s.base=(SElemtype*)
malloc(STACK_INIT_SIZE*sizeof(SElemtype));
if(!
s.base)exit(OVERFLOW);
s.top=s.base;
s.stacksize=STACK_INIT_SIZE;
ReturnOK;
}//initstack
进栈(插入新元素)
算法思想:
若栈满,返回OVERFLOW;否则将新元素e入栈,并返回OK。
Statuspush(sqstack&s,selemtypee){
if(s.top-s.base>=s.stacksize)
exit(OVERFLOW);
*S.top++=e;
returnOK;
}//push
出栈(删除栈顶元素)
算法思想:
若栈空,返回ERROR;否则将删除S的栈顶元素,并用e返回其值。
statuspop(sqstack&s,selemtype&e){
if(s.top==s.base)returnERROR;
e=*--s.top;
returnOK;
}//pop
取栈顶元素
算法思想:
若栈空,返回ERROR;否则将用e返回栈顶元素。
statusgettop(sqstacks,selemtype&e){
if(s.top==s.base)returnERROR;
e=*(s.top-1);
returnOK;
}//getpop
队列:
一个只能在队首进行删除、队尾进行插入的线性表。
队尾(rear):
允许插入的一端。
队头(front):
允许删除的一端。
特征:
先进先出(FIFO)。
树:
•结点(node)一个数据元素及若干指向其子树的分支;
•结点的度(degree)结点的子树个数;
•树的度(degree)树内各结点度的最大值;
•分支(branch)结点度不为0的结点;
•叶(leaf)结点度为0的结点;
•孩子(child)结点某结点的子树;
双亲(parent)结点相应的,该结点称为孩子的双亲
•兄弟(sibling)结点具有同一双亲的所有结点;
•祖先(ancestor)结点从根到该结点所经分支上的所有结点;
•子孙(descendant)结点以某结点为根的子树中的任一结点。
•结点的层次(level)根结点的层数为1,其余结点的层数为双亲结点的层数加1;
•树的深度(depth)树中结点的最大层数;
•有序树子树的次序不能互换;
•无序树子树的次序可以互换;
•森林m(m≥0)棵互不相交的树的集合,对于树中每个结点而言,其子树的集合即为森林。
二叉树是结点数为0或每个结点最多只有左右两棵子树的树。
二叉树是一种特殊的树,它的特点有:
(1)每个结点最多只有两棵子树,即不存在结点的度大于2的结点;
(2)子树有左右之分,不能颠倒。
二叉树的性质
性质1二叉树的第i层最多有2i-1个结点。
(i1)
性质2深度为k的二叉树最多有2k-1个结点。
(k1)
性质3对任何一棵二叉树,如果其叶结点个数为n0,度为2的非叶结点个数为n2,则有n0=n2+1。
满二叉树(FullBinaryTree)
一棵深度为k,且有2k-1个结点的二叉树。
满二叉树的特点:
每一层都取最大结点数;结点层序编号方法:
从根结点起从上到下逐层,层内从左到右,对二叉树的结点进行连续编号。
⏹完全二叉树(CompleteBinaryTree)
深度为k,有n个结点的二叉树是一棵完全二叉树,当且仅当其每个结点都与深度为k的满二叉树中层次编号1—n相对应。
完全二叉树的特点
(1)除最后一层外,每一层都取最大结点数,最后一层结点都有集中在该层最左边的若干位置。
(2)叶子结点只可能在层次最大的两层出现。
(3)对任一结点,若其右分支下的子孙的最大层次为L,则其左分支下的子孙的最大层次为L或L+1。
性质4具有n个结点的完全二叉树的深度
为+1。
性质5如果将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号1,2,…,
n-1,n,则对任一结点i(1≤i≤n)有:
(1)若i=1,则结点i是二叉树的根,无双亲;
若i>1,则i的双亲为i/2
(2)如果2i>n,则结点无左孩子,否则其左孩子
lchild(i)是结点2i
(3)如果2i+1>n,则结点无右孩子,否则其右孩
子rchild(i)是结点2i+1
二叉树的存储结构
一、顺序存储结构
二、链式存储结构
设计不同的结点结构,可以构成不同的链式存储结构,常用的有:
二叉链表、三叉链表和线索链表
遍历二叉树(traversingbinarytree)
按某条搜索路径访问树中每一个结点,使得每个结点均被访问一次,且仅被访问一次。
先序(根)遍历:
DLR
中序(根)遍历:
LDR
后序(根)遍历:
LRD
二叉树的遍历算法主要有两种:
递归算法;非递归算法。
先序遍历算法:
StatusPreOrderTraverse(BitreeT){
if(T){
visit(T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
returnok;}
else
returnerror;
}
中序遍历算法:
voidInOrderTraverse(BiTreeT){
if(T){
InOrderTraverse(T->lchild);
Visit(T->data);
InOrderTraverse(T->rchild);
returnok;}
else
returnerror;
}
后序遍历算法:
StatusPostOrderTraverse(BitreeT){
if(T){
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
visit(T->data);
returnok;}
else
returnerror;
}
先序遍历——非递归算法:
StatusPreOrderTraverse(BitreeT,status(*visit)(TelemTypee)){
Initstack(s);p=T;
while(p||!
StackEmpty(s)){
if(p){visit(p->data);
push(s,p);p=p->lchild;}
else{
pop(s,p);p=p->rchild;}
}
returnok;
}
中序遍历——非递归算法:
StatusInOrderTraverse(BitreeT,status(*visit)(TelemTypee)){
Initstack(s);p=T;
while(p||!
StackEmpty(s)){
if(p){push(s,p);p=p->lchild;}
else{
pop(s,p);visit(p->data);
p=p->rchild;}
}
returnok;
}
线索二叉树
☐可利用二叉链表结点结构中的空指针域(共(n+1)个),在空指针域中存放结点在某种遍历次序下的前趋和后继结点信息,这种附加的指针称为“线索”。
☐为避免混淆,需改变结点结构,即增加两个标志域。
☐若结点有左子树,则标志位ltag=0,表示左链域指针lchild指向其左孩子结点;否则,令其左链域指示其前驱,ltag=1。
☐若结点有右子树,则标志位rtag=0,表示右链域指针rchild指向其右孩子结点;否则,令其右链域指示其后继,rtag=1。
☐指向结点前驱或后继的指针叫做线索。
☐加上线索的二叉树叫线索二叉树。
☐对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
☐按先序遍历得到的线索二叉树称为先序线索二叉树;
☐按中序遍历得到的线索二叉树称为中序线索二叉树;
☐按后序遍历得到的线索二叉树称为后序线索二叉树。
树的存储表示
1.双亲表示法
用一组地址连续的存储单元来存放树的结点,每一个结点有两个域:
Data域——存放结点的信息;
Parent域——存放该结点的双亲结点的位置;
.孩子表示法:
◆每个结点的孩子用单链表存储,称为孩子链表;
◆N个结点可以有n个孩子链表;
◆N个孩子链表的头指针可以组成一个顺序表。
4.孩子-兄弟表示法:
用二叉链表作为树的存储结构,转换规则:
每个结点的左链域指向该结点的第一个孩子,
每个结点的右链域指向下一个兄弟结点。
•简单来说,哈希表就是基于哈希函数建立的一张查找表。
•哈希函数是一个映射,其设定可以很灵活,只要使得任何关键字的哈希函数值都落在表长允许范围内即可。
对不同关键字可能得到同一哈希地址,这一现象称为“冲突”,即key1≠key2,而f(key1)=f(key2)。
并且,改进哈希函数只能减少冲突,而不能避免冲突。
•因此,在设计哈希函数时:
一方面,要考虑选择一个“好”的哈希函数;另一方面,要选择一种处理冲突的方法。
查找的分类:
静态查找:
不涉及插入和删除操作的查找。
动态查找:
涉及插入和删除操作的查找。