线性表
由n(n>=0)个数据特性相同的元素构成的有限序列称为 线性表;(n=0,是空表)
相邻元素之间存在着序偶关系;同一线性表中的元素必定具有相同的特性;
顺序表
(1)线性表的顺序表示是指用一组 地址连续的存储单元 依次存储线性表的数据元素
这种表示称为线性表的顺序存储结构或顺序映像 ,称这种存储结构的线性表为顺序表(SequentialList)
(2)线性表的顺序存储结构是 一种随机存取 结构
// …顺序表的存取结构…
#define MAXSIZE 100
typedefy struct
{
ElemType *elem; //存储空间的基地址 (用指针的方式定义创建了数组 这是基地址)
int length ; //说明一下长度 ,表示当前数据元素的个数;
}sqList; // 顺序表的结构类型为 SqList (数据类型)
说明: 因为C 语言中数组的下标是从0 开始的而位置序号是从1 开始的
数据元素 a1 a2 a3 , an 依次存放的数组的
elem[0] elem[1] elem[2] elem[length-1] 中
eg : (一元多项式的顺序存储结构)
#define MAXSIZE 100 //定义一个表长可能达到的最大长度
typedef struct
{
float coef ; // 系数
int expn ; // 指数
}Polynomial ; 多项式
typedef strcut
{
Polynomial *elem; //结构体类型的指针
int length ;
}Sqlist ;
(3)顺序表的基本操作
初始化
思想:就是构造一个空的 顺序表
算法步骤 :
- 先为顺序表L动态分配一个 预定义大小的数组空间,使elem 指向 这段空间的基地址
- 将表的当前长度设为0;
status InitList(SqList &L)
{
L.elem = new ElemType[MAXSIZE];
if (! L.elem) exit (OVERFLOW); //分配地址失败 返回 -2;
L.length = 0; //当前的表长为0 空表
return OK;
}
取值
思想:根据顺序表的存储结构 直接定位 elem[i-1] 单元存储的地 i 个数据元素
算法步骤:
- 判断 给的位置是否合理 (1<= i <=length),如不合理 返回 ERROR;
- 若i 合理则将第 i 数据元素L。elem[i-1] 赋给参数 e
status GetElem(SqList L ,int i ,ElemType &e)
{
if (i<1 || i> length) return ERROR;
e = L .elem[i-1];
return OK;
}
查找
思想:查找第一个 与 e 相等的元素 返回元素的位置序号 若失败则返回0 ;
算法步骤:
- 从第一个元素起 ,依次和e 比较 若找到与 e 相等的元素L.elem[i] ,返回 i +1;
- 若查找这个顺序表都没找到 则返回0;
int LocateElem(SqList L,ElemType e)
{
for( i=0 ; i < Length; i ++)
if (L.elem[i] == e ) return i+1;
return 0;
}
插入
思想:在第i 个位置插入一个元素时 需要从最后一个元素即第n 个元素开始,依次向后移动一个位置,
直至第 i 个元素(共 n-i+1 个)
算法步骤:
- 判断 i 是否合法,(i值合法 1 <=i<= n-+1)
- 判断顺序表的存储空间 是否已满,若满足则返回ERROR.
- 将第 n 个元素至 第 i 个元素依次向后移动一个位置 空出第 i 个位置(i= n+1 时无需移动)
- 将要插入的元素 e 加入到第 i 个位置
- 表长加一
status ListInsert(Sqlist &L ,int i, Elemtype e)
{
if (i<1 || i>length +1) return ERROR;
if (L.length == MAXSIZE) return ERROR ;
for (j=L.length-1; j>=i-1; j --) // 注意一下下标与位置的关系
L.elem[j+1] = L.elem[j];
L.elem[i-1] = e;
++ L.length;
return OK;
}
删除
思想: 一般情况下,删除第 i (1<=i <=n)个元素 时需要将第 i+1 个至第n 个元素( 共n-i 个元素)
依次向前移动 一个位置 (i=n 时无需移动)
算法步骤:
- 判断 i 是否合法
- 移动第 i 至第 n 个元素依次向前移动 一个位置( i= n 时无需移动)
- 表长减一
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;
}
链表
用 一组任意的存储单元 存储线性表中的数据元素;(这组存储单元可以是连续的,也可以是不连续的)
为了表示每一个数据元素 ai 和其直接后继数据元素 a i+1 的关系的逻辑关系;
对于数据 ai 来说,除了存储器本身的信息外,还需存储一个能指示下一个位置的信息,
这两个部分组成数据元素的ai 的存储映像, 称为结点。
存储数据元素的信息的称为数据域 ;存储直接后继存储位置的域为指针域
每个结点只包含一个指针域,故 称为 线性链表或单链表
线性表中元素逻辑顺序 ,而不是它在存储器中的实际位置;
// 单链表的存储结构
typedef struct LNode
{
ElemType date ;
struct LNode *next ;
}LNode ,*LinkList ; //LinkList 为指向结构体LNode 的指针类型;
在这里LinkList 和 LNode * 是一样的
在C++ 中引用时 LinkList & 方便使用 不用想c 语言中的指针 害的在 用指针引用内容;
-
区分指针变量和结点变量两个不同的概念,若定义 LinkList p 或 LNode * p ;
则 p 是指向某结点的指针变量,表示该结点的地址,而 *p 为对应的结点变量 表示该结点的名称, -
增加头结点的作用
(1)便于首元结点的处理 使其没有特殊性
(2)便于空表和非空表的统一处理
(当链表不设头结点的时假设 L 为单链表的头指针,指向首元结点当单链表的长度n =0 为空表时,L指针为空,可记为 L==NULL);
增加头结点后无论链表是否为空,头指针都是指向头结点的非空指针 若为空表则头结点的指针域为非空指针可设为L->next ==NULL; -
要想取得第i 个数据元素必须从头结点出发顺链去找也称顺序存储结构
单链表的初始化
思想:构建一个带有头结点的空表
算法步骤:
- 生成新节点作为头结点,用头指针指向头结点;
- 头结点的指针域置空
Status InitList (LinkList &L)
{
L = new LNode;
L->next = NULL;
return OK;
}
取值
给定的结点位置序号i 从链表的首元结点出发顺链去找 逐个结点访问
算法步骤
- 用指针 p 指向首元结点,用 J 来做计数器初值为 1 ( j 和 i 比较 )
- 从首元结点开始,只要指向当前的的结点指针 p 不为空并且没有达到序号为 i 的结点 ,则进行以下操作
(1)p 指向下一个结点
(2)计数器加1 ;
status GetElem( LinkList L,int i ELemType &e)
{
p = L->next ;j=1;
while (p && j < i)
{
p = p->next ;
++j;
}
if ( !p || j > i) return ERROR; // i >n 或 i <=0
e = p->date ;
return OK;
}
查找
从链表的首元结点出发,依次比较结点值和给定值,返回结果;
算法步骤:
- 用指针 P 指向首元结点
- 只要当前结点 的指针不为空,如果和 e 不相等 p 指向下一个结点;
- 若成功 返回p 地址值 否则 返回 p 的值为 NULL;
LNode *LocateElem ( LinkList L ,ElemType e )
{
p = L->next ;
while( p && p->date!=e)
p =p->next;
return p;
}
插入
将值为e 的新结点 插入到标的第 i 个结点位置上,及插入到 ai-1 和 ai 之间;;
算法步骤;
- 查找到 结点ai-1 并有指针 p 指向该结点;
- 生成一个新结点 *s ;
- 将新结点*s 的数据域置为 e ;
- 将新结点的指针域指向 ai;
- 将结点* p 的指针域指向新结点;
Status ListInsert(Linklist &L ,int i, ElemType e)
{
p = L , j =0;
while (p && (j< i-1))
{
p = p-> next ;
j++;
}
if ( !p || j<i-1) return ERROR; // i > n+1 或者 i<1;
s= new LNode ; // 生成 新结点 *s
s->date = e; // 将**结点 *s** 的数据域 置为 e;
s -> next = p ->next ;
p ->next = s;
return OK;
}
总结 : 如果表中有 n 个结点,则合法的操作中 合法的位置 有 n +1 个,1 <=i <=n+1; n+1 时为链表尾部
删除
给定位置 i 顺链寻找 首先找到该位置的首元结点;
算法步骤:
- 查找 结点ai-1 并由指针 p 指向该结点;
- 临时保存待删结点ai 的地址 在 q 中已备释放
- 将结点*p 的指针域 指向ai 的直接后继结点
- 释放结点ai 的空间;
status ListDelete (LinkList &L, int i)
{
p= L; j=0;
while( (P->next) &&( j < i-1)
{
p=p->next;j++;
}
if (! (p->next )||(j>i-1) ) return ERROR; //当 i > n或 i<1时,删除位置不合理;
q =p->next ;
p->next = q->nxet ;
delete q;
return OK;
}
单链表的创建
(1)用前插法创建单链表;
这里从(a1 a2 a3 a4 a5 …an )插入;
结果是( an,an-1,…a3, a2 a1)输出
思想: 是将新结点逐个插入链表的头部(头结点之后)每次申请一个新结点,
读入相应的数据元素,然后将新结点插入到头结点之后
算法步骤:
- 创建一个只有头结点的空表;
- 输入元素的赋给新节点*p 的数据域;
- 将新结点* p 插入到头结点之后
void CreatList_H (LinkList &L ,int n)
{
L =new LNode;
L->next = NULL;
for (i =0 ;i<n;i++)
{
p =new LNode;
cin >> p->date;
p ->next =L->next ; //将新结点* p 插入到结点之后
L->next =p; // L 指向头结点 一直不动作为一个转接点
}
}
(2)用尾插法创建链表
这个是顺序的
这里从(a1,a2,a3…an)
输出 (a1,a2 a3…an)
思想: 为了使新节点能插入到表尾需要增加一个尾指针 r 指向链表的尾结点
算法步骤:
- 创建一个只有头结点的空链表;
- 尾指针初始化,指向头结点
- 输入n 个元素,循环 n 次
(1) 生成一个新结点 *p;
(2)输入元素赋值给新结点 *p;
(3)将新结点 *p 插入到尾结点 *r 之后
(4)尾指针 r 指向新的尾结点 *p
void CreateList_R(LinkList & L ,int n)
{
L= new LNode;
L->next =NULL;
r= L;
for ( i=0;i<n;i++)
{
p= new LNode;
cin >> P->date;
p ->next = NULL;
r->next =p;
r=p;
}
}
循环链表
它的特点是,表中的最后一个结点的指针域指向头结点,整个链表形成一个环,
从表中任意一个元素出发可以找到表中其他结点。
循环链表与单链表的基本操作一致,差别当链表遍历时,判别当前指针 p 是否指向表尾结点的终止条件不同,
(1)单链表中 : 判别条件是 p!= NULL 或 p->next != NULL;
(2)循环链表中判别条件: p!= L 或 p-> next != L;
循环条件:p!=NULL =》 p!=L
p->next!=NULL =》 p->next!=L
在某些情况下,循环链表中设尾指针而不设头指针 例如: 将两个线性表合并,仅需将第一个表的尾指针指向第二个表的第一个结点,第二个表的尾指针指向第一个表的头结点,然后释放第二个表的头结点,
LinkList Connect(LinkList Ta,LinkList Tb)
{//假设Ta、Tb都是非空的单循环链表
p=Ta->next; //①p存表头结点
Ta->next=Tb->next->next; //②Tb表头连结Ta表尾
deleteTb->next; //③释放Tb表头结点
Tb->next=p; //④修改指针
return Tb;
}
上述时间复杂度为O(1);
顺序表和单链表的比较
双向链表
这里 的双向即是 存在两个指针域,在单链表中,查找直接后继
结点的执行时间为O(1), 而查找直接前驱的执行时间为O(n)
为了克服单链表的这种单向性的缺点,可利用双向链表;
(1)在***C 语言上***的算法定义,可描述为
// 双向链表的存储结构
Typedef struct DuLNode
{
ElemType date;
struct DuLNode *P;
struct DuLNode * next;
} DuLNode ,*DuLinkList;
(2)和单链表相似,双向链表也可以有循环链表,
(3)在循环链表中若d 为指向表中的某一节点的指针;(d 为DuLinkList型的变量)
显然有:
d ->next ->prior = d->prior ->next =d;
双向链表的插入
在插入结点时,需要修改四个指针,
Status ListInsert_DuL(DuLinkList &L,int i,ElemType e)
{ //在带头结点的双向链表L中第i 个位置插入元素 e
if ( !(p=GetElemP_DuL(L,i)) ) // 找到第i 个元素的位置指针 P
return ERROR;
s=new DuLNode;
s->data=e;
//进行四步的指针操作
s->prior=p->prior; //对应第一步
p->prior->next=s; //对应 第二步
s->next=p; //第三步
p->prior=s; //第四步 ;
return OK;
}
双向链表的删除
只需要修改两个指针;
Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e)
{
if (!(p=GetElemP_DuL(L,i)))
return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
delete p;
return OK;
}