C语言数据结构补充.docx
《C语言数据结构补充.docx》由会员分享,可在线阅读,更多相关《C语言数据结构补充.docx(19页珍藏版)》请在冰豆网上搜索。
C语言数据结构补充
不思,故有惑;不求,故无得;不问,故不知。
一、基本概念和术语
数据(Data):
是对信息的一种符号表示
在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称
数据元素(DataElement):
是数据的基本单位
在计算机程序中通常作为一个整体进行考虑和处理
一个数据元素可由若干个数据项组成
数据项是数据的不可分割的最小单位
数据对象(DataObject):
是性质相同的数据元素的集合
是数据的一个子集
数据结构(DataStructure):
是相互之间存在一种或多种特定关系的数据元素的集合
数据结构分逻辑结构和物理结构
数据之间的相互关系称为逻辑结构
通常分为四类:
*集合结构中的数据元素除了同属于一种类型外
别无其它关系
*线性结构结构中的数据元素之间存在一对一的关系
*树型结构结构中的数据元素之间存在一对多的关系
*图状结构或网状结构结构中的数据元素之间存在多对多的关系
数据结构的形式定义为:
数据结构是一个二元组:
Data-Structure=(D
S)
其中:
D是数据元素的有限集
S是D上关系的有限集
例复数的数据结构定义如下:
Complex=(C
R)
其中:
C是含两个实数的集合﹛C1
C2﹜
分别表示复数的实部和虚部
R={P}
P是定义在集合上的一种关系{〈C1
C2〉}
数据结构在计算机中的表示称为数据的物理结构
又称为存储结构
数据结构在计算机中有两种不同的表示方法:
顺序表示和非顺序表示
由此得出两种不同的存储结构:
顺序存储结构和链式存储结构
*顺序存储结构:
用数据元素在存储器中的相对位置来表示数据元素之间的逻辑关系
*链式存储结构:
在每一个数据元素中增加一个存放地址的指针
用此指针来表示数据元素之间的逻辑关系
数据类型(DataType):
在一种程序设计语言中
变量所具有的数据种类
在C语言中数据类型:
基本类型和构造类型
基本类型:
整型、浮点型、字符型
构造类型:
数组、结构、联合、指针、枚举型、自定义
二、线性表
线性表(LinearList):
由n(n≧0)个数据元素(结点)a1
a2
...an组成的有限序列
其中数据元素的个数n定义为表的长度
当n=0时称为空表
常常将非空的线性表(n>0)记作:
(a1
a2
...an)
例1、26个英文字母组成的字母表(A
B
C、...、Z)
例2、某校从1978年到1983年各种型号的计算机拥有量的变化
(6
17
28
50
92
188)
例3、学生健康情况登记表如下
姓名学号性别年龄健康情况王小林790631男18健康陈红790632女20一般刘建平790633男21健康张立立790634男17神经衰弱2.1顺序表
把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里
用这种方法存储的线性表简称顺序表
假设线性表的每个元素需占用l个存储单元
并以所占的第一个单元的存储地址作为数据元素的存储位置
则线性表中第i+1个数据元素的存储位置LOC(ai+1)和第i个数据元素的存储位置LOC(ai)之间满足下列关系:
LOC(ai+1)=LOC(ai)+l线性表的第i个数据元素ai的存储位置为:
LOC(ai)=LOC(a1)+(i-1)*l
由于C语言中的一维数组也是采用顺序存储表示
故可以用数组类型来描述顺序表
又因为除了用数组来存储线性表的元素之外
顺序表还应该用一个变量来表示线性表的长度属性
所以我们用结构类型来定义顺序表类型
#defineListSize100
typedefintDataType;
typedefstruc{DataTypedata[ListSize];
intlength;}Sqlist;
在顺序表存储结构中
很容易实现线性表的一些操作
如线性表的构造、第i个元素的
访问
线性表的插入运算是指在表的第i(1≦i≦n+1个位置上
插入一个新结点x
使长度为n的线性表(a1
...ai-1
ai
...
an)变成长度为n+1的线性表(a1
...ai-1
x
ai
...
an)
线性表插入算法:
VoidInsertList(Sqlist*l
DataTypex
inti)
{intj;
if(i<1||i>l.length+1)
printf("Positionerror");returnERROR;
if(l.length>=ListSize)
printf("overflow");exit(overflow);
for(j=l.length-1;j>=i-1;j--)
l.data[j+1]=l.data[j];
l.data[i-1]=x;
l.length++;}
线性表删除算法:
VoiddeleteList(Sqlist*l
inti)
{intj;
if(i<1||i>l.length)
printf("Positionerror");returnERROR
for(j=i;j<=l.length-1;j++)l.data[j-1]=l.data[j];
l.length--;}
2.2链表
线性表的顺序表示的特点是用物理位置上的邻接关系来表示结点间的逻辑关系
这一特点使我们可以随机存取表中的任一结点
但它也使得插入和删除操作会移动大量的结点.为避免大量结点的移动
我们介绍线性表的另一种存储方式
链式存储结构
简称为链表(LinkedList)
2.2.1线性链表
链表是指用一组任意的存储单元来依次存放线性表的结点
这组存储单元即可以是连续的
也可以是不连续的
甚至是零散分布在内存中的任意位置上的
因此
链表中结点的逻辑次序和物理次序不一定相同
为了能正确表示结点间的逻辑关系
在存储每个结点值的同时
还必须存储指示其后继结点的地址(或位置)信息
这个信息称为指针(pointer)或链(link)
这两部分组成了链表中的结点结构:
datanext 其中:
data域是数据域
用来存放结点的值
next是指针域(亦称链域)
用来存放结点的直接后继的地址(或位置)
链表正是通过每个结点的链域将线性表的n个结点按其逻辑次序链接在一起的
由于上述链表的每一个结只有一个链域
故将这种链表称为单链表(SingleLinked)
显然
单链表中每个结点的存储地址是存放在其前趋结点next域中
而开始结点无前趋
故应设头指针head指向开始结点
同时
由于终端结点无后继
故终端结点的指针域为空
即null(图示中也可用^表示)
例1、线性表:
(bat
cat
eat
fat
hat
jat
lat
mat)的单链表示意图如下:
用C语言描述的单链表如下:
typedefchardatatype;
typedefstructnode{datatypedata;
structnode*next;}listnode;
typedeflistnode*linklist;
listnode*p;
p为动态变量
它是通过标准函数生成的
即
p=(listnode*)malloc(sizeof(listnode));
函数malloc分配了一个类型为listnode的结点变量的空间
并将其首地址放入指针变量p中
一旦p所指的结点变量不再需要了
又可通过标准函数free(p)释放所指的结点变量空间
2.2.2建表算法:
头插法建表:
该方法从一个空表开始
重复读入数据
生成新结点
将读入数据存放到新结点的数据域中
然后将新结点插入到当前链表的表头上
直到读入结束标志为止
头插法建表算法:
linklistcreatelistf(void)
{charch;
listnode*p
*head;
head=null;
ch=getchar();
while(ch!
=′\n′){
p=(listnode*)malloc(sizeof(listnode));
p->data=ch;
p->next=head;
head=p;
ch=getchar();}
return(head);}
尾插法建表:
该方法是将新结点插入到当前链表的表尾上
为此必须增加一个尾指针r
使其始终指向当前链表的尾结点
尾插法建表算法linklistcreater()
{charch;listnode*p
*r
*head;
head=NULL;r=NULL;
while((ch=getchar()!
=′\n′){
p=(listnode*)malloc(sizeof(listnode));
p->data=ch;
if(head==NULL)head=p;
elser->next=p;
r=p;}
if(r!
=NULL)r->next=NULL;
return(head);}
说明:
第一个生成的结点是开始结点
将开始结点插入到空表中
是在当前链表的第一个位置上插入
该位置上的插入操作和链表中其它位置上的插入操作处理是不一样的
原因是开始结点的位置是存放在头指针(指针变量)中而其余结点的位置是在其前趋结点的指针域中
算法中的第一个if语句就是用来对第一个位置上的插入操作做特殊处理
算法中的第二个if语句的作用是为了分别处理空表和非空表两种不同的情况
若读入的第一个字符就是结束标志符
则链表head是空表
尾指针r亦为空
结点*r不存在;否则链表head非空
最后一个尾结点*r是终端结点
应将其指针域置空
如果我们在链表的开始结点之前附加一个结点
并称它为头结点
那么会带来以下两个优点:
a、由于开始结点的位置被存放在头结点的指针域中
所以在链表的第一个位置上的操作就和在表的其它位置上的操作一致
无需进行特殊处理;b、无论链表是否为空
其头指针是指向头结点在的非空指针(空表中头结点的指针域为空)
因此空表和非空表的处理也就统一了
linklistcreatelistr1(){
charch;
listnode*head=(linklist)malloc(sizeof(listnode));
listnode*p
*r
r=head;
while((ch=getchar())!
=′\n′{
p=(listnode*)malloc(sizeof(listnode));
p->data=ch;
p->next=p;
r=p;}
r->next=NULL;
return(head);}
2.2.3查找运算
1、按序号查找
在链表中
即使知道被访问结点的序号i
也不能象顺序表中那样直接按序号i访问结点
而只能从链表的头指针出发
顺链域next逐个结点往下搜索
直到搜索到第i个结点为止
因此
链表不是随机存取结构
设单链表的长度为n
要查找表中第i个结点
仅当1≦i≦n时
i的值是合法的
但有时需要找头结点的位置
故我们将头结点看做是第0个结点
其算法如下:
Listnode*getnode(listnode*head
inti)
{intj;
listnode*p;
p=head;j=0;
while(p->next&&j
p=p->next;
j++;}
if(i==j)returnp;
elsereturnNULL;}
2、按值查找
按值查找是在链表中
查找是否有结点值等于给定值key的结点
若有的话
则返回首次找到的其值为key的结点的存储位置;否则返回NULL
查找过程从开始结点出发
顺着链表逐个将结点的值和给定值key作比较
其算法如下:
Listnode*locatenode(linklisthead
intkey)
{listnode*p=head->next;
while(p&&p->data!
=key)
p=p->next;
returnp;}
2.2.4插入运算
插入运算是将值为x的新结点插入到表的第i个结点的位置上
即插入到ai-1与ai之间
因此
我们必须首先找到ai-1的存储位置p
然后生成一个数据域为x的新结点
并令结点*p的指针域指向新结点
新结点的指针域指向结点ai
从而实现三个结点ai-1
x和ai之间的逻辑关系的变化
插入过程如:
voidinsertnode(linklisthead
datetypex
inti)
{listnode*p
*q;
p=getnode(head
i-1);
if(p==NULL)error(〝positionerror〞);
q=(listnode*)malloc(sizeof(listnode));
q->data=x;
q->next=p->next;
p->next=q;}
2.2.5删除运算
删除运算是将表的第i个结点删去
因为在单链表中结点ai的存储地址是在其直接前趋结点ai-1的指针域next中
所以我们必须首先找到ai-1的存储位置p
然后令p->next指向ai的直接后继结点
即把ai从链上摘下
最后释放结点ai的空间
将其归还给"存储池"
voiddeletelist(linklisthead
inti)
{listnode*p
*r;
p=getnode(head
i-1);
if(p==NULL||p->next==NULL)returnERROR;
r=p->next;
p->next=r->next;
free(r);}
从上面的讨论可以看出
链表上实现插入和删除运算
无须移动结点
仅需修改指针
三、循环链表
循环链表时一种头尾相接的链表
其特点是无须增加存储量
仅对表的链接方式稍作改变
即可使得表处理更加方便灵活
单循环链表:
在单链表中
将终端结点的指针域NULL改为指向表头结点的或开始结点
就得到了单链形式的循环链表
并简单称为单循环链表
为了使空表和非空表的处理一致
循环链表中也可设置一个头结点
这样
空循环链表仅有一个自成循环的头结点表示
如下图所示:
双向链表(Doublelinkedlist):
在单链表的每个结点里再增加一个指向其直接前趋的指针域prior
这样就形成的链表中有两个方向不同的链
故称为双向链表
形式描述为:
typedefstructdlistnode{
datatypedata;
strucdlistnode*prior
*next;
}dlistnode;
typedefdlistnode*dlinklist;
dlinklisthead;
和单链表类似
双链表一般也是由头指针唯一确定的
增加头指针也能使双链表上的某些运算变得方便
将头结点和尾结点链接起来也能构成循环链表
并称之为双向链表
设指针p指向某一结点
则双向链表结构的对称性可用下式描述:
(p->prior)->next=p=(p->next)->prior
即结点*p的存储位置既存放在其前趋结点*(p->prior)的直接后继指针域中
也存放在它的后继结点*(p->next)的直接前趋指针域中
双向链表的前插操作算法如下:
voiddinsertbefor(dlistnode*p
datatypex)
{dlistnode*q=malloc(sizeof(dlistnode));
q->data=x;
q->prior=p->prior;
q->next=p;
p->prior->next=q;
p->prior=q;}
voidddeletenode(dlistnode*p)
{p->prior->next=p->next;
p->next->prior=p->prior;
free(p);}
四、栈和队列
栈(Stack)是限制在表的一端进行插入和删除运算的线性表
通常称插入、删除的这一端为栈顶(Top)
另一端为栈底(Bottom)
当表中没有元素时称为空栈
假设栈S=(a1
a2
a3
...an)
则a1称为栈底元素
an为栈顶元素
栈中元素按a1
a2
a3
...an的次序进栈
退栈的第一个元素应为栈顶元素
换句话说
栈的修改是按后进先出的原则进行的
因此
栈称为后进先出表(LIFO)
队列(Queue)也是一种运算受限的线性表
它只允许在表的一端进行插入
而在另一端进行删除
允许删除的一端称为队头(front)
允许插入的一端称为队尾(rear)
例如:
排队购物
操作系统中的作业排队
先进入队列的成员总是先离开队列
因此队列亦称作先进先出(FirstInFirstOut)的线性表
简称FIFO表
当队列中没有元素时称为空队列
在空队列中依次加入元素a1
a2
...an之后
a1是队头元素
an是队尾元素
显然退出队列的次序也只能是a1
a2
...an
也就是说队列的修改是依先进先出的原则进行的
循环队列
链队列
队列的链式存储结构简称为链队列
它是限制仅在表头删除和表尾插入的单链表
五、串
串(String)是零个或多个字符组成的有限序列
一般记作S="a1a2a3...an"
其中S是串名
双引号括起来的字符序列是串值;ai(1≦i≦n)可以是字母、数字或其它字符;串中所包含的字符个数称为该串的长度
长度为零的串称为空串(EmptyString)
它不包含任何字符
六、数组和广义表
七、树和二叉树
八、图和网
?
?
?
?
?
?
?
?
数据结构补充
1/8