1.06k likes | 1.21k Views
第 2 章 线性表和数组. 企业帐务管理系统 线性表的逻辑结构 线性表的顺序存储结构 线性表的链式存储结构 循环链表和双向链表 线性表的应用 -- 多项式相加问题 数组. 企业帐务管理系统功能块. 1 .入帐模块 accein() 知识点 : 函数定义、线性表的建立、插入 2 .出帐模块 acceout() 知识点 : 线性表的查找、删除 3 .企业总帐本模块 countsum() 知识点 : 字符串查找、输出 4 .部门分帐本 知识点 : 稀疏矩阵. 2.1 线性表的逻辑结构. 定义
E N D
第2章 线性表和数组 • 企业帐务管理系统 • 线性表的逻辑结构 • 线性表的顺序存储结构 • 线性表的链式存储结构 • 循环链表和双向链表 • 线性表的应用--多项式相加问题 • 数组
企业帐务管理系统功能块 • 1 .入帐模块accein() 知识点:函数定义、线性表的建立、插入 • 2 .出帐模块acceout() 知识点:线性表的查找、删除 • 3 .企业总帐本模块countsum() 知识点:字符串查找、输出 • 4 .部门分帐本 知识点:稀疏矩阵
2.1 线性表的逻辑结构 • 定义 n( 0)个数据元素组成的有限序列 记作: L=(a1 ,a2 ,…ai ,…an) ai 是数据元素 n 是表的长度(长度为零(n=0)的表称为空表) • 线性表的逻辑结构由元素之间的相邻关系体现: a1为开始结点; an为终端结点; ai-1为ai 的直接前趋结点 (predecessor)(2≤i≤n); ai+1为ai 的直接后继结点 (successor) (1≤i≤n-1)。
学号 姓名 性别 年龄 班级 ... 0205010 程晓珉 女 18 计02 ... 0205012 方正飞 男 19 计02 ... 0205013 刘津津 男 19 计02 ... ... ... ... ... ... ... • 线性表举例 • 英文字母表:alphabet=(A,B,C,D,…,X,Y,Z) • 学生登记表:
线性表的基本操作 • InitList(L); 建立一个空的线性表L; • GetElem(L,i);取线性表L中的第i个元素; • Length(L);求线性表L的长度; • Locate(L,x);确定元素x在线性表L中位置; • Insert(L,i,x);在线性表L中第i个元素之前 (或之后)插入一个新元素x; • Delete(L,i);删除线性表L中的第i个元素; 线性表是一种灵活的数据结构,可在任意位置上进行插入和删除,其表长也根据不同的操作增长或缩短
2.2 线性表的顺序存储结构 存储方式 用内存中一批地址连续的存储单元依次存储线性表中的数据元素 特点 随机存取 假设第一个元素存放的位置为b,每个元素占用的空间大小为L, 则元素a1的存放位置为: LOC( ai )=b十L*(i-1)
内存状态 a1 a2 . . . ai . . . an 数据元素在线性表中的位置 1 2 . . . i . . . n 空闲 存储地址 b b+L . . . b+(i-1)*L . . . b+(n-1)*L . . b+ (MAXSIZE-1)*L
基本操作的实现 • 线性表的顺序存储结构定义 • typedef struct • { • ElemType elem[MAXSIZE]; /*存储线性表中的元素*/ • int len; /*线性表的当前表长*/ • }SqList; • 为了讨论的方便,不使用数组中下标为“0”的单元
插入 • 插入操作是指在线性表的第i-1个数据元素和第i个数据元素之间插入一个新的数据元素x,即: 原表 : (a1 ,..., ai-1 ,ai ,..., an) 插入后的表:(a1 ,...,ai-1 ,x , ai ,..., an+1) • 操作实现: (1)从an 起依次向后移动一个位置; (2)在位置 i 上插入新元素x; (3)表长加1。 • 要求:插入位置在合理范围内(1≤i≤n+1) 表未满
len=6 len=7 x 88 17 92 78 插入图示 elem i=4 插入x=88 elem
算法 2.1 int Insert_Sq (SqList *L ,int i, ElemType x){ /* 在线性表的第i-1和第i元素之间插入一个新元素x*/ if (i<1 || i>L->len+1) return 0; /* 不合理的插入位置i */ if ( L->len== MAXSIZE-1) return -1; /* 表已满 */ for (j=L->len;j>=i;--j) L->elem[j+1]=L->elem[j]; /* 插入位置及之后的元素右移*/ L->elem[i]=x; /*插入x */ ++L->len; /*表长加1 */ return 1; } /* Insert_Sq */
删除 • 删除线性表的第i个数据元素. 即: 原表 : (a1...,ai-1,ai,…,an-1 ,an ) 删除后的表:(a1 ...,ai-1,ai+1,...,an-1 ) • 操作实现: (1)将ai+1,... an依次向前移动一个位置; (2)表长减1。 • 要求:删除位置在合理范围内(1≤i≤n) 表未空
len=7 len=6 删除图示 elem i=2 删除x=65 elem
算法 2.2 int Delete_Sq (SqList *L ,int i ){ /* 删除线性表中第个i元素 */ if (i<1 || i>L->len) return 0; /*不合理的删除位置i*/ if (L->len==0) return -1; /* 表已空*/ for (j=i;j<=L->len-1;j++) L->elem[j]=L->elem[j+1]; /*被删除元素之后的元素左移 */ --L->len; /*表长减1*/ return 1; }/* Delete_Sq */
插入和删除算法的时间复杂度分析 • 主要耗费在移动元素上,而移动元素的个数取决于插入或删除元素的位置 • 设 pi 是在第i个元素前插入一个元素的概率,则所需移动元素次数的平均次数为:(设pi=1/(n+1))
假设qi是删除第i个元素的概率,则删除一个元素时所需移动元素次数的平均次数为: (设qi=1/n) 插入和删除算法的时间复杂度均为O(n)。
#include <stdio.h> #include <conio.h> #define MAXSIZE 20 typedef int ElemType ; typedef struct{ ElemType elem[MAXSIZE]; int len; }SqList; void Creat_list(SqList *L,int n); int Insert_Sq (SqList *L ,int i, ElemType x); int Delete_Sq (SqList *L ,int i ); void Print_list(SqList L); main() { int len,i,x; SqList s; clrscr(); printf("\n------Creat list--------\n"); printf(" please enter length:"); scanf("%d", &len); creat_list(&s,len); printf("\n------Print list--------\n"); Print_list(s); printf("\n------insert--------\n"); printf(" please enter i,x:"); scanf("%d,%d", &i,&x); Insert_Sq (&s , i, x); printf("\n------Print list--------\n"); Print_list(s); printf("\n------Delet--------\n"); printf(" please enter i:"); scanf("%d", &i); Delete_Sq (&s , i ); printf("\n------Print list--------\n"); Print_list(s); getch(); } void Creat_list(SqList *L,int n) { int k; printf("\nplease enter datas:\n"); for(k=1;k<=n;k++) scanf("%d",&L->elem[k]); L->len=n; } void Print_list(SqList L) { int k; printf("list has %d datas:",L.len); for(k=1;k<=L.len;k++) printf("%5d",L.elem[k]); } int Insert_Sq (SqList *L ,int i, ElemType x) { int j; if (i<1 || i>L->len+1) return 0; if ( L->len== MAXSIZE-1) return -1; for (j=L->len;j>=i;--j) L->elem[j+1]=L->elem[j]; L->elem[i]=x; ++L->len; return 1; } /* Insert_Sq */ int Delete_Sq (SqList *L ,int i ) {int j; if (i<1 || i>L->len) return 0; if (L->len==0) return -1; for (j=i;j<=L->len-1;j++) L->elem[j]=L->elem[j+1]; --L->len; return 1; }/* Delete_Sq */ 顺序表算法应用举例 #include <stdio.h> #include <conio.h> #define MAXSIZE 20 typedef int ElemType ; typedef struct{ ElemType elem[MAXSIZE]; int len; }SqList; /* 函数声明*/ void Creat_list(SqList *L,int n); /* 建立顺序表算法声明*/ int Insert_Sq (SqList *L ,int i, ElemType x); int Delete_Sq (SqList *L ,int i ); void Print_list(SqList L); /* 输出顺序表算法声明*/
main( ) {/* 主函数*/ int len,i,x; SqList s; clrscr(); /* 清屏 */ printf("\n------Creat list--------\n"); printf(" please enter length:"); scanf("%d", &len); creat_list(&s,len); printf("\n------Print list--------\n"); Print_list(s); printf("\n------insert--------\n"); printf(" please enter i,x:"); scanf("%d,%d", &i,&x); Insert_Sq (&s , i, x); printf("\n------Print list--------\n"); Print_list(s); printf("\n------Delet--------\n"); printf(" please enter i:"); scanf("%d", &i); Delete_Sq (&s , i ); printf("\n------Print list--------\n"); Print_list(s); getch(); }
void Creat_list(SqList *L,int n) /* 建立顺序表算法 */ { int k; printf("\nplease enter datas:\n"); for(k=1;k<=n;k++) scanf("%d",&L->elem[k]); L->len=n; } /* Creat_list */ int Insert_Sq (SqList *L ,int i, ElemType x) { int j; if (i<1 || i>L->len+1) return 0; if ( L->len== MAXSIZE-1) return -1; for (j=L->len;j>=i;--j) L->elem[j+1]=L->elem[j]; L->elem[i]=x; ++L->len; return 1; } /* Insert_Sq */
int Delete_Sq (SqList *L ,int i ) { int j; if (i<1 || i>L->len) return 0; if (L->len==0) return -1; for (j=i;j<=L->len-1;j++) L->elem[j]=L->elem[j+1]; --L->len; return 1; }/* Delete_Sq */ void Print_list(SqList L) /*输出顺序表算法*/ { int k; printf("list has %d datas:",L.len); for(k=1;k<=L.len;k++) printf("%5d",L.elem[k]); } /*Print_list */ 运行
动态分配的顺序存储结构介绍 利用数组实现线性表的顺序存储,结构简单,算法容易理解。但是,由于存储分配只能预先进行,如果插入的数据量超出预先分配的存储空间,要临时扩大有很大困难;如果按最大的可能空间进行分配,势必降低了存储空间的利用率。为解决此问题,我们可以利用C语言动态分配内存的机制,实现线性表的顺序存储。
动态分配的顺序存储结构的描述 #define INIT_SIZE 100 /*线性表存储空间的初始分配量 */ #define INCREMENT 10 /*线性表存储空间的分配增量 */ typedef struct { ElemType *elem /* 存储空间基址*/ int len; /*当前长度*/ int cursize; /*当前分配的存储容量*/ }DySqList;
初始化线性表:为顺序表分配一个预定义的存储空间,并将线性表的当前长度定义为零。初始化线性表:为顺序表分配一个预定义的存储空间,并将线性表的当前长度定义为零。 算法 2.3 int Init_List(DySqList *L){ /*分配存储空间*/ L->elem= (ElemType*) malloc(INIT_SIZE * sizeof(ElemType)); if(!L->elem) exit(0); /*存储分配失败*/ L->len=0; /*空表长度为0*/ L->cursize= INIT_SIZE; /*初始存储容量*/ return 1; }/* Init_List */
插入 • 算法思路与算法 2.1类似 • 存储空间已满时,可以通过调用C语言的动态分配库函数realloc( )来增加存储空间,从而实现线性表的可扩充性。
/ *算法 2.4 在动态分配的顺序表的第i-1和第i元素之间插入一个新元素x*/ int Insert_DySq (DySqList *L ,int i, ElemType x){ if(i<1 || i>L->len+1) return 0; /*不合理的插入位置i*/ if(L->len >= L-> cursize){ /*当前存储空间已满,增加分配*/ new=(ElemType *) realloc(L->elem, (L->cursize+INCREMENT)*sizeof(ElemType)); if(!new) exit(0); /* 存储分配失败*/ L->elem=new; /*新基址*/ L->cursize+=INCREMENT; /*增加后的存储空间*/ } q=L->elem+(i-1); /*q为插入位置*/ for (p=L-elem+(L->len-1);p>=q;--p) *(p+1)= *p; /* 插入位置及之后的元素右移*/ *q=x; /*插入x */ ++L->len; /*表长加1 */ return 1; }/* Insert_DySq */
2.3 线性表的链式存储结构 • 线性表的顺序存储结构的不足之处 ●插入和删除操作时需移动大量信息,效率较低; ●不易扩充; ●内存空间不能充分利用。 • 采用链式存储结构能有效地克服顺序存储方式的不足 • 链式存储结构 用内存中一组任意的存储单元存储线性表的数据元素
结点 data next 单链表 每个元素由结点(Node)构成 data:数据域--存储数据元素信息 next:指针域--存储直接后继结点的地址 • 结点的存储结构定义 typedef struct node { ElemType data; /*数据域*/ struct node *next; /* 指针域*/ }LNode;
首元结点 表头指针 表头结点 a1 a2 an ∧ H … H 空表: ∧ 单链表--n个结点链接成一个链表。 其中每个结点中只包含一个指针域,结点可以不连续存储
链表的基本操作 在下面的讨论中,默认单链表均为带表头结点的结构,这样在实现操作中的一些边界条件更加容易处理,使算法实现更加规范、简化。 • 在建立链表或向链表中插入结点时,应调用C语言的动态分配库函数malloc( )向系统申请一个结点,系统按结点的类型分配存储空间。 • 设有说明语句:LNode *p;则调用函数malloc的形式为: P=(LNode *)malloc(sizeof(LNode)); • 此时p指向—个新的结点,结点的数据域用p->data来表示,指针域用p->next来表示 • 从链表中删除一个不需要的结点时,要把结点归还给系统,则通过调用库函数free(p)实现。
ai a1 ai an an a1 ∧ ∧ H … … p H … … p 查找单链表中第i个结点 在单链表中查找某结点,需要设置一个指针变量p从首元结点出发,逐步向后移动查找,查找到则返回该指针值,否则返回空指针。 初始时: 查找结束时:
算法 2.5 LNode *Search( LNode *H, int i){ /*H为带表头结点的单链表的头指针。当第i个结点存在时,返回该结点的指针,否则返回空指针 */ p=H->next; j=1; /*P指向第一个结点,j为计数器*/ while( p && j<i ){ /* 查找第i个结点 */ p=p->next; ++j; } if( !p || j>i) return (NULL); /*不存在第i个结点 */ return p; }/*Search*/
由算法可以看出,基本操作是比较j和i并移动p,while循环体中的语句频度与被查元素在表中的位置有关,若1≤i≤表长n,则频度为i-1,否则频度为n。因此算法的时间复杂度为0(n)。 同时,我们也看到整个链表的存取必须从头指针H开始进行,因此单链表是一种非随机存取的存储结。
插入 在单链表中插入新结点,首先应确定插入的位置,然后只要修改相应结点的指针,而无须移动表中的其他结点。 例如在指针P所指向的结点后插入一个元素x。首先为待插入元素x分配一个结点(假使由指针变量s指向),把x赋给s结点的数据域;然后修改p结点和s结点的指针域。操作实现如下所示
p p a b a b s x s x (a) 插入前 (b) 插入后 • 插入结点的基本操作 /* 生成新结点 */ s=(LNode *)malloc(sizeof(LNode)); s->data=x; /* 修改指针域,完成插入 */ s->next=p->next;p->next=s;
p a1 a1 x H … p … H q … (a) 插入前 q (b) 插入后 s • 在P指针所指向的结点前插入一个元素x 首先设指针q从表头结点开始向后进行查找,直到找到p的前趋结点为止;然后在q结点后(即q所指向的结点前)插入s 结点
/*算法 2.6 在带表头结点的单链表H中的P结点前插入一个元素x */ void Insert_Linkst(LNode *H, LNode *p, ElemType x) { q=H; while(q->next!=p) q=q->next; /*寻找p结点的前趋 */ s=(LNode *) malloc (sizeof(LNode)); s->data=x; s->next=p; q->next=s; /* 插入 */ } /* Insert_Linkst*/ 思考:为什么在算法中指针q的初值为H,而不是H->next?
在链表中值为x的结点前插人一个值为y的新结点。如果x值不存在,则把新结点插在表尾在链表中值为x的结点前插人一个值为y的新结点。如果x值不存在,则把新结点插在表尾 算法思想 设置一个指针指针p从第一个元素结点开始向后查找,由于是在值为x 的结点前插人,因此还需另设一个指针q 从头结点开始紧跟p (即指向p 的前趋结点)。当指针p 指向x结点,便在q 结点后插入;如果值为x 的结点不在链表中,此时指针q 正好指向尾结点,即可完成插入。
p y x (a) x结点在表中 q s x P==NULL q y ∧ s (b)x结点不在表中
/*算法 2.7 在带表头结点的单链表中值为x的结点前插入y, 如果x不存在, 则把y插在表尾 */ void Insertx_Linkst (LNode *H, ElemType x, ElemType y) { s=(LNode *) malloc (sizeof(LNode)); s->data=y; q=H; p=H->next; while ( p && p->data!=x ){ /*寻找值为x的结点*/ q=p; p=p->next; } s->next=p; q->next=s; /* 插入*/ } /* Insertx_Linkst */
删除 从单链表中删除一个结点的基本操作过程 • 寻找被删结点的前驱结点 • 修改相应结点的指针域 • 释放被删结点的存储空间
p a b c (a)删除前 q p (b)删除后 a b c • 删除指针P所指结点的后继结点的操作 { q=p->next; /* q指向p的后继 */ p->next=q->next; /* 修改p的指针域 */ free(q); /* 释放q结点 */ }
p q ai-1 ai ai+1 • 删除单链表中的第i (i>0)个元素。若删除成功返回值1,否则返回0。 算法思想:先设置一个指针 p 从第一个元素结点开始向后移动,另设一个指针q 从头结点开始紧跟p(即指向p的前趋结点)。当p 移动到第i 个结点时,即可进行删除操作。操作如图所示。
/*算法 2.8 删除带表头结点的单链表第i(i>0)个元素 */ int Delete_Linkst ( LNode *H, int i ) { q=H; p=H->next; /* q始终指向p的前驱*/ j=1; while ( p && j<i ){ /* 寻找第i个结点*/ q=p; p=p->next; ++j; } if ( !p ) return 0; /* i大于表长*/ q->next=p->next; /* 删除结点*/ free(p); /* 释放q结点 */ return 1; }/* Delete_Linkst */
删除链表中所有值为x的结点,并返回值为x的结点的个数删除链表中所有值为x的结点,并返回值为x的结点的个数 • 算法思想: • 设指针p 从第一个元素结点起逐一查找链表中值为x 的结点 • 设辅助指针q 始终指向它的前趋结点 • 每找到一个结点便进行删除,同时统计被删结点的个数
/*算法 2.9 删除带表头结点的单链表H中的所有值为x的结点 */ int Deletex_Linkst ( LNode *H , ElemType x) { q=H; count=0; while ( q->next ){ /* 遍历整个链表*/ p=q->next; if( p->data==x ){ q->next=p->next; free(p); ++count; } else q=p; } return count; }/* Deletex_Linkst */
从以上的插入和删除算法可见,在单链表中插入或删除一个元素时,都要从链表的头结点开始,先顺链向后寻找插入或删除的位置,然后进行插入或删除。所以若表长为n,则上述算法的时间复杂度均为O(n)。从以上的插入和删除算法可见,在单链表中插入或删除一个元素时,都要从链表的头结点开始,先顺链向后寻找插入或删除的位置,然后进行插入或删除。所以若表长为n,则上述算法的时间复杂度均为O(n)。
建立带表头结点的单链表的算法 建立链表的过程是一个动态生成的过程,即从“空表”的初始状态起,依次输入数据元素,每输入一个就生成一个新结点并插入到表尾。为了方便新结点的插入,算法中设置一个指针p,使其始终指向当前链表的表尾结点。
/* 顺序输入n个元素的值,建立带表头结点的单链表 */ LNode * Creat_Linkst ( int n ) { head=( LNode *) malloc(sizeof(LNode )); /* head为表头指针 */ head->next=NULL; /* 先建立一个带表头结点的空表*/ p=head; printf(“please enter data:\n"); for (i=1; i<=n; ++i ) { s=( LNode *) malloc(sizeof(LNode )); /* 生成新结点*/ scanf (&s->data); /*输入结点值*/ s->next=NULL; p->next=s; p=s; /* 新结点插入在表尾*/ } return (head); }/* Creat_Linkst ()*/
a1 a2 an … H 空表: H 2.4 循环链表和双向链表 • 循环链表 将单链表的最后一个结点的指针域指向表头结点
特点:从表中任一结点出发均可找到表中的其它结点特点:从表中任一结点出发均可找到表中的其它结点 • 基本操作类似于普通链表,差别在于算法中循环条件不再是p或 p->next 是否为空,而是它们是否等于头指针 • 可只设尾指针,那么无论是访问第一个结点还是访问最后一个结点都很方便