《大话数据结构》
- 第一章 数据结构绪论
- 第二章 算法
- 2.5 算法的五个特性
- 第三章 线性表
- 第四章 栈与队列
- 4.3 栈的抽象数据类型
- 第五章 串
- 第6章 树
- 第七章 图
- 第八章 查找
第一章 数据结构绪论
前言
数据结构(data structure)的简单解释:相互之间存在一种或多种特定关系的数据元素(data element)的集合。
1.3 数据结构起源
首先计算机的应用场景是解决实际问题,步骤为:
- 从实际问题抽象出一个适当的数学模型;
- 设计解此模型的算法;
- 编出程序。
其中,寻找数学模型的实质是:
- 分析问题;
- 从中提取操作的对象,并找出这些操作对象之间含有的关系;(data structure 是一门研究非数值计算的程序设计问题中的操作对象,以及他们之间关系和操作等相关问题的学科)
- 用数学的语言加以描述。
1.4 基本概念和术语
数据(data):所有能输入到计算机中并被计算机程序处理的符号的总称。
数据元素(data element):数据的基本单位。
数据项(data item):是数据不可分割的最小单位。一个 data element 可由多个 data item 组成。
数据对象(data object):性质相同的 data element 集合。
1.5 逻辑结构与物理结构
物理结构是指逻辑结构在计算机中的存储形式。
1.6 抽象数据类型
数据类型:一个值的集合和定义在这个值集上的一组操作的总称。
C语言中,数据类型可以分为两类:
- 原子类型:是不可以再分解的基本类型,包括整型、实型、字符型等;
- 结构类型:由若干个类型组合而成,是可以再分解的。
抽象:抽取出事物具有的普遍性的本质。抽取特征忽略细节,是对具体事物的概括。
对已有的数据类型进行抽象,就有了抽象数据类型 。
抽象数据类型(Abstract Data Type,ADT):一个数学模型及定义在该模型上的一组操作。
1.7 小结
第一章介绍了一些术语的概念,例如:data、data element、data item、data subject。
并介绍了数据结构分为两大类:逻辑结构和物理结构,向下又可以更细的划分类型。
第二章 算法
算法(algorithm):是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令代表一个或多个操作。
2.5 算法的五个特性
2.6 算法设计的要求
好的算法有以下要求 :
- 正确性:算法应该具有输入、输出和加工处理的无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
- 可读性:便于阅读、理解和交流。
- 健壮性:当输入数据不合法是,算法也能做出相关处理,而不是产生异常或莫名秒的结果。
- 时间效率高和存储量低:时间复杂度和空间复杂度低。
2.7 算法效率的度量方法
事后统计方法:设计好程序后,利用计算机计时器对不同算法编制的程序的运行时间进行比较。
这种方法有很大缺陷:
- 测试前需要编制好程序,所耗时间和精力较大;
- 时间的比较受到计算机硬件和软件等环境因素影响较大;
- 测试数据的规模会影响不同算法的运行时间比较结果,因此不好选取测试数据的规模。
事前分析估算方法:在程序编制前,依据统计方法对算法进行估算。
抛开与计算机软、硬件相关的因素,一个程序的运行时间依赖于算法的好坏和问题的输入规模。
测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数。
2.8 函数的渐近增长
判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更因该关注最高阶项的次数。
2.9 算法时间复杂度
时间复杂度随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,记作:
T(n)= O(f(n))
随着n的增大,T(n)增长最慢的算法为最优算法。
推导大O阶的方法:
- 用常数1取代运行时间中的所有加法常数。
- 再修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。
2.10 常见的时间复杂度
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
2.11 最坏情况与平均情况
一般没有特殊说明的情况下,时间复杂度指最坏时间复杂度。
2.12 空间复杂度
算法执行所需辅助空间相对于输入数据量而言如果是常数,则S(n)=O(1)。
2.13 小结
- 算法的定义
- 算法的特性
- 好算法的特征
- 算法好坏的度量方法
- 常见时间复杂度所耗时间的大小排列
第三章 线性表
线性表(linear list):零个或多个 data element 的有限序列。
3.2 线性表的定义
线性表的逻辑结构是线性结构
3.3 线性表的抽象数据类型
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,……,an},每个元素的类型为DataType,除第一个元素,每个元素都有一个直接前驱元素,除最后一个元素外,每个元素都有一个直接后继元素。data element 之间的关系是一对一的。
Operation
InitList(*L):初始化操作,建立一个空的线性表L。
ListEmpty(L):若线性表为空,返回true,否则返回false。
ClearList(*L):将线性表清空。
GetElem(L,i,*e):将线性表L中第i个位置元素返回给e。
LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功,否则返回0表示失败。
ListInsert(*L,i,e):在线性表L中第i个位置插入新元素e。
ListDelete(*L,i,*e):删除L中第i个位置的元素,并用e返回其值。
ListLength(L):返回L的元素个数。
endADT
3.4 线性表的顺序存储结构
定义:用一段地址连续的存储单元依次存储线性表的数据元素。
#define MAXSIZE 20
typrdef int ElemType;
typredef struct
{
ElemType data[MAXSIZE];
int length;
}SqList;
可以用C语言的一维数组来实现顺序存储结构,数组的存取时间复杂度为O(1),把具有这一特点的存储结构称为随机存取结构。
3.5 顺序存储结构的插入和删除
3.5.1 获得元素操作GetElem
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALESE 0
typrdef int Status;
Status GetElem (SqList L; int i ; ElemType *e)
{
if(L.length == o || i < 1 || i > L.length)
return ERROR;
*e = L.data[i-1];
return OK;
}
3.5.2 插入操作ListInsert
Status ListInsert(SqList *L,int i ,ElemType e)
{
int k;
if(L->length == MAXSIZE)
return ERROR;
if(i<1 || i>L->length+1)
return ERROR;
if(i <= L->length) //注意要判断插入数据位置是否在表尾。
{
for(k = L->length-1; k >= i-1; k--)
{
L->data[k+1] = L->data[k];
}
}
L-.data[i-1] = e; //
L->length++; //notice
return OK;
}
3.5.3 删除操作
Status ListDelete(SqList *L, int i , ElemType e)
{
int k;
if(L->length == 0)
return ERROR;
if(i < 1 || i > L->length)
return ERROR;
if(i < L->length)
{
for(k = i; k < L->length; k++)
L->data[k-1] = L->data[k];
}
L->length --;
return OK;
}
3.5.4 线性表顺序存储结构的优缺点
优点:
- 无需为存储元素间逻辑关系增加额外存储空间;
- 存取时间复杂度为O(1).。
缺点:
- 插入、删除操作需要移动大量元素;
- 当线性表长度变化较大时,难以确定存储空间的容量;
- 造成存储空间的“碎片”。
3.6 线性表的链式存储结构
3.6.2 线性表链式存储结构的定义
每个 element 除了存储数据信息外(数据域),还需存储其直接后继的位置信息(指针域)。这两部分信息组成 data element 的存储映像,称为结点(Node)。
头指针:存储链表中第一个 node 的地址。
头结点:单链表第一个 node 前附设一个 node,头结点数据域通常不存储信息,也可以存链表的长度等附加信息。
3.6.4 线性表链式存储结构代码表述
typedef struct Node
{
ElemType data;
struct Node * next;
}Node;
typedef struct Node * LinkList;
3.7 单链表的读取
Status GetElem( LinkLst L, int i , ElemTypre * e)
{
int j;
LinkList p;
p = L->next;
j = 1;
while(p && j < i)
{
p = p->next;
j++;
}
if(!p || j>i)
return ERROR;
*e = p->data;
return OK;
}
3.8 单链表的插入与删除
3.8.1 单链表的插入
Status ListInsert(LinkList * L, int i , ElemType e)
{
int j ;
LinkList p,s;
p = *L;
j = 1;
while(p && j<i)
{
p = p->next;
j++;
}
if(!p || j>i)
return ERROR;
s = (LinkList) malloc ( sizeof(Node) );
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
3.8.2 单链表的删除【代码有疑惑】
Status ListInsert(LinkList L, int i , ElemType * e)
{
int j ;
LinkList p,q;
p = *L;
j = 1;
while(p && j<i)
{
p = p->next;
j++;
}
if(!p || j>i)
return ERROR;
s = (LinkList) malloc ( sizeof(Node) );
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
3.9 单链表的整表创建
void CreatListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand( time(0) );
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for(i= 0;i < n;i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100+1;//随机生成100以内的数;
p-next = (*L)->next;
(*L)->next = p;
}
}
尾插法:
void CreatListTail(LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(i = 0;i < n;i++)
{
p = (Node *)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
r = p;
}
r->next = NULL;
}
3.10 单链表的整表删除
Status ClearList(LinkList *L)
{
LinkLIst p,q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
3.12 静态链表
静态链表是为了没有指针的高级语言设计的一种实现单链表能力的方法,这种思考方式颇为巧妙,理解其思想,以备不时之需。
3.13 循环链表
循环链表的定义:将单链表中终端结点的指针端由空指针改为指向头结点。整个链表首尾相接,形成了一个“环”。
循环链表若想访问尾结点,时间复杂度为O(n),当对链表进行改造后,增加一个尾指针(LinkList rear),则访问尾结点时间复杂度为O(1),如此访问存放首个 element 的结点时间复杂度也为O(1)。
合并两个循环链表的操作:
链表1的尾指针为 rearA
链表2的尾指针为 rearB
p = rearA->next;
rearA->next = rearB->next->next;
q = rearB->next;
rearB->next = p;
free(q);
3.14 双向链表
定义:双向链表(double linked list)是在单链表的每个结点中,在设置一个指向其前驱结点的指针域。
typedef struct DulNode
{
ElementType data;
struct DulNode * prior;
suruct DulNode * next;
}DulNode, * DuLinkList;
双向链表部分操作和单链表相同,例如 ListLength,GetElem,LocateElem等,而插入、删除操作需要更改两个指针变量。
插入操作(在 p 结点后插入 s 结点);
s->prior = p;
s-> next = p->next;
p->next = s;
s->next->prior = s;
删除操作(删除 p 结点):
p->prior = p->next;
p->next->prior = p->prior;
free(p);
3.15 小结
线性表分顺序存储、链式存储,其中链式存储又可以细分为单链表、静态链表、循环链表、双向链表。
掌握链表的定义、初始化、插入、删除等操作。
第四章 栈与队列
栈:限定仅在表尾进行插入或删除操作的线性表。
队列:只允许在一端进行插入操作,另一端进行删除操作的线性表。
4.2 栈(stack)的定义
栈顶(Top):允许插入和删除操作的一端。
栈底(Bottom):另一端。
空栈:不含任何数据元素的栈。
栈又称为后进先出(Last In First Out)结构,LIFO结构。
栈是一个线性表,栈元素具有线性关系。
栈元素的插入又称入栈,元素的删除又称出栈
栈的出战次序要保证始终是栈顶元素出栈。
4.3 栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同类型,相邻元素具有前驱和后继关系。
Opration
InitStack(*S):初始化操作,建了一个空栈。
DestoryStack(*S):若栈存在,则销毁它。
ClearStack(*S):将栈清空。
StackEmpty(S):若栈为空,返回true,否则返回false。
GetTop(S,*e):若栈S存在且非空,用e返回S的栈顶元素。
Push(*s,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop(*S,*e):删除栈S中的栈顶元素,并用e返回其值。
StackLe