目录
线性表
由n个数据特性相同的元素,例如书籍信息(结构体),构成的有限序列。
何为“线性”:
除第一个和最后一个数据元素外,每个数据元素只有一个前驱和后驱数据元素。
顺序存储结构——顺序表
通过数组的方式,将线性表的数据元素存储在一块连续地址空间的内存单元中,从而得到顺序表的特点——逻辑上相邻的数据元素,其物理次序也是相邻的。
存储地址 | b | b+l | ... | b+i*l | ... | b+n*j |
存储内容 | a0 | a1 | ... | ai | ... | an |
定义结构体如下:
#define MaxSize 100
//顺序表的静态数组实现方法
typedef struct
{
DataType list[MaxSize]; //数据存储在数组中
int size; //数据元素个数
}SeqList;
//动态分配顺序存储结构(优先级高)
typedef struct
{
ElemType *elem; //存储空间基址
int length; //当前长度
}SqList;
下面以动态分配方法定义的结构体为基础来实现顺序表的操作。
顺序表操作实现
1.初始化操作
typedef int Status;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
Status InitList(SqList &L)
{//初始化
L.elem = new ElemType[MaxSize]; //请看注解
if(!L.elem) return OVERFLOW;
L.length = 0;
return OK;
}
注:通常来说,当在局部函数中new出一段新的空间,该段空间在局部函数调用结束后仍然能够使用,可以用来向主函数传递参数。另外需要注意的是,new的使用格式,new出来的是一段空间的首地址。所以一般需要用指针来存放这段地址。
2.取值操作
Status GetElem (SqList L, int i, ElemType &e)
{
if ( i<1 || i>L.length ) // i 不在有效位置
return ERROR;
e = L.elem[i-1];
return OK;
} // 时间复杂度 O(1) 取决于i
3.查找操作
int LocateElem( SqList L, ElemType e)
{//在顺序表L中查找值为 e 的数据元素,返回其序号
for( i = 0; i < L.length; i++ )
if ( L.elem[i] == e )
return i+1;
return 0;
}//时间复杂度 O(n)
4.插入操作
Status ListInsert (SqList &L, int i, ElemType e)
{
if(i<1 || i >L.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;
}//时间复杂度 O(n)
5.删除操作
和插入操作差不多,只需要关注超界的L.length+1->L.length即可,其他反向操作就不多水啦
顺序表的优缺点
顺序表的优点
- 逻辑相邻,物理相邻
- 可随机存取任一元素
- 存储空间使用紧凑
顺序表的缺点
- 插入、删除操作需要移动大量的元素
- 预先分配空间需按最大空间分配,利用不充分
- 表容量难以扩充
链式存储结构——链表
链式存储结构存储线性表数据元素的方式是,把存储有数据元素的结点用指针域构造成链。
单链表
结点:数据域|指针域
数据域:元素本身信息
指针域:指示直接后继的存储位置
特点:
- 不需预先分配空间
- 指针占用额外存储空间
- 不能随机存取,查找速度慢
表示方法
typedef struct LNode
{
ElemType data; // 数据域
struct LNode *next; // 指针域
}LNode,*LinkList;
带头结点的单链表
不带头结点的单链表
空表条件: L == NULL 表尾条件: P->next == NULL
带头结点的单链表
空表条件: L->next == NULL 表尾条件: P->next == NULL
单链表操作实现
1.初始化操作
Status InitList(LinkList &L)
{
L = new LNode; //生成头结点
L->next = NULL; //头结点的指针域置空
return OK;
}
2.取第i个数据元素的操作
Status GetElem (LinkList L, int i, ElemType &e)
{// L指向头结点
int j=1;
LinkList p=L->next; // p指向首元结点
while(j<i && P)
{
p=p->next;
++j;
}
if(!p || j>i)
return ERROR;
// i>n 或 i<=0 所指位置超界
e=p->data;
return OK;
} // 时间复杂度O(n)
3.查找操作
LNode *LocateElem (LinkList L, ElemType e)
{// L指向头结点
p=L->next; // p指向首元结点
while(p && p->data != e )
p=p->next;
return p;
// 查找成功返回值为e的结点地址,失败p为NULL
} // 时间复杂度O(n)
4.插入操作
Status ListInsert (LinkList L, int i, ElemType e)
{// L指向头结点
p=L;j=0;
while(p && j<i-1) //p是要插入结点的前驱结点
{
p=p->next;
++j;
}
if (!p||j>i-1) return ERROR; // i>n+1 or i<1
s=new LNode; //(LinkList)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}//时间复杂度O(n)
5.删除操作
Status ListDelete (LinkList &L, int i)
{
p=L; j=0;
while( p->next && j<i-1 )
{
//查找第i-1个结点,p->next是要删除的结点
p=p->next;
++j;
}
if( !p->next || j>i-1)
//当 i>n or i<1时 删除位置不合理
return ERROR;
q=p->next;
p->next=q->next;
delete q; // 释放删除结点的空间
return OK;
}
建立带头结点的单链表
void CreateList_H(LinkList &L, int n)//前插法
{
L= new LNode;
L->next=NULL; // 先建立一个带头节点的空链表
for(i=0; i<n; i++)
{
p=new LNode; // 生成新结点
cin >> p->data; // 给数据域赋值
p->next=L->next;
L->next=p; // 新结点插在头结点后
}
} //时间复杂度O(n)
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->data; // 给数据域赋值
p->next=NULL;
r->next=p; // 新结点插在r结点后
r=p; // r=r->next;
}
} //时间复杂度O(n)
顺序(单)链表
循环链表:
表尾条件:p->next==H
有头结点的空表条件:H->next == H
对循环链表,有时不给出头指针,而给出尾指针可以更方便的找到第一个和最后一个结点。
(借用一下老师的ppt)
双向链表![](https://img-blog.csdnimg.cn/6245697f608647d5b0e61601b4c14e99.png)
typedef struct Node
{
DataType data;
struct Node *next;
struct Node *prior;
}DLNode;
实现操作的代码省略(主要是p->prior->next之类的运用,虽然我习惯用(--p)->next)
顺序表和链表的比较
应用(算法思路)
放在社区学习活动(找不到就是我还没放)
参考资料:
1. 数据结构——使用C语言(第5版)
2. 数据结构——C++实现
3. 数据结构与算法 ppt(线性表) 张合生