第二章 线性表
线性结构特点 : 在数据元素非空的有限集中
1 存在唯一一个第一和最后一个元素
2 除第一个元素之外,集合中的每个元素都只有一个前驱
3 除最好一个元素之外,集合中每个元素都只有一个后继
线性表:n 个数据元素的有限序列
抽象数据线性表定义
ADT list{
数据对象 D : = {......}
数据关系
基本操作{
InitList(&L); //构造一个空的线性表
DestroyList(&L); //销毁线性表
ClearList(&L); //清空线性表
ListEmpty(L); //判断线性表是否为空
ListLength(L); //求线性表长度
GetElem(L, i, &e); //用 e 返回线性表第 i 个元素
LocateElem(L, e, compare()); //返回L中第 1 个与 e 满足 compare()关系的元素
PriorElem(L, cur_e, &pre_e); //求 cur_e 的前驱元素
NextElem(L, cur_e, &next_e); //求 cur_e 的后继元素
ListInsert(&L, i, e); //在第 i 个位置之前插入元素 e
ListDelete(&L, i, &e); //删除第 i 个位置的元素,用 e 返回其值
ListTraverse(L, visit()); //依次对L的每个元素调用函数 visit()
}
}
稍复杂操作举例
合并线性表A,B
void unionList(&La, List Lb)
{
//将所有在线性表Lb中但不再La的元素插入La中
La.len = ListLength(La); //求线性表长
Lb.len = ListLength(Lb);
for (int i = 1; i < Lb.len; i++)
{
GetElem(Lb, i, e); //取表 Lb 第 i 个元素赋给 e
if (!LocatElem(La, e, equal)) //La 中不存在和 e 相同的元素则插入
ListInsert(La, ++La_len, e);
}
}
归并线性表
//将表A,B按照非递减顺序归并成一新表C
void MergeList(List La, List Lb, List &Lc)
{
//已知线性表La和Lb中的数据元素按值非递减排列
//归并La和Lb
InitList(Lc);
i = j = 1;
k = 0;
La.len = ListLength(La);
Lb.len = ListLength(Lb);
while (i <= La.len&&j <= Lb.len)
{
GetElem(La, i, ai);
GetElem(Lb, j, bj);
if (ai <= bj)
{
ListInsert(Lc, ++k, ai);
++i;
}
else
{
ListInsert(Lc, ++k, bj);
++j;
}
}
while (i <= La.len)
{
GetElem(La, i++, ai);
ListInsert(Lc, ++k, ai);
}
while (j <= Lb, len)
{
GetElem(Lb, ++j, bj);
ListInsert(Lc, ++k, bj);
}
}
线性表的顺序结构--使用数组
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
typedef struct {
ElemType *elem; //存储空间基址
int length; //当前长度
//int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位)下述测试代码不用
}SqList;
上述基本操作的实现:以顺序结构为例
/*测试结构
typedef struct{
int *data;
int length;
}SqList;
*/
SqList *InitList(void) //创建空表,分配相应内存
{
SqList *L = (SqList *)malloc(sizeof(SqList));
L->data = (int *)malloc(sizeof(int)*LIST_INIT_SIZE);
if (!L) //分配内存失败
{
printf("failure\n");
exit(0);
}
L->length = 0; //初始化表长
return L;
}
void DestroyList(SqList *L)
{
free(L->data);
free(L);
}
顺序结构下的线性表插入和删除操作比较复杂,在每插入或删除一个元素之后,该元素后面的元素要做相应的移动以保持存储结构上的相邻
int ListInsert(SqList *L, int i, int e) //插入操作 在第 i 个元素的位置上插入新元素
{
for (int p = L->length; p >= i - 1; p--)
{
L->data[p + 1] = p;
}
L->data[i - 1] = e;
L->length++; //增加当前表长
return OK;
}
int ListDelete(SqList *L, int i) //删除操作,删除第 i 个元素,返回删除的元素值
{
if (L == NULL)
{
printf("the list is null\n");
exit(0);
}
int temp = L->data[i - 1];
for (int p = i; p < L->length; p++)
{
L->data[p - 1] = L->data[p];
}
L->length--; //减少当前表长
return temp;
}
//上述操作的范例代码
Status ListInsert(SqList &L, int i, ElemType e)
{
//在顺序表L中第 i 个位置之前插入新的元素 e
// i 的合法值为 1 <= i <= ListLength(L)+1
if (i<1 || i>L.length + 1) // i 值不合法
return ERROR;
if (L.length >= L.listsize) //当前存储空间已满,增加分配
{
newbase = (ElemType *)realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if (!newbase)
exit(OVERFLOW);
L.elem = newbase; //新基址
L.listsize += LISTINCREMENT;
}
q = &(L.elem[i - 1]); //q 为插入位置
for (p = &(L.elem[L.length = 1]); p >= q; --p)
*(p + 1) = *p; //后移元素
*q = e; //插入元素
++L.length; //增加表长
return OK;
}
Status ListDelete(SqList &L, int i, ElemType &e)
{
//在顺序表 L 中删除第 i 个元素,用 e 返回其值
// i 的合法值为 1 <= i <= ListLength(L)
if (i<1 || i>L.length)
return ERROR;
p = &(L.elem[i - 1]); //p为被删除元素位置
e = *p; //被删除元素值赋给 e ,注意这些位置都是以地址表示的,得到值要用取地址运算符
q = L.elem + L.length - 1;
for (++p; p <= q; ++p)
*(p - 1) = *p;
--L.length;
return OK;
}
//NOTE 在对顺序结构的线性表进行插入删除操作时较为复杂,移动元素的次数过多
//其解决方法可以采用链式结构的线性表,或者对顺序结构做处理,增加标记元素,但是这样会造成逻辑结构与存储结构的不一致
//扩展如静态链表
//在上例中用到的LocateElem()操作
int LocateElem(SqList *L, ElemType e, Status(*compare)(ElemType, ElemType))
{
//在顺序表 L 中查找第 1 个值与 e 满足 compare() 元素的位序
//若找到返回其在 L 中的位序,否则返回 0
i = 1; // i 的初值位第一个元素的位序
p = L.elem; //p 的初值为第一个元素的存储位置
while (i <= L.length && !(*compare)(*p++, e))
++i;
if (i <= L.length)
return i;
else
return 0;
}
//MergeList的第二种写法
void MergeList(SqList La, SqList Lb, SqList *Lc)
{
//已知线性表La和Lb中的数据元素按值非递减排列
//归并La和Lb
pa = La.elem;
pb = Lb.elem;
Lc.listsize = Lc.length = La.lengt + Lb.length;
pc = Lc.elem = (Elemtype*)malloc(Lc.listsize * sizeof(ElemType));
if (!Lc.elem)
exit(OVERFLOW);
pa.last = La.elem + La.length - 1;
pb.last = Lb.elem + Lb.length - 1;
while (pa <= pa.last&&pb <= pb.last) //归并
{
if (*pa <= *pb)
*pc++ = *pa++;
else
*pc++ = *pb++;
}
//插入剩下元素
while (pa <= pa.last)
*pc++ = *pa++;
while (pb <= pb.last)
*pc++ = *pb++;
}
NOTE 以线性表合并进行集合的各种运算最好先进行排序,这样可以减少部分算法的时间复杂度
顺序表的链式结构--使用指针索引
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
//GetElem 在链表中的实现
//由于链表的每个结点数据在存储位置上没有必然联系,只能通过索引指针来进行元素索引
int GetElem(LinkList L, int i)
{
//L为带头结点的单链表头指针
//存在第 i 个元素时,返回其值,否则返回 ERROR
LinkList p = L->next;
int count = 0;
if (i > L.length)
{
printf("ERROR");
return ERROR;
}
while (count < i)
{
p = p->next;
count++;
}
return p->data;
}
//范例代码
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;
e = p->data;
return OK;
}
//链表的插入与删除
//由于链表是由指针索引,插入与删除操作只需断开结点后再续接即可,不需要对大量元素进行移动,时间复杂度较低
int listInsert(LinkList L, int i, int e)
{
LinkList P = L->next;
LinkList q = L;
if (i > L.length)
return ERROR;
int count = 1;
while (count < i)
{
p = p->next;
q = q->next;
count++;
}
//结束循环后 p 指向第 i 个位置 ,q 指向 i - 1 的位置,初始 count = 0 时该条件会使使指针溢出
/*在第 i 个位置之前插入元素 e -- 需要对 q 指针做操作
LinkList temp = (LinkList)malloc(sizeof(LNode)); //申请新结点
if (!temp)
exit(0);
temp->data = e;
temp->next = NULL;
q->next = temp; //续接指针
temp->next = p;
*/
/*在第 i 个位置之后插入元素 e -- 需要对 p 指针操作
LinkList temp = (LinkList)malloc(sizeof(LNode)); //申请新结点
if (!temp)
exit(0);
temp->data = e;
temp->next = NULL;
temp->next = p->next;
p->next = temp;
*/
return OK;
}
int listDelete(LinkList L, int i) //删除第 i 个结点
{
LinkList p = L->next;
LinkList q = L;
//删除位置不合理
if (p == NULL)
return ERROR;
else if (i > L.length)
return ERROR;
int count = 1;
while (count < i)
{
p = p->next;
q = q->next;
count++;
}
q->next = p->next;
free(p);
return OK;
}
//esp 上述代码中的L.length表示 L 的长度,在该链表结构中并没有存储其值,在此处仅做形象表示,
//可以通过函数计算或则在创建的时候存储在头节点的数据域中
//范例代码
Status ListInsert(LinkList &L, int i, ElemType e)
{
//在带头结点的单链表L中第 i 个位置之前插入元素 e
ListLink p = L;
int j = 0;
while (p&&j < i - 1)
{
p = p->next;
j++;
}
if (!p || j > i)
return ERROR;
LinkList s = (LinkList)malloc(sizeof(LNode));
if (!s)
exit(0);
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
Status ListDelete(LinkList &L, int i, ElemType &e)
{
//在带头结点的单链表L中删除第 i 个元素并用 e 返回其值
LinkList p = L;
int j = 0;
while (p->next&&j < i - 1) //寻找第 i 个结点,并令 p 指向其前驱
{
p = p->next;
j++;
}
if (!p->next || j > i - 1)
return ERROR;
LinkList q = p->next;
p->next = q->next;
e = q->data;
free(q);
return OK;
}
//链表是一种动态存储结构,使用时要分配相应内存,销毁时要对相应内存进行释放
//由于是动态结构,相较于顺序结构的链表,在存储上具有优势,可以任意的添加或删除元素
//下面提供几种创建链表的方法
void creatListFIFO(LinkList *L) //先进先出
{
*L = (LinkList)malloc(sizeof(LNode));
if (!(*L))
exit(0);
(*L)->next = NULL;
LinkList current = NULL, last = NULL;
int e;
while (scanf("%d", &e) == 1)
{
current = (LinkList)malloc(sizeof(LNode));
if (!current)
exit(0);
else
{
current->next = NULL;
current->data = e;
if ((*L)->next == NULL)
{
(*L)->next = current;
last = current;
}
else
{
last->next = current;
last = current;
}
}
}
if (last)
last->next = NULL;
}
LinkList creatListFIFO(void) //先进先出,无参数
{
LinkList L = (LinkList)malloc(sizeof(LNode));
if (!L)
exit(0);
L->next = NULL;
LinkList current = NULL, last = NULL;
int e;
while (scanf("%d", &e) == 1)
{
current = (LinkList)malloc(sizeof(LNode));
if (current != NULL)
{
current->data = e;
if (L->next == NULL)
{
L->next = current;
last = current;
}
else
{
last->next = current;
last = current;
}
}
}
if (last)
last->next = NULL;
return L;
}
void creatListFILO(LinkList *L) //先进后出,有参数
{
*L = (LinkList)malloc(sizeof(LNode));
if (!(*L))
exit(0);
(*L)->next = NULL;
LinkList current = NULL;
int e;
while (scanf("%d", &e) == 1)
{
current = (LinkList)malloc(sizeof(LNode));
if (current != NULL)
{
current->data = e;
current->next = (*L)->next;
(*L)->next = current;
}
}
}
LinkList creatListFILO(void) //先进后出,无参数
{
LinkList L = (LinkList)malloc(sizeof(LNode));
if (!L)
exit(0);
L->next = NULL;
LinkList current = NULL;
int e;
while (scanf("%d", &e) == 1)
{
current = (LinkList)malloc(sizeof(LNode));
if (current != NULL)
{
current->data = e;
current->next = L->next;
L->next = current;
}
}
return L;
}
//示例代码
void CreatList(LinkList &L, int n)
{
//逆位序输入n个元素值,建立带头结点的单链表
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
for (int i = n; i > 0; i--)
{
LinkList p = (LinkList)malloc(sizeof(LNode));
scanf(&p->data);
p->next = L->next;
L->next = p;
}
}
//归并有序链表
void MergeList(LinkList &La, LinkList &Lb, LinkList &Lc)
{
LinkList pa = La->next;
LinkList pb = Lb->next;
LinkList pc = La;
Lc = pc;
while (pa&&pb)
{
if (pa->data <= p->data)
{
pc->next = pa;
pc = pa;
pa = pa->next;
}
else
{
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
pc->next = pa ? pa : pb;
free(Lb);
}
循环链表--将最后一个结点接入头结点上形成一个环
双向链表
//双向链表的存储结构--在每个结点里增添了一个指向前驱的指针
typedef struct DuLNode {
ElemType data;
struct DuLNode *prior;
struct DuLNode *next;
}DuLNode,*DuLinkList;
NOTE 在双向链表的计算表长,取元素和检索元素之类的操作与单链表基本一致,但是插入删除等操作设计了两个指针,就有很大不同
//示例代码
Status ListInsert(DuLinkList &L, int i, ElemType e)
{
//在带头结点的双循环链表中第 i 个位置之前插入元素 e
// i 的合法值为 1 <= i <= 表长 + 1
if (!(p = GetElem(L, i))) // 在 L 中确定插入位置
return ERROR; //p = NULL,插入位置不合法
if (!(s = (DuLinkList)malloc(sizeof(DuLNode))))
return ERROR;
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
Status ListDelete(DuLinkList &L, int i, ElemType e)
{
//删除带头结点的双循环链表的第 i 个元素,i 的合法值为 1 <= i <= 表长
if (!(p == GetElem(L, i)))
return ERROR;
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
return OK;
}
单链表的应用--一元多项式的表示及相加
在计算机中表示一元多项式可由一个线性表来表示 P = (p0, p1, p2...pn);
每一项的指数 i 隐含在其系数 pi 的序号里
假设Q是一元m次多项式,同样可以用线性表Q表示:Q = (q0, q1, q2...qm);
一般,设 m < n 则两个多项式相加有 Rn = Pn + Qm;
R = (p0 + q0, p1 + q1, p2 + q2...pm + qm, ...pn);
抽象数据类型一元多项式的定义如下
ADT Polynomial{
数据对象
数据关系
基本操作
creatPolyn(&P,m) //输入 m 项的系数和指数,建立一元多项式
DestroyPolyn(&P) //销毁一元多项式
PrintPolyn(P) //打印一元多项式
PolynLength(P) //返回一元多项式的项数
AddPolyn(&Pa,&pb) //相加并销毁 Pb
SubtractPolyn(&Pa,&Pb) //相减并销毁 Pb
MultiplyPolyn(&Pa,&Pb) //相乘并销毁 Pb
}