(数据结构C++版)(第二版)重要知识点 第二章

注:以下有任何错误,欢迎指正,谢谢指正。

第二章

2.1
%__________________________________________________________________________________________________
1.线性表是一种最基本、最简单的数据结构,数据元素之间仅具有单一的前驱和后继关系。
2.线性表的定义 线性表简称表,是n(n>=0)个具有相同类型的数据元素的有限序列
3.线性表的数据元素的个数称为线性表的长度
4.长度等于零的表称为空表,一个非空表通常记作 L = (a1,a2,a3…an) ,其中ai(1<=i<=n)称为数据元素,下脚标表示该元素在线性表中的位置或序号;a1没有前驱,an无后继。
5.线性表中数据元素的类型可以是 整型,比如说成绩单。可以是字符型,还可以是相应的结构体类型
%__________________________________________________________________________________________________
2.2
6.线性表的顺序存储结构称为顺序表
7.用数组存储顺序表
下标:| 0 | | 1 | | … | | i-2 | | i-1 | | …| |n-1| |空闲| |表的长度|
|a1| |a2| | … | |ai-1| |ai| |an|
|< ---------线性表的长度(length)--------- >|
|< -------------数组的长度(MaxSize)-------------- >|
8.设顺序表的每个元素占用c个存储单元,则第i个元素的存储地址为:LOC(I) = LOC(a1)+(i-1)*c
位置描述
9.顺序表中数据元素的存储地址是其序号的线性函数,只要确定了顺序表的起始地址(基地址),计算任意一个元素的存储地址的时间是相等的。具有这样特点的存储结构称为随机存取
10.将线性表的抽象数据类型定义在顺序表存储结构下,用C++的类实现。由于线性表的数据元素类型不确定,所以采用C++的模板机制。
模板知识的补充:

template<typename T>
返回类型  函数名(模板型参数){
函数体
}
typename 可以用class 替换
//抽象数据类型的实现
const int Maxsize = 100;//定义一个数组的长度
template <class T>//定义模板类 SeqList
class SeqList{
public:
SeqList(){length = 0;}//无参构造函数,构造一个空的顺序表
SeqList(T a[],ing n);//有参构造函数,建立一个长度为n的顺序表
~SeqList();//析构函数
int Length (){return length};//求线性表的长度
T get(int i);//按位查找,在线性表中查找第i个元素
int Locate(T x);//按值查找,在线性表中查找值为x的元素
void insert(int i,T x);//插入操作,在线性表中第i个位置插入值为x的元素
T Delete(int i);//删除操作,删除线性表中的第i个元素
void Print();//遍历操作,按序号依次输出元素
private:
T data[Maxsize];//存放顺序表的数组
int length;//线性表的长度
}

11.重点讲一下顺序表的插入操作
//如果表满了,则抛出溢出异常
//如果元素的插入位置不合理,则抛出位置异常 即插入后他还是一个线性表
//将最后一个元素直至第i个元素分别向后移动一个位置;
//将x插入第i个位置
//表长+1
%__________________________________________________________________________________________________
2.3
12.顺序表利用数组元素在物理位置(即数组下标)上的邻接关系来表示线性表中数据元素之间的逻辑关系,这使得顺序表有以下的缺点:(根本原因:静态存储分配
//插入和删除操作需移动大量的数据元素。
//表的容量难以确定,由于数组的长度必须事先确定,因此,当线性表的长度变化较大时,难以确定合适的存储规模。
//造成存储空间的碎片,数组要求占用连续的存储空间,及时存储单元数超过所需的数目,如果不连续也不能使用,造成存储空间的碎片现象。
13.为了克服顺序表的缺点,我们可以采用动态存储分配来存储线性表,也就是采用链接存储结构。
14.单链表 单链表是用一组任意的存储单元存放线性表的元素。可以连续,也可以不连续,还可以零散的分布在内存中的任意位置。
为了能正确的表示数据元素的逻辑关系,每个存储单元在存储数据元素的同时,还必须存储其后继元素所在的地址信息,这个地址信息是指针。
15.当前数据元素和该元素的后继元素的地址信息构成了数据元素的存储映像,称为结点。data-next;
16.其中data是数据域,用来存放数据元素。next是指针域,也称链域。用来存放后继者的地址
17.单链表正是通过每个结点的指针域将线性表的数据元素按逻辑次序链接在一起。由于每个结点只有一个指针域,所以称为单链表。
18.描述单链表的结点

template<typename T>
struct Node
{
T data;
Node<T> * next;
};

19.单链表中每个结点的存储地址放在其前驱结点的next域中。
第一个元素没有前驱,所以设头指针(head pointer)指向第一个元素所在结点,称为开始结点,整个链表的存取必须从头指针开始进行,因而头指针具有标识一个单链表的作用;
最后一个结点称为终端结点,指针域为空,即NULL,图中用^表示。也称尾标志。
头指针——>|a1 next|——>|a2 next|——>|a3 next|——>|a4 ^|
20.为了使空表和非空表的处理统一,通常在单链表的开始结点之前附设一个类型相同的结点,称为头结点。这样无论单链表是否为空,头指针始终指向头结点。
21.单链表的实现

template <class T>//定义模板类 LinkList
class LinkList{
public:
LinkList();//无参构造函数,建立只有头结点的空链表
LinkList(T a[],ing n);//有参构造函数,建立一个有n元素的单链表
~LinkList();//析构函数
int Length ();//求单链表的长度
T get(int i);//按位查找,在线性表中查找第i个结点的元素值
int Locate(T x);//按值查找,在线性表中查找值为x的元素
void insert(int i,T x);//插入操作,在线性表中第i个位置插入值为x的结点
T Delete(int i);//删除操作,删除线性表中的第i个结点
void Print();//遍历操作,按序号依次输出元素
private:
Node<T>*first;//单链表的头指针
}

22.通常情况下,设置一个工作指针p,当指针p指向某结点时执行相应的处理,然后将指针p修改为指向其后继结点,直到p为null为止。
23.单链表的基本操作:
%%遍历操作:A.工作指针p初始化 B.重复执行下述操作,知道p为空。B.1 输出结点的数据域;B.2 工作指针后移。

template<typename T>
void LinkList<T>::Print(){
p = first->next;
while(p!=null){
cout<<p->data;
p = p->next;
}
}

%%求单链表的长度:
A.工作指针p初始化,计数器count初始化
B.重复执行下述操作,知道p为空。B.1 工作指针后移;B.2 .count++
C.返回count

template<typename T>
int LinkList<T>::Length()
{
p= first->next;
while(p!=null){
p->next;
count++;
}
return count;
}

%%查找操作 顺序存取结构
顺序表要依次存,但位置可以随便找。单链表可以随便存,但是位置却要依次找。只能从头指针出发顺next域逐个结点往下搜索。当p指向某个结点时判断是否为第i个结点,若是则查找成功,若p查找到Null,则失败。

//按位查找
template<typename T>
T LinkList<T>::Get(int i)
{
p = first->next;count = 1;
while(p!=null&&count<i){
p= p->next;
count++;
}
if(p==null) throw"位置";
else return p->data;
}
//按值查找
template<typename T>
int LinkList<T>::Locate(T x){
p = first->next;count = 1;
while(p!=null){
if(p->data == x) return count;
p = p->next;
count++;
}
return 0;//如果退出循环则查找失败。
}

%%插入操作
A.工作指针p初始化
B.查找第i-1个结点并使工作指针p指向该结点
C.如果查找不成功,说明插入位置不合理,抛出异常
否则,C.1 生成一个新的结点s;C.2 .将s插入到结点p之后。

template<typename T>
void LinkList<T>::Insert(int i,T x){
p = first;count = 0;
while(p!=null&&count<i-1){
p = p->next;
count++;
}
if(p==null) throw 异常;
else{
s = new Node;
s->data = x;
s->next = p->next;
p->next = s;
}
}

%%构造函数
无参构造函数

template<typename T>
LinkList<T>::LinkList()
{
first = new Node;
first->next = null;
}

有参构造函数
头插法

//头插法   头插法是每次将新申请的结点插在头结点的后面
template<typename T>
LinkList<T>::LinkList(T a[],int n)
{
first  = new node;first->next = null;//初始化一个空链表
for(int i = 0;i<n;i++){
s =new node;
s->data = a[i];
s->next = first->next;
first->next = s;
}
}

尾插法

//尾插法  尾插法就是每次将新申请的结点放在终端结点的后面
template<typename T>
LinkList<T>::LinkList(T a[],int n){
first = new node;//生成头结点
r = first;
for(int i = 0;i<n;i++){
s = new node;
s->data = a[i];
r->next = s;
r = s;
}
r->next = NULL;//单链表建立完毕,将终端结点的指针域置空。
}

%%删除操作
删除操作是将单链表的第i个结点删去。
因为在单链表中,结点ai的存储地址在其前驱结点ai-1的指针域中,所以必须首先找到ai-1的存储地址p,然后令p得next域指向ai的后继结点。即把ai从链上摘下来,最后释放结点ai的空间。
//当且仅当被删结点的前驱结点p存在且p不是终端结点时,才能确定被删结点存在。
删除操作
//删除操作伪代码
工作指针初始化,计数器count初始化
查找第i-1个结点并使工作指针p指向该结点
若p不存在或者后继结点不存在,则抛出位置异常。
否则,暂存被删结点,和被删元素值。
摘链,将结点p的后继结点从链表上摘下
释放被删结点
返回被删的值。

//删除操作
template<typename T>
T LinkList<T>::Delete(int i)
{
p = first;count=0;
while(p!=null&&count<i-1){
p = p->next;
count++;
}
if(p==null||p->next==null){
throw 异常
}
else{
q = p->next;x = q->data;//暂存被删结点
p->next = q->next;
delete q;
return x;
}
}

%%析构函数

//析构函数应将单链表中结点包括头结点的存储空间释放
template<typename T>
LinkList<T>::~LinkList()
{
while(first!=null){
q = first;
first = first->next;//指向被释放结点的下一个结点
delete q;
}
}

//循环链表
在单链表中,如果将终端结点的指针域由空指针改为指向头结点。
头指针指示的循环链表
尾指针指示的循环链表
//在用头指针指示的循环链表中,找到开始结点的时间是O1,但是找到终端结点,要从头指针遍历整个链表,其时间是On,不方便;
如果用尾指针来指示链表,查找开始结点和终端结点的时间复杂度都是O1;
它们的存储地址分别是 (rear->next)->next;rear
//循环列表的优点,从任意一点出发,可以扫描到任何结点。灵活
但是没有明显的尾端,可能进入死循环。
要注重循环条件,通常判断用作循环变量的工作指针是否等于某一指定指针。如头指针或者尾指针。来判定工作指针是否扫描了整个循环链表。
//双链表的出现,是因为在循环链表中,如果想找一个结点的前驱结点,要遍历整个循环链表。
双链表就是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。 prior data next

template<typename T>
struct DulNode{
T data;
DulNode<T> *prior,*next;
}

循环双链表的对称性

//双链表的插入,需要修改四个指针
A.s->prior = p;
B.s->next = p->next;
C.p->next->prior = s;
D.p->next = s;
双链表插入操作
//双链表的删除操作
双链表的删除操作
A。(p->prior)->next = p->next;
B.。(p->next)->prior = p->prior;
执行完删除操作后释放p的存储空间。
%__________________________________________________________________________________________________
2.4
作为一般规律,若线性表需要频繁的查找,却很少进行插入和删除操作,或者其操作和数据元素在线性表中的位置密切相关时,宜采用顺序表作为存储结构,如果需要频繁的进行删除和插入,则采用链接存储结构。
//所谓空间性能是指某种存储结构所占用的存储空间的大小。首先要定义存储密度
存储密度=数据域占用的存储量/整个结点占用的存储量。
顺序表中只用存储数据,所以他的存储密度为1.如果链表的数据域占用的空间比较小,则指针的结构性开销就占去了整个节点的大部分,因而从结点的存储密度上讲,顺序表的存储空间利用率较高。但是如果事先不知道顺序表的大致长度,可能会造成空间 浪费。所以,当线性表中的元素个数比较大或者个数未知时,用链表实现。否则用顺序表的空间效率更高。
%__________________________________________________________________________________________________
2.5
静态链表
静态链表是指用数组来表示单链表,用数组下标来模拟单链表的指针。
静态链表的每个数组元素由两个域构成:数据元素data,后继元素所在的数组下标next。
由于它是利用数组定义的,所以属于静态存储分配。

//静态链表的存储结构
const int MaxSize = 100;
template<typename T>
struct SNode{
T data;.
int next;
}SList[MaxSize];

avail 是空闲链(所有空闲数组单元组成的单链表)头指针,first是静态链表头指针,为了运算方便,通常静态链表也带头结点。
在静态链表中进行插入操作时,首先从空闲链最前端摘下一个结点,将该结点插入静态链表中。
静态链表插入
s = avail;//不用申请新的结点,利用空闲链的第一个结点
avail = SList[avail].next;//空闲链的头指针后移。
SList[s].data = x;
SList[s].next = SList[p].next;
SList[p].next = s;
进行删除操作时,将被删除结点从静态链表中摘下,再插入空闲链的最前端。
静态链表删除
q = SList[p].next;

SList[p].next= SList[q].next;
SList[q].next = avail;
avail = q;

间接寻址

间接寻址是将数组和指针结合起来的一种方法。
他将数组中存储数据元素的单元改为存储指向该元素的指针,在进行插入操作时,它也需要移动,但是移动的不是元素而是指向元素的指针,虽然时间算法度仍然是O(n),但是每个元素占用的空间较大的时候,比顺序表的插入操作快的多。没有解决表长分配的问题。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值