抽象数据类型的标准格式:ADT(Abstract Date Type)
ADT 抽象数据类型名
Data
数据元素之间逻辑关系的定义
Operation
操作
endADT
线性表抽象数据类型格式
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为DataType。
其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素;
除最后一个元素an外,每一个元素有且只有一个直接后驱元素。
数据元素之间的关系是一对一的关系。
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
上面说到的Operation是最基本的操作(基操基操),对于实际问题中涉及到的关于线性表的复杂操作,完全可以用这些基本操作组合实现。
例如:求A=A∪B(将B中A不存在的元素找出,插入到A中)
/*
Name: A=A∪B
Author:Liweidong
Date: 16/07/18 15:00
Description: 求集合A与B的并集。
*/
void unionL(List *La,List *Lb){
int La_len,Lb_len,i;
ElemType e;
La_len = ListLength(La);
Lb_len = ListLength(Lb);
for(i=1;i
线性表的顺序存储结构(sequence list ->SqList)
线性表的顺序存储的结构代码
#define MAXSIZE 20
typedef int ElemType; //ElemType类型根据实际情况确定,此处假设为int。
typedef struct
{
ElemType data[MAXSIZE];
int length;
}SqList;
GetElem(L,i,*e):
将线性表L中的第i个位置元素值返回给e。
算法思路:
- 只要 i 的数值在数组下标范围内,就返回数组第 i - 1 的值即可
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
/*
Status是函数的类型,其值是函数结果状态代码,如OK等。
初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(SqList L,int i,ElemType *e)
{
if(L.length==0 || i<1 || i>L.length)
return ERROR;
*e=L.data[i-1];
return OK;
}
ListInsert(*L,i,e):
在线性表L中的第i个位置插入新元素e。
算法思路:
- 如果插入位置不合理,抛出异常;
- 如果线性表长度大于数组长度,则抛出异常或动态增加容量;
- 从最后一个元素开始向前遍历到第i个元素,分别将它们都向后移动一个位置;
- 将要插入元素填入位置i处;
- 表长加1.
/*
初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
*/
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++;
return OK;
}
ListDelete(L,i,e):
删除线性表L中的第i个位置元素,并用e返回其值。
算法思路:
- 如果删除位置不合理,抛出异常;
- 取出删除元素;
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
- 表长减1。
/*
初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
操作结果:删除线性表L中的第i个位置元素,并用e返回其值,L的长度减1.
*/
Status ListDelete(SqList *L,int i,ElemType e)
{
int k;
if(L->length == 0)
return ERROR;
if(i<1 || i > L->length)
return ERROR;
*e=L->data[i-1];
if(ilength)
{
for(k=i;klength;k++)
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
线性表顺序存储结构的优缺点
优点:
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间;
- 可以快速的存取表中任一位置的元素。
缺点:
- 插入和删除操作需要移动大量的数据(参考代码,时间复杂度为n);
- 当线性表长度变化较大时,难以确定存储空间的容量;
- 造成存储空间的"碎片"。
顺序表的链式存储结构
一些名词定义:
- 为了表示每个数据元素ai与其直接后继元素ai+1之间的
逻辑关系
, - 对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即
地址
)。 - 把存储数据元素信息的域称为
数据域
,把存储直接后继位置的域称为指针域
。 - 指针域中存储的信息称作
指针
或链
。 - 这两部分信息组成数据元素ai的存储映像,称为
结点(Node)
。 - n个结点(ai的存储映像)链结成一个链表,即为线性表的
链式存储结构
。 - 因为此链表的每个结点只包含一个指针域,所以叫做
单链表
。
头指针和头结点
头指针:链表中第一个结点的存储位置叫做头指针;
头结点:在单链表的第一个结点前附设一个结点,称为头结点。
异同点:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针;
- 头指针具有标志作用,所以常用头指针冠以链表的名字;
(标志冠名)
无论链表是否为空,头指针均不为空。头指针是链表的必要元素。(必须存在)
- 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义;
(也可存放链表的长度)
- 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了;
(方便插入、删除操作)
- 头结点不一定是链表的必须要素。
(可有可无)
线性表的链式存储的结构代码(链表:linked list -> LinkList)
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
GetElem(L,i,*e):
将线性表L中的第i个位置元素值返回给e。
算法思路:
- 声明一个指针p指向链表第一个结点,初始化j从1开始;
- 当 j < i 时,就遍历链表,让p的指针向后移动,不断的指向下一结点,j累加1;
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,返回结点p的数据。
/*
初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
操作结果:用e返回L中第i个数据元素的值。
核心思想:工作指针后移。
*/
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p;
p = L->next;
j = 1;
while(p && jnext;
++j;
}
if(!p || j>i)
return ERROR;
*e = p->data;
return OK;
}
ListInsert(*L,i,e):
在线性表L中的第i个位置插入新元素e。
算法思路:
- 声明一个指针p指向链表第一个结点,初始化j从1开始;
- 当 j < i 时,就遍历链表,让p的指针向后移动,不断的指向下一结点,j累加1
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,在系统中生成一个空结点s;
- 将数据元素e赋值给s->data;
- 单链表的插入标准语句 s->next = p->next; p->next = s;
- 返回成功。
/*
初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
操作结果:在L中第i个结点位置之前插入新的数据元素e,L的长度加1。
核心思想:工作指针后移。
*/
Status ListInsert(LinkList *L,int i,ElemType e)
{
int k;
LinkList p,s;
p = *L;
k = 1;
while (p && knext;
++k;
}
if(!p || k>i)
return ERROR;
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next =s;
return OK;
}
ListDelete(L,i,e):
删除线性表L中的第i个位置元素,并用e返回其值。
算法思路:
- 声明一个指针p指向链表第一个结点,初始化j从1开始;
- 当 j < i 时,就遍历链表,让p的指针向后移动,不断的指向下一结点,j累加1;
- 若到链表末尾p为空,则说明第i个结点不存在;
- 否则查找成功,将欲删除的结点p->next赋值给q;
- 单链表的删除标准语句 p->next = q->next;
- 将q结点中的值重新赋值给e,作为返回;
- 释放q结点;
- 返回成功。
/*
初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
操作结果:删除线性表L中的第i个位置元素,并用e返回其值,L的长度减1。
核心思想:工作指针后移。
*/
Status ListDelete(LinkList *L,int i,ElemType e)
{
int k;
LinkList p,q;
p = *L;
k = 1;
while (p->next && knext;
++k;
}
if(!(p->next) || k>i)
return ERROR;
q = p->next;
p->next = q->next;
*e = q->date;
free(q);
return OK;
}
单链表的整表创建
算法思路:
- 声明一指针P和计数器变量i;
- 初始化一空链表L;
- 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
- 循环:
- 生成一新结点赋值给p;
- 随机生成一数字赋值给p的数据域p->data;
- 将p插入到头结点与前一新结点之间。
// 头插法创建单链表
void CreateListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for(i=0; idata = rand()%100 + 1;
p->next = (*L)->next;
(*L)->next = p;
}
}
// 尾插法法创建单链表
void CreateListTail(LinkList *L,int n) {
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(i=0; idata = rand()%100+1;
r->next = p;
r = p;
}
r->next = NULL;
}
单链表的整表删除
算法思路:
- 声明一结点p和结点q;
- 将第一个结点赋值给p;
- 循环
- 将下一节点赋值给q;
- 释放p;
- 将q赋值给p;
// 删除单链表(将L置为空表)
Status ClearList(LinkList *L){
LinkList p,q;
p = (*L)->next;
while(p){
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
单链表和顺序表 存储结构优缺点
存储分配方式:
顺序表用一段连续的存储单元依次存储线性表的数据元素
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能:
查找:
顺序表:O(1)
单链表:O(n)
插入和删除
顺序表:平均需移动表长一半的元素,时间为:O(n)
单链表:在找出某位置的指针后,插入和删除时间仅为O(1)
空间性能:
顺序表需预分配存储空间,分大了,浪费;分小了,易发生溢出
单链表不需要分配空间,只要有就可以分配,元素个数也不受限制
TO BE CONTINUE