目录
-
线性表的定义和特点
- 定义:具有相同特性的数据元素的一个有限序列
- 构成:线性起点、ai的直接前趋、ai、ai的直接后继、线性终点
- 特点
- 数据元素个数n为表长
- n=0时为空表
- 非空线性表记作(a1, a2, a3, ..., an)
- ai具体含义在不同情况下可以不同
- 例子
- 26个英文字母表、12个星座、学生信息表……
- 稀疏多项式使用线性表存储时要注意(有两个数据项,一个是系数,一个是指数)
- 顺序储存结构存在的问题
- 空间分配不灵活
- 运算的空间复杂度高
- 总结
- 线性表中的数据元素可以为简单类型(数字),也可以为复杂类型(既有字符串,也有整数)
- 涉及实际问题的基本操作具有很大相似性
- 要先抽象出逻辑结构和基本操作,然后实现存储结构和基本操作
-
线性表的类型定义
- 抽象数据类型线性表定义:
- 基本操作
- InitList(&L)
- 操作结果:构造一个空线性表L
- DestroyList(&L):
- 初始条件:表须存在
- 操作结果:销毁一个线性表L
- ClearList(&L)
- 初始条件:表须存在
- 操作结果:重置一个线性表L,使其变为空表
- ListMepty(L)
- 初始条件:表须存在
- 判断L是否为空,若空则返回TRUE,否则返回FALSE
- ListLength(L)
- 初始条件:表须存在
- 操作结果:返回线性表中的数据元素个数
- GetElem(L,i,&e)
- 初始条件:表须存在, i要满足1 <= i <= ListLength(L)
- 操作结果:用e返回线性表L中第i个数据元素的值
- LocateElem(L,e,compare())
- 初始条件:表须存在
- 操作结果:返回L中第一个与满足compare()的数据元素的位序,若无符合条件的元素则返回0(compare()是数据元素的判定函数,可理解为是一个判断条件)
- PriorElem(L, cur_e, &pre_e)
- 初始条件:表须存在
- 操作结果:若cur_e是L的数据元素且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无意义
- NextElem(L, cur_e, &next_e)
- 初始条件:表须存在
- 操作结果:若cur_e是L的数据元素且不是最后一个,则用next_e返回它的前驱,否则操作失败,next_e无意义
- LIstInsert(&L, i, e)
- 初始条件:表须存在。1 <= i <= ListLength(L) + 1
- 操作结果:在L的第i个位置之前插入新的数据元素e, L长度加1
- ListDelete(&L, i, &e)
- 初始条件:表须存在,1 <= ListLength(L)
- 操作结果:删除L的第i个数据元素,并用e返回已经删除的元素的值,L长度减一
- ListTraverse(&L, visited())
- 初始条件:表须存在
- 操作结果:依次对线性表中的每个元素调用visited(),(遍历一遍线性表,每一个都进行一个相同的操作visited(),可以是修改,输出等等)
- InitList(&L)
- 总结:以上只是逻辑结构上定义的运算,实现细节在确定存储结构后才考虑
-
线性表的顺序表示和实现
- 线性表的顺序存储表示
- 顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构(地址需连续,若地址不连续——中间存在空的存储单元则其不是一个线性表顺序存储结构)
- 需要占用一片连续的空间——可以由每个元素需要的存储单元l推算出每个数据元素的存储位置
- 顺序存储结构是一种随机存取的存储结构
- 可以用一维数组表示顺序表
- 线性表长可变
- 数组长度不可动态定义
- 可以用一变量表示顺序表的长度属性
- 线性表定义方式SqList:Sequence List
- 类C语言有关操作补充
- 元素类型说明:ElemType可以是int、char、float等类型,length表示顺序表中元素个数
- 数组的静态分配和动态分配,使用时:L.data = (ElemType*)malloc(sizeof(Elemtype)*MAXSIZE), L.length存放元素个数
- 动态分配内存
- malloc(m)函数:开辟m字节长度的地址空间,返回这段空间的首地址
- 例:L.data = (ElemType*)malloc(sizeof(Elemtype)*MAXSIZE),sizeof(ElemType)*MAXSIZE表示出分配的字节长度后,用ElemType*告诉计算机这段空间存放的数据类型是什么,即把这m字节的空间划分成多少块用来存放特定类型的数据
- 需要加载头文件<stdlib.h>
- sizeof(x)函数
- free(p)函数
- malloc(m)函数:开辟m字节长度的地址空间,返回这段空间的首地址
- C++中的动态存储分配
- new 类型名T (初值列表)
- 结果值:成功时,T类型的指针,指向新分配的内存;失败时,0
- new 类型名T (初值列表)
- C++中的参数传递
- 要注意形参与实参的类型、个数、顺序一致
- 方式
- 传值(整型、实型、字符型)
- 传地址
- 指针变量
- 形参变化可以影响实参、也可以不影响实参
- 影响
- 不影响
- 数组名
- 对形参数组所做的任何改变都将反映到实参数组中
- 引用类型
- 形参变化实参也发生变化
- 是相当于对实参进行直接操作,所以当传递的数据量较大时,用此方法比较好
- 指针变量
- 逻辑为序与物理为序相差1
- 操作算法中用到的预定义常量和类型
- 结果状态代码
-
#define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2
-
- typedef int Status Status是函数的类型,值是函数结果状态代码
- typedef char ElemType
- 结果状态代码
- 顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构(地址需连续,若地址不连续——中间存在空的存储单元则其不是一个线性表顺序存储结构)
- 顺序表基本操作的实现
- 查找
- 平均查找长度ASL(Average Search Length):需要与给定值进行比较的关键字的个数的期望值
- 假设每个记录查找的概率相等,则ASL=(n+1)/2,时间复杂度为O(n)
- 平均查找长度ASL(Average Search Length):需要与给定值进行比较的关键字的个数的期望值
- 插入
- 平均移动次数为 n/2,时间复杂度为O(n)
- 向第i个元素之前插入一个新元素时,需要向后移动n+1-i个元素
- 删除
- 平均移动次数:(n-1)/2
- 删除第i个元素,需要向前移动n-i个元素
- 查找
- 顺序表(线性表的顺序存储结构)的小结
- 顺序表的特点
- 逻辑结构与存储结构一致
- 访问每个元素所花的时间相等
- 存取元素的方法为随机存取法
- 优点
- 存储密度大
- 可随机存取表中的任意一个元素
- 缺点
- 插入或删除某个元素时,需要移动大量元素
- 浪费存储空间
- 数据元素的个数不能自由扩充
- 顺序表的特点
- 线性表的顺序存储表示
-
线性表的链式表示和实现
-
与链式存储有关的术语
- 结点:是数据元素的储存映像,由数据域和指针域组成。
- 链表:n个结点由指针链组成的一个链表,是线性表的链式存储映像,称为线性表的链式存储结构。
- 单链表:每个结点上只有一个指针域的链表,又称线性链表
- 双链表:每个结点上有两个指针域,一个指向前一个,一个指向后一个
- 循环链表:首尾相接的链表
- 头指针:指向链表中第一个结点的指针
- 首元结点:链表中存储第一个数据元素a1的结点
- 头节点:链表首元结点之前附设的一个结点
- 空的表示:“^”
-
关于链表的一些讨论
- 空表的表示?
- 无头结点时,头指针为空表示空表
- 有头结点时,头结点指针域为空表示空表
- 设置头结点的好处?
- 便于首元结点的处理
- 便于空表和非空表的统一处理
- 头结点的数据域内装什么内容?
- 可以为空,或者表的属性信息,但不能算入表长
- 空表的表示?
-
链表的特点
- 在存储器上的位置任意,逻辑上相邻的元素物理上不一定相邻
- 访问时只能通过头指针进入链表,因此寻找第一个和最后一个结点所需时间不等——顺序存取法
-
单链表的定义和表示
- 定义方式
- Lnode表示的是结点、LinkList为指向Lnode的指针类型
- 定义链表L:LinkList L;
- 定义节点指针p:LNode *p; ←→LinkList p;
- 为了统一操作,通常这样定义
- 单链表基本操作的实现
- 一些重要操作
- p指向头结点:p=L;
- p=L->next:p指向首元结点
- p=p->next:p指向下一结点
- 初始化
- 步骤:生成新结点作为头结点;用头指针L指向头结点;将头结点的指针域置空;
- 算法描述
- 判断链表是否为空
- 思路:判断头结点的指针域是否为空:(if(L->next))
- 销毁
- 思路:从头指针开始,依次释放所有结点
- p=L; L=L->next; free(p);
- 结束条件:L==NULL;
-
清空
- 思路:从首元结点开始依次释放所有结点,并将头结点的指针域置空
- p=L->next; whlie(p) {q=p->next; free(p); p=q;} ……L->next=NULL;
- 结束条件:p==NULL; 循环条件:P!=NULL;
-
求链表表长
- 思路:从首元结点开始,依次计数所有结点
- (首结点不为空的情况下)p=L->next;while(p!=NULL){i++;p=p->next} return i;
-
取第i个元素的内容
- 思路:从头指针出发,顺着链域next逐个结点依次搜索,直到到了第i个结点为止(链表不是随机存取结构);特殊情况:i不合法或者找不到
- 步骤:从第一个节点开始(p=L->next),计数器j当前值为1;p扫描到下一结点时,计数器j加一;j==i时,返回当前值。
-
查找
- 按值查找——获取数据所在的地址
- 步骤:从第一个节点开始,依次将e和节点内数据相比较;如果找到了,返回其在链表中的“位置”,即第几个元素,或者地址;若没找到则返回NULL或者0
- 按值查找——获取指定数据的位置序号
- 按值查找——获取数据所在的地址
-
插入
- 步骤:首先找到第i-1个结点的位置p;生成一个数据域为e的新结点s;①新结点的指针域指向第i个结点②第i-1个结点的指针域指向新结点
- s->next=p->next; p->next=s;
- 下图中j从零开始计数是因为p一开始指向的是头结点
-
删除
- 步骤:首先找到第i-1个元素的位置p,进而保存要删除的第i个元素的值(可以用q);令p->next指向第i+1个元素;释放第i个元素结点的空间
- p->next = p->next->next;
-
单链表的查找、插入、删除算法的时间效率分析
- 查找:O(n);
- 插入和删除:O(1);(这两个操作都需要借助查找操作,所以总的时间复杂度还是O(n))
- 对于已知结点的后继结点操作时,时间复杂度为O(1),而对于前驱结点操作时,时间复杂度为O()
-
单链表的建立
- 头插法——每一个新元素插入到首元结点的前面
- 空表;生成新结点,将读入的数据放到新结点的数据域中;将该新结点插入到首元结点的前面
- L = (LinkList)malloc(sizeof(LNode)); //生成头结点
- 数据的输入顺序和逻辑顺序是相反的:如输入abcde,存放时按edcba存放
- 该算法的时间复杂度为O(n)
- 尾插法——每一个新元素插入到最后一个结点的后面
- 建立空表;建立新结点,p->data=a;p->next=NULL(给新结点赋值),令尾指针指向这个新结点r->next=p;再将该结点设置为尾结点 r=p;(初始时,尾指针与头指针均指向头结点)
- 该算法的时间复杂度为O(n)
- 头插法——每一个新元素插入到首元结点的前面
- 一些重要操作
- 定义方式
-