3.2线性表定义
线性表(List):零个或多个数据元素的有限序列
例 (a1,a2,......an) a2是a1的唯一直接后继元素 an-1是an的唯一直接前驱元素
3.4线性表顺序存储结构
指用一段地址连续的存储单元依次存储线性表的数据元素
顺序存储结构代码:
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data [MAXSIZE]; //最大容量
int length;//当前长度
}SqList;
简便定义
int A[maxsize];
int n;
三要素:存储空间起始位置data,最大存储容量MAXSIZE,当前长度length
3.5顺序存储结构的插入与删除
3.5.1获取元素索引代码
int findElem(SqList L,int x)
{
int i;
for(i=0;i<L.length;++i)
{
if(x==L.data[i]){
return i;
}
}
return -1;
}
3.5.2插入操作
Status ListInsert(Sqlist &L, int p,int e)
{
int i;
if(L.length == MAXSIZE)//队满
return 0;
if(p<0 || p>L.length)//索引值错误
return 0;
for(i=L.length-1;i>p-1;i--){
L.data[k+1] = L.data[k];//插队位置之后的人往后移动
}
L.data[p]=e;//插入
L.length++;
return 1;
}
3.5.3删除操作
Status ListDelete(SqList &L, int i)
{
int k;
if(L.length==0)//队空
return 0;
if(i<0 || i>L.length-1)
return 0;
for(k=i;k<L.length-1;k++){
L.data[k]=L.data[k+1];//插队的人走了 后面的人往前移动
}
L.length--;
return 1;
}
3.5.4顺序存储结构优缺点
优点:简单逻辑关系、快速读取、能够随机访问
缺点:插入与删除操作麻烦、空间易有碎片 难以确定容量
3.6线性表链式存储结构
结点=数据域+指针域(p->data p->next)
链表中第一个结点存储位置叫做头指针(有头结点指向头结点,链表为空也存在头指针),最后一个结点指向NULL
头结点(可选)存储公共信息(例如长度),在第一元素之前
typedef int ElemType;
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
3.7单链表读取
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p;//声明一指针p
p = L->next;
j=1;
while(p && j<i)
{
p=p->next;
++j;
}
if(!p || j>i)
return 0;
*e = p->data;
return 1;
}
3.8单链表插入与删除
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while(p && j<i)//寻找插入位置
{
p = p->next;
++j;
}
if(!p || j>i)
return 0;
s = (LinkList)malloc(sizeof(Node));//申请结点
s->data = e;
s->next = p->next;//s的指针被赋值
p->next = s;
return 1;
}
Status ListDelete(LinkList *L, int i, ElemType *e)
{
int j=1;
LinkList p,q;
p = *L;
while(p->next && j<i)
{
p = p->next;
++j;
}
if(!(p->next) || j>i)//第i个结点不存在
return 0;
q = p->next;//新指针q
p->next=q->next;
*e = q->data;
free(q);//释放(删除)结点
return 1;
}
3.9单链表整表创建
1、头插法
void CreateListHead(LNode *&L, int n, int a[])
{
LNode *s,*r;
int i;
C = (LNode *) malloc (sizeof(LNode));//建立带头结点的链表
C->next = NULL;
for(i=0;i<n;i++)
{
s=(LNode *) malloc (sizeof(LNode));//新结点
s->data = a[i];
s->next = C->next;
C->next = s;
}
}
2、尾插法
void CreateListTail(LinkList *&L,int n,int a[])
{
LNode *s,*r;
int i;
C = (LNode *) malloc (sizeof(LNode));//建立带头结点的链表
C->next=NULL;
r=C
for(i=0;i<n;i++)
{
s = (LNode *) malloc (sizeof(Node));//新结点
s->data = a[i];
r->next=s;
r=r->next;//尾指针向后移动
}
r->next = NULL;
}
3.10单链表整表删除
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L->next);
while(p)//没到表尾
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL;//头结点指针域为空
return 1;
}
3.11单链表与顺序存储对比优缺点
1、存储方式
顺序存储用一段连续的存储单元依次存储线性表数据元素
单链表用一组任意的存储单元存放线性表元素
2、时间性能
查找:顺序表O(1)、链表O(n)
插入与删除:顺序表需要平均移动表长一半的元素O(n)、链表在找到元素后仅为O(1)
3、空间性能
顺序表需要预分配空间,链表不需要
3.12静态链表
用数组描述的链表,有两个数据域data与cur,cur描述next的位置
数组第一个元素的cur用来存放备用链表第一个节点的下标
数组最后一个元素的cur用来存储第一个插入元素的下标,相当于头结点
Status InitList(StaticLinkList space)
{
int i;
for(i =0;i<MAXSIZE-1;i++)
{
space[i].cur=i+1;
}
space[MAXSIZE-1].cur=0;
return 1;
}
3.12.1静态链表初始化插入与删除
int Malloc_SLL(StaticLinkList sapce)
{
int i = space[0].cur;//第一个备用空间下标
if(space[0].cur)
space[0].cur = space[i].cur;//更新备用空间位置
return i;
}
Status ListInsert(StaticLinkList L, int i,ElemType e)
{
int j,k,l;
k = MAX_SIZE-1;
if(i<1||i>ListLength(L)+1)
return 0;
j = Malloc_SSL(L);
if(j)
{
L(j).data = e;
for(l=1;l<=i-1;l++)
k = L[k].cur;
L[j].cur = L[k].cur; //把第i个元素之前的cur赋值给新元素cur
L[k].cur = j;//把新元素下标赋值给第i个元素之前元素的cur
return 1;
}
return 0;
}
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if(i < 1||i>ListLength(L))
return 0;
k =MAX_SIZE - 1;
for(j = 1;j<=i-1;j++)
k=L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L,j);
return 1;
}
3.12.3静态链表优缺点
优点:在插入和删除操作时只需要修改游标,不需要移动元素
缺点:不能解决表长难以确定的问题,不能够随机存取
意义:静态链表是为了给没有指针的高级语言一种实现单链表的方法
——————————————————————————————————————
3.13循环链表
将单链表中的终端结点指针端由空指针改为指向头结点,这种头尾相接的单链表称为循环链表
3.14双向链表
双向链表是在单链表的每个结点,再设置一个指向其前驱结点的指针域
typedef struct DulNode
{
ElemType data;
struct DuLNode *prior;
struct DulNode *next;
}DulNode, *DuLinkList;
3.14.1双向链表的插入与删除
_______________________________________________________________________________
备注:
1、结点与指针
结点是内存中一片由用户分配的存储空间,只有一个地址来表示存在,没有显式的名称。因此我们会在分配链表结点空间的时候,同时定义一个指针,来存储这片空间的地址,这个过程称为指针指向结点,并且通常用这个指针的名称来作为结点的名称。
例如:LNode *A=(LNode *)malloc(sizeof(LNode));
用户分配了一片LNode型空间,也就是构造了一个LNode型结点,这时定义一个名字为A的指针来指向这个结点,同时我们把A也当做这个结点的名字,即A既是这个新申请的结点,又是指向这个结点的指针。
若出现描述“p指向q”,此时p指代指针,因为结点不能指向结点。若出现描述“用函数free()释放p的空间”,此时p指代结点,因为指针变量存储空间由系统分配,无需用户释放。
2、例题:A和B是两个单链表,其中元素递增有序,设计算法将A和B归并成一个元素非递减有序的链表C
void merge(LNode *A,LNode *B,LNode *&C)
{
LNode *p = A->next;
LNode *q = B->next;
LNode *r;//r指向C的终端结点
C=A;//A的头结点来作为C的头结点
C->next=NULL;
free(B);
r=C;
while(p!=NULL&&q!=NULL)
{
if(p->data<=q->data)
{
r->next = p;
p=p->next;
r=r->next;
}
else
{
r->next = q;
q=q->next;
r=r->next;
}
}
r->next=NULL;
if(p!=NULL) r->next =p;
if(q!=NULL) r->next =q;
}
例题:查找链表C中是否存在一个值为X的结点 存在则删除
int findAndDelete(LNode *C,int x)
{
LNode *p,*q;
p=C;
while(p->next!=NULL)
{
if(p->next->data==x)
{
break;
}
p=p->next;
}
if(p->next==NULL)
return 0;
else
{
q=p->next;
p->next=p->next->next;
free(q);
return 1;
}
}