参考数据结构C语言版|第2版 严蔚敏
目录
预定义的常量及类型
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status; //为整型数据类型重新取一个名字为Status
//数据元素的类型约定为ElemType,由用户在使用时自行定义
理解:线性表,栈,队列,二叉树等的链式存储结构在定义其存储结构时,其实都是在定义它的结点类型,因为其是右许多这些结点构成的
第一章 绪论
1.1 数据结构研究的内容:数据结构主要研究非数值计算问题
1.2 基本概念和术语
1.2.1 数据、数据元素、数据项和数据对象
数据:客观事物的符号表示,是所有能输入到计算机中并被计算机程序处理的符号。
数据元素(记录,元素):数据的基本单位。如一条学生记录
数据项:组成数据元素的最小单位。如学生记录的某一个字段
数据对象:性质相同的数据元素的集合。
包含关系:数据>数据对象>数据元素>数据项
1.2.2 数据结构
数据结构是相互存在一种或多种特定关系的数据元素的集合。
一、数据结构有两个层次:逻辑结构和存储结构
1、数据的逻辑结构:描述数据元素间的逻辑关系,和存储无关。数据的逻辑结构有两个要素:一个是数据元素,二是关系。关系是数据元素间的逻辑关系
数据的逻辑结构通常有四类基本结构:
(1)集合结构:数据元素之间除了有属于同一集合的关系外,再无其他关系
(2)线性结构:数据元素之间存在一对一的关系
(3)树结构:数据元素之间存在一对多的关系
(4)图结构:数据元素之间存在多对多的关系
数据的逻辑结构分类
2、数据的存储结构:数据对象在计算机中的存储表示
数据元素在计算机中有两种基本的存储结构:分别是顺序存储结构和链式存储结构
(1)顺序存储结构:用一组地址连续的存储空间来存储元素,通常用程序设计语言的数组来表示。
(2)链式存储结构:为了表示结点间的关系,为每个结点附加指针,用于存放后继元素的地址,通常用程序设计语言的指针来表示。
1.2.3 数据类型和抽象数据类型(无)
1.3 抽象数据类型的表示和实现
抽象数据类型只是一个模型的定义,不涉及具体的实现
1.4 算法和算法分析
1.4.1 算法的定义和特性
算法:为了解决某类问题所规定的一个有限操作序列。
算法的五个特性:
(1)有穷性
(2)确定性
(3)可行性
(4)输入
(5)输出
1.4.2 评价算法优劣的基本标准
正确性:在合理的输入下能够在有限的时间内得到正确的结果
可读性:便于人们理解和交流
健壮性:能够对输入的错误数据进行相应的处理
高效性:包括时间和空间两个方面
1.4.3 算法的时间复杂度
1、问题规模和语句频度:问题规模是算法求解问题输入量的多少,问题大小的本质表示,一般用整数n来表示。一条语句重复执行的次数称为语句频度。
2、算法的时间复杂度定义
1.4.4 算法的空间复杂度
解决问题所需的额外空间
小结:要求掌握数据结构的相关基本概念,包括数据,数据元素,数据项,数据对象,数据结构,逻辑结构,存储结构等,重点掌握数据结构所含两个层次的具体含义和其相互关系,了解抽象数据类型的定义、表示与实现方法,了解算法的特性和评价标准,重点掌握算法时间复杂度的分析方法。
第二章 线性表
2.1 线性表的定义和特点
线性表:由n(n>=0)个数据特性相同的元素构成的有限序列。
说明:
线性表的长度:线性表中元素的个数;没有元素时称为空表。
对于非空线性表或线性结构,其特点是:
(1)存在唯一一个被称作第一个的数据元素
(2)存在唯一一个被称作最后一个的数据元素
(3)除第一个数据元素之外,每个数据元素都只有一个直接前驱
(4)除最后一个数据元素之外,每个数据元素均只有一个直接后继
2.2 线性表的抽象数据类型定义
2.3线性表的顺序表示和实现
2.3.1 线性表的顺序存储表示
线性表的顺序存储结构:用一组地址连续的存储单元依次存储线性表中的数据元素。
- 采用这种存储结构的线性表也称为顺序表。(特点:逻辑上相邻的数据元素,在物理次序也是相邻的)
//顺序表的顺序结构
#define MAXSIZE 100 //线性表的最大长度
typedef struct
{
ElemType *elem; //用一个指针来存储存储空间的基地址
int length; //表示顺序表的当前长度
}SqList;
2.3.2 线性表的基本操作实现
1.顺序表的初始化:构造一个空的顺序表。
算法步骤:①为线性表L动态分配一个预定义大小的数组空间,并让elem指向它。②将表的当前长度设置为0。
Status InitList(SqList &L)
{
L.elem=new ElemType[MAXSIZE]; //动态分配一个预定义大小的数组空间给顺序表L
if(!L.elem) exit(OVERFLOW); //如果存储分配失败则退出
L.length=0; //设置当前顺序表的长度为0
return OK;
}
2.取值:按序号获取顺序表中数据元素的值
算法步骤:①先判断序号i是否合理(1<=i<=L.length),如果不合理,则返回ERROR。②如果i的值合理,就将第i个数据元素L.elem[i-1]赋值给参数e,通过e返回第i个数据元素的值。
Status GetElem(SqList L,int i,ElemType &e)
{
if(i<1||i>L.length)return ERROR;
e=L.elem[i-1]; //第i个数据元素存储在elem[i-1]号存储单元中
return OK;
}
3.查找:在顺序表中查找第一个与e相等的元素
算法步骤:①从表中第一个元素开始,依次和e进行比较,如果查找到与e相等的数据元素,则查找成功,返回该元素的序号i+1(序号为1的元素存储在下标为0的数组空间中)。②如果整个表中都没有查找到e,则查找失败,返回0。
Status SearchElem(SqList L,ElemType e)
{
for(int i=0;i<L.length;i++) //从表中第一个元素开始,依次和e进行比较
{
if(L.elem[i]==e)return i+1; //如果查找到,返回元素e在表中的序号
}
return 0; //查找失败,返回0
}
4.插入:插入元素到顺序表中
算法步骤: ①先判断插入位置i是否合理(1<=i<=L.length+1),如果不合法则返回ERROR。②再判断顺序表的存储空间是否满了,满了则返回ERROR。③将最后一个元素到第i个位置的元素依次向后移动一个位置,腾出第i个位置(i=n+1时不需移动),将e插入到第i个位置上。④当前长度加1。
Status InsertList(SqList &L,int i,ElemType e)
{
if((i<1)||(i>L.length+1)) return ERROR; //
if(L.length==MAXSIZE) return ERROR;
for(int j=L.length-1;j>=i-1;j--)
{
L.elem[j+1]=L.elem[j];
}
L.elem[i-1]=e;
++L.length;
return OK;
}
5.删除:删除表中的第i个元素
算法步骤:①先判断删除位置i是否合理(1<=i<=L.length),如果不合理则返回ERROR。②将第i+1个到最后一个元素依次往前移动一个位置(i=L.length的时候不用移动)③将线性表的当前长度减一。
Status ListDelete(SqList &L,int i)
{
if((i<1)||(i>L.length)) return ERROR; //插入位置不合理
for(j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j]; //被删除元素之后的元素都往前移
--L.length;
return OK;
}
顺序表的优点:可以随机存取表中的任一元素
顺序表的缺点:①插入删除时要移动大量元素②浪费存储空间③数据元素的个数不能随意扩充
2.4 线性表的链式存储表示与实现
2.4.1 单链表的定义和表示
单链表:由n个结点链接构成一个链表,每个结点由数据域和指针域组成。数据域是存储数据的域,指针域存储直接后继结点的地址。(因为每个结点只包含一个指针域所以叫做单链表或线性链表)
双链表:每个结点有两个指针域的链表
循环链表:首尾结点相接的链表
//----------线性表的链式存储结构(就是如何定义单链表的每个结点)
typedef struct LNode
{
ElemType data; //结点的数据域
struct LNode *next //结点的指针域
}LNode,*LinkList;
单链表的存取必须从头指针开始,头指针指向链表的第一个结点。
说明:
(1)LNode和*LinkList本质上是等价的,习惯上用LinkList定义单链表(指向单链表的头指针) ,LNode定义指向结点的指针变量。例如:
LNode *p
LinkList L //L为单链表的头指针
(2)单链表是由表头指针唯一确定的,所以可以用头指针来命名单链表。
(3)注意区分指针变量和结点变量
LinkList p
LNode *p
//以上,p为指向某结点的指针变量,表示该结点的地址;
//而*p为对应的结点变量,表示该结点的名称
增加头结点的单链表:
首元结点、头结点、头指针说明:
(1)首元结点:存储链表中第一个数据元素a1的结点。
(2)头结点:是首元结点之前的一个结点。其指针域指向首元结点,数据域可以不存储任何信息,也可以存储和数据元素类型相同的其他信息。
(3)头指针:是指向链表的第一个结点的指针;分情况可指向头结点或首元结点
2.4.2单链表基本操作的实现
1.初始化:构造一个空表
算法:
①创建一个结点作为头结点,用头指针L指向头结点
②将头结点的指针域设置为空
Status InitList(LinkList &L)
{
L=new LNode; //创建一个新的结点为头结点
L->next=NULL; //将头结点的指针域置空
return OK;
}
2.取值:根据结点的序号i,从链表的首元结点开始顺着链域next逐个结点向下访问
算法:
①用结点类型的指针p指向首元结点,用j做计时器并设置初值为1
②从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针p不为空,且没有达到序号为i的结点时,进行以下循环:
p指向下一个结点
计数器j相应加1
③退出循环时,如果p指针为空,或计数器j大于i,则表示序号i不合法(i大于表长n或小于等于0),取值失败返回ERROR;否则取值成功,当i==j时,p指向的结点就是要查找的第i个结点,用参数e保存当前结点的数据域,返回OK
Status GetElem(LinkList L,int i,ElemType &e)
{
p=L->next;
j=1;
while(p&&j<i) //当p指向最后一个结点的后一个位置或p指向第i个元素
{
p=p->next; //p指向下一个结点
++j; //计数器加1
}
if(!p||j>i)return ERROR; //如果p为空或i不合法(i大于表长n或小于等于0),则返回ERROR
e=p->data;
return OK;
}
3.查找:和顺序表类似,从链表的首元结点出发,依次将结点值和给定值e比较,返回查找结果
算法:
①用指针p指向首元结点
②从首元结点开始依次向下查找,指向当前结点的指针p不为空,并且p所指结点的数据域不等于e时,循环执行以下操作:
p指向下一个结点
③返回p。如果查找成功,p此时为结点的地址值,若查找失败,p的值即为NULL
LNode *LocateElem(LinkList L,ElemType e)
{
p=L->next; //先用p指向首元结点
while(p&&p->data!=e)
p=p->next;
return p;
}
4.插入:将 值e 插入到链表的第i个结点的位置上,即插入到ai-1和ai之间
算法:
①找到第i-1个结点并用指针p指向该结点
②创建一个新结点*s
③将新结点*s的数据域置为e
④将新结点*s的指针域指向第i个结点
⑤将结点*p的指针域指向新结点*s
Status ListInsert(LinkList &L,int i,ElemType e)
{
p=L;
j=0;
while(p &a