线性表是一种最简单的线性结构
线性结构的基本特征为:线性结构是一个数据元素的有序(次序)集
1.集合中必存在唯一的一个“第一元素”;
2.集合中必存在唯一的一个“最后元素” ;
3.除最后元素在外,均有唯一的后继;
4.除第一元素之外,均有唯一的前驱
线性表:n个数据元素组成的有限序列。表示为(a1,a2,…,ai,ai+1,…,an)
一:线性表的顺序存储方式:
线性表的顺序存储方式定义为:用一组地址连续的存储单元依次存放线性表中的数据元素以“存储位置相邻”表示有序对<ai-1,ai>
线性表的起始地址称作线性表的基地址
即:LOC(ai) = LOC(ai-1) + C
LOC(ai) = LOC(a1) + (i-1)×C
所有数据元素的存储位置均取决于第一个数据元素的存储位置
线性表的表示方法:
#define LIST_INIT_SIZE 80 // 线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct {
ElemType *elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量,(以sizeof(ElemType)为单位)
} SqList; // 俗称 顺序表
算法1:(利用已有的函数实现)
假设:有两个集合 A 和 B 分别用两个线性表 LA 和 LB 表示,即:线性表中的数据元素即为集合中的成员。
现要求一个新的集合A=A∪B。
上述问题可演绎为:要求对线性表作如下操作:
扩大线性表 LA,将存在于线性表LB 中而不存在于线性表 LA 中的数据元素插入到线性表 LA 中去。
操作步骤:
1. 从线性表LB中依次察看每个数据元素;
GetElem(LB, i)→e
2. 依值在线性表LA中进行查访;
LocateElem(LA, e, equal( ))
3. 若不存在,则插入之。
ListInsert(LA, n+1, e)
例: LA=(8,5,11,3)
LB=(6,8,2,9,20,15,11)
则: LA=(8,5,11,3,6,2,9,20,15}
void union(List &La, List Lb) {
La_len = ListLength(La); // 求线性表的长度
Lb_len = ListLength(Lb);
for (i = 1; i <= Lb_len; i++) {
GetElem(Lb, i, e); // 取Lb中第i个数据元素赋给e
if (!LocateElem(La, e, equal( )) )
ListInsert(La, ++La_len, e); // La中不存在和 e 相同的数据元素,则插入之
}
} // union
有序表:若线性表中的数据元素依值非递减或非递增有序排列,
即 ai≥ai-1 或 ai≤ai-1(i = 2,3,…, n),则称该线性表为有序表。
比如:1,3,5,6,8,10,20,23,
算法2:
已知线性表La和Lb中的数据元素按值非递减有序排列,现要求La和Lb归并为一个新的线性表Lc,且Lc中的数据元素仍按值非递减有序排列
实现步骤:
1.分别从La和Lb中取得当前元素ai和bj;
2.若ai≤bj,则将ai插入到Lc中,否则将bj插入到Lc中。
例: La=(3,5,8,11)
Lb=(2,6,8,9,11,15,20)
则: Lc=(2,3,5,6,8,8,9,11,11,15,20}
void MergeList(List La, List Lb, List &Lc) // 本算法将非递减的有序表 La 和 Lb 归并为 Lc
{
initList(Lc); // 构造空的线性表 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; }// 将 ai 插入到 Lc 中
else { ListInsert(Lc, ++k, bj); ++j; }// 将 bj 插入到 Lc 中
}
while (i<=La_len) // 当La不空时
{ GetElem(La, i++, ai); ListInsert(Lc, ++k, ai);} // 插入 La 表中剩余元素
while (j<=Lb_len) // 当Lb不空时
{ GetElem(Lb, j++, bj); ListInsert(Lc, ++k, bj); } // 插入 Lb 表中剩余元素
}
算法3:直接实现有序表的合并操作,不用已知函数。
void MergeList(List La, List Lb, List &Lc) {
pa=La.elem; pb=Lb.elem;
Lc.listsize=Lc.length=La.length+Lb.length; //Lc的长度赋值
Pc=Lc.elem=(elemtype *)malloc(Lc.listsize*sizeof(elemtype));
If(!Lc.elem) exit(overflow); //给Lc动态分配存储空间
pa_last = La.elem+La.length-1; //记录La的最后元素存放位置
pb_last = Lb.elem+Lb.length-1 ; //记录Lb的最后元素存放位置
while (pa <= pa_last) && (pb<= pb_last)
{ if(*pa<=*pb) *pc++=*pa++;
eles *pc++=*pb++;}//归并操作
while (pa <= pa_last) *pc++=*pa++; // 若 La 不空
while (pb<= pb_last) *pc++=*pb++; // 若 Lb 不空
}
算法4:线性表的结构初始化
Status InitList_Sq( SqList& L ) {
L.elem = (ElemType*) malloc (LIST_INIT_SIZEsizeof (ElemType));
if (!L.elem) exit(OVERFLOW);
L.length = 0;
L.listsize = LIST_INIT_SIZE;
return OK;
} // InitList_Sq算法时间复杂度:O(1)
算法5:输出线性表内容
Void Printlist( SqList L ) {
int i;
printf("\n*********************");
printf("\nThe element of the sqlist are:\n");
for(i=0;i<L.length;i++)
printf("%d ",L.elem[i]);
printf("\n*********************\n");
return;
}//算法时间复杂度:O(L.length)
算法6:线性表的插入元素
Status ListInsert_Sq(SqList &L, int i, ElemType e)
{ // 在顺序表L的第 i 个元素之前插入新的元素e,
// i 的合法范围为 1≤i≤L.length+1
if (i < 1 || i > L.length+1) 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表尾元素的位置
*(p+1) = *p; // 插入位置及之后的元素右移
*q = e; // 插入e
++L.length; // 表长增1
return OK;
} // ListInsert_Sq 算法时间复杂度为: O( ListLength(L) )
具体插入过程见下图
算法7:线性表的删除元素
Status ListDelete_Sq (SqList &L, int i, ElemType &e) {
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; // 表长减1
return OK;
} // ListDelete_Sq
具体插入过程见下图
顺序存储结构的优缺点:
优点:
1:逻辑相邻,物理相邻:
2:可随机存取任一元素
3:存储空间使用紧凑
缺点::
1:插入、删除操作需要移动大量的元素
2:预先分配空间需按最大空间分配,利用不充分:
3:表容量难以扩充
附:代码参考
#define list_size 15
#define increment 5
#define ok 1
#define overflow -1
#define error -2
#include "stdlib.h"
#include "stdio.h"
typedef struct{ int *elem;
int length;
int listsize;}sqlist;
int initlist(sqlist *L)
{ L->elem=(int *)malloc(list_size*sizeof(int));
if(!L->elem) exit(overflow);
L->length=0;
L->listsize=list_size;
return ok; }
int listinsert( sqlist *L, int i, int e )
{ int *p,*q,*newbase;
if(i<1||i>L->length+1) return error;
if(L->length>=L->listsize)
{newbase=(int *)realloc(L->elem,(L->listsize+increment)*sizeof(int));
if(!newbase)exit(overflow);
L->listsize+=increment;}
q=&(L->elem[i-1]);
for(p=&(L->elem[L->length-1]);p>=q;--p) *(p+1)=*p; *q=e; ++L->length;
return ok;}
void printlist(sqlist *L)
{int i;
printf("\n*********************");
printf("\nThe element of the sqlist are:\n");
for(i=0;i<L->length;i++)
printf("%d ",L->elem[i]);
printf("\n*********************\n");
return;
}
main( )
{ int i,j,e;
sqlist L1;
initlist(&L1);
printf(“\n Please insert the length of the sqlist:\n");
scanf("%d",&L1.length);
printf(“\n Please insert the element of the sqlist:\n");
for(i=0; i<L1.length; i++)
scanf( “%d”, &L1.elem[ i ]);
printlist(&L1);
printf("\nPlease insert the value of j ane e :\n");
scanf("%d%d",&j,&e);
listinsert(&L1, j, e);
printlist(&L1);
}
二:线性表的链式存储方式:
线性表的链式存储就是用一组地址任意的存储单元存放线性表的数据元素
1、 单链表
以元素(数据元素的映象) + 指针(指示后继元素存储位置) =结点
以“结点的序列”表示线性表称作单链表
以线性表中第一个数据元素的存储地址作为线性表的地址,称作线性表的头指针
有时为了操作方便,在第一个结点之前虚加一个“头结点”,以指向头结点的指针为链表的头指针。
Typedef struct LNode {
ElemType data; // 数据域
struct Lnode *next; // 指针域
} LNode, *LinkList
LinkList L; // L 为单链表的头指针
算法1. Status GetElem_L(LinkList L, int i, ElemType &e) // L是带头结点的链表的头指针,以 e 返回第 i 个元素
{ p = L->next; j = 1; // p指向第一个结点,j为计数器
while (p && j<i) { p = p->next; ++j; }
// 顺指针向后查找,直到 p 指向第 i 个元素或 p 为空
if ( !p || j>i )
return ERROR; // 第 i 个元素不存在
e = p->data; // 取得第 i 个元素
return OK;
} // GetElem_L
算法时间复杂度为O(ListLength(L))
具体过程见下图
算法2. Status ListInsert (LinkList &L, int i, ElemType e)
{ // 在链表中第i 个结点之前插入新的元素 e
p = L; j = 0; // j为计数器
while (p && j<i-1) { p = p->next; ++j; }
// 顺指针向后查找,直到p指向第i-1个元素或p为空
if ( !p || j>i-1 ) return ERROR;
s = (LinkList) malloc ( sizeof (LNode)); // 生成新结点
s->data = e;
s->next = p->next; p->next = s; // 插入
return OK;
}
具体过程见下图
算法3 . Status ListDelete_L(LinkList L, int i, ElemType &e)
{// 删除以 L 为头指针(带头结点)的单链表中第 i 个结点
p = L; j = 0;
while (p->next && j < i-1) { p = p->next; ++j; } // 寻找第 i 个结点并令 p 指向其前趋
if (!(p->next) || j > i-1) return ERROR; // 删除位置不合理
q = p->next; p->next = q->next;
e = q->data; free(q); // 删除并释放结点
return OK;
} // ListDelete_L
具体过程见下图
算法4 置空表操作 ClearList(&L) 在链表中的实现:
void ClearList(&L) {// 将单链表重新置为一个空表
while (L->next) { p=L->next; L->next=p->next; free(p);}} // ClearList
注意:插入的算法p->next = L->next; L->next = p;
具体过程见下图
算法5 Status MergeList (LinkList &La, LinkList &Lb, LinkList &Lc)
{
pa = La ->next; pb = Lb ->next;
Lc=pc = La;
while (pa && pb) {
if (pa->data<= pb->data)
{pc->next =pa;pc=pa;pa= pa->next; }
else{pc->next=pb;pc=pb;pb= pb->next; } }
pc->next =pa?pb:pb;
Free(Lb);
}
2. 双向链表
typedef struct DuLNode {
ElemType data; // 数据域
struct DuLNode *prior; // 指向前驱的指针域
struct DuLNode *next; // 指向后继的指针域
} DuLNode, *DuLinkList;
3. 循环链表
最后一个结点的指针域的指针又指回第一个结点的链表
和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”。
4 双向循环链表
插入
s->prior = p->prior; p->prior->next = s;
s->next = p; p ->prior = s;
在ai-1与ai之间插入节点为指针s,其中p指针指向ai
注意:也可以这样理解,=之前的指针指向=之后的指针
删除
q= p->next;
e=q->data
p->next = q->next;
p->next->prior = p;
free(q);
在ai-1与ai+1之间删除节点为指针ai,其中p指针指向ai-1,其中q指针指向ai