1 / 106

第 2 章 线性表和数组

第 2 章 线性表和数组. 企业帐务管理系统 线性表的逻辑结构 线性表的顺序存储结构 线性表的链式存储结构 循环链表和双向链表 线性表的应用 -- 多项式相加问题 数组. 企业帐务管理系统功能块. 1 .入帐模块 accein() 知识点 : 函数定义、线性表的建立、插入 2 .出帐模块 acceout() 知识点 : 线性表的查找、删除 3 .企业总帐本模块 countsum() 知识点 : 字符串查找、输出 4 .部门分帐本 知识点 : 稀疏矩阵. 2.1 线性表的逻辑结构. 定义

coby-bolton
Download Presentation

第 2 章 线性表和数组

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第2章 线性表和数组 • 企业帐务管理系统 • 线性表的逻辑结构 • 线性表的顺序存储结构 • 线性表的链式存储结构 • 循环链表和双向链表 • 线性表的应用--多项式相加问题 • 数组

  2. 企业帐务管理系统功能块 • 1 .入帐模块accein() 知识点:函数定义、线性表的建立、插入 • 2 .出帐模块acceout() 知识点:线性表的查找、删除 • 3 .企业总帐本模块countsum() 知识点:字符串查找、输出 • 4 .部门分帐本 知识点:稀疏矩阵

  3. 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)。

  4. 学号 姓名 性别 年龄 班级 ... 0205010 程晓珉 女 18 计02 ... 0205012 方正飞 男 19 计02 ... 0205013 刘津津 男 19 计02 ... ... ... ... ... ... ... • 线性表举例 • 英文字母表:alphabet=(A,B,C,D,…,X,Y,Z) • 学生登记表:

  5. 线性表的基本操作 • 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个元素; 线性表是一种灵活的数据结构,可在任意位置上进行插入和删除,其表长也根据不同的操作增长或缩短

  6. 2.2 线性表的顺序存储结构 存储方式 用内存中一批地址连续的存储单元依次存储线性表中的数据元素 特点 随机存取 假设第一个元素存放的位置为b,每个元素占用的空间大小为L, 则元素a1的存放位置为: LOC( ai )=b十L*(i-1)

  7. 内存状态 a1 a2 . . . ai . . . an 数据元素在线性表中的位置 1 2 . . . i . . . n 空闲 存储地址 b b+L . . . b+(i-1)*L . . . b+(n-1)*L . . b+ (MAXSIZE-1)*L

  8. 基本操作的实现 • 线性表的顺序存储结构定义 • typedef struct • { • ElemType elem[MAXSIZE]; /*存储线性表中的元素*/ • int len; /*线性表的当前表长*/ • }SqList; • 为了讨论的方便,不使用数组中下标为“0”的单元

  9. 插入 • 插入操作是指在线性表的第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) 表未满

  10. len=6 len=7 x 88 17 92 78 插入图示 elem i=4 插入x=88 elem

  11. 算法 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 */

  12. 删除 • 删除线性表的第i个数据元素. 即: 原表 : (a1...,ai-1,ai,…,an-1 ,an ) 删除后的表:(a1 ...,ai-1,ai+1,...,an-1 ) • 操作实现: (1)将ai+1,... an依次向前移动一个位置; (2)表长减1。 • 要求:删除位置在合理范围内(1≤i≤n) 表未空

  13. len=7 len=6 删除图示 elem i=2 删除x=65 elem

  14. 算法 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 */

  15. 插入和删除算法的时间复杂度分析 • 主要耗费在移动元素上,而移动元素的个数取决于插入或删除元素的位置 • 设 pi 是在第i个元素前插入一个元素的概率,则所需移动元素次数的平均次数为:(设pi=1/(n+1))

  16. 假设qi是删除第i个元素的概率,则删除一个元素时所需移动元素次数的平均次数为: (设qi=1/n) 插入和删除算法的时间复杂度均为O(n)。

  17. #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); /* 输出顺序表算法声明*/

  18. 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(); }

  19. 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 */

  20. 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 */ 运行

  21. 动态分配的顺序存储结构介绍 利用数组实现线性表的顺序存储,结构简单,算法容易理解。但是,由于存储分配只能预先进行,如果插入的数据量超出预先分配的存储空间,要临时扩大有很大困难;如果按最大的可能空间进行分配,势必降低了存储空间的利用率。为解决此问题,我们可以利用C语言动态分配内存的机制,实现线性表的顺序存储。

  22. 动态分配的顺序存储结构的描述 #define INIT_SIZE 100 /*线性表存储空间的初始分配量 */ #define INCREMENT 10 /*线性表存储空间的分配增量 */ typedef struct { ElemType *elem /* 存储空间基址*/ int len; /*当前长度*/ int cursize; /*当前分配的存储容量*/ }DySqList;

  23. 初始化线性表:为顺序表分配一个预定义的存储空间,并将线性表的当前长度定义为零。初始化线性表:为顺序表分配一个预定义的存储空间,并将线性表的当前长度定义为零。 算法 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 */

  24. 插入 • 算法思路与算法 2.1类似 • 存储空间已满时,可以通过调用C语言的动态分配库函数realloc( )来增加存储空间,从而实现线性表的可扩充性。

  25. / *算法 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 */

  26. 2.3 线性表的链式存储结构 • 线性表的顺序存储结构的不足之处 ●插入和删除操作时需移动大量信息,效率较低; ●不易扩充; ●内存空间不能充分利用。 • 采用链式存储结构能有效地克服顺序存储方式的不足 • 链式存储结构 用内存中一组任意的存储单元存储线性表的数据元素

  27. 结点 data next 单链表 每个元素由结点(Node)构成 data:数据域--存储数据元素信息 next:指针域--存储直接后继结点的地址 • 结点的存储结构定义 typedef struct node { ElemType data; /*数据域*/ struct node *next; /* 指针域*/ }LNode;

  28. 首元结点 表头指针 表头结点 a1 a2 an ∧ H … H 空表: ∧ 单链表--n个结点链接成一个链表。 其中每个结点中只包含一个指针域,结点可以不连续存储

  29. 链表的基本操作 在下面的讨论中,默认单链表均为带表头结点的结构,这样在实现操作中的一些边界条件更加容易处理,使算法实现更加规范、简化。 • 在建立链表或向链表中插入结点时,应调用C语言的动态分配库函数malloc( )向系统申请一个结点,系统按结点的类型分配存储空间。 • 设有说明语句:LNode *p;则调用函数malloc的形式为: P=(LNode *)malloc(sizeof(LNode)); • 此时p指向—个新的结点,结点的数据域用p->data来表示,指针域用p->next来表示 • 从链表中删除一个不需要的结点时,要把结点归还给系统,则通过调用库函数free(p)实现。

  30. ai a1 ai an an a1 ∧ ∧ H … … p H … … p 查找单链表中第i个结点 在单链表中查找某结点,需要设置一个指针变量p从首元结点出发,逐步向后移动查找,查找到则返回该指针值,否则返回空指针。 初始时: 查找结束时:

  31. 算法 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*/

  32. 由算法可以看出,基本操作是比较j和i并移动p,while循环体中的语句频度与被查元素在表中的位置有关,若1≤i≤表长n,则频度为i-1,否则频度为n。因此算法的时间复杂度为0(n)。 同时,我们也看到整个链表的存取必须从头指针H开始进行,因此单链表是一种非随机存取的存储结。

  33. 插入 在单链表中插入新结点,首先应确定插入的位置,然后只要修改相应结点的指针,而无须移动表中的其他结点。 例如在指针P所指向的结点后插入一个元素x。首先为待插入元素x分配一个结点(假使由指针变量s指向),把x赋给s结点的数据域;然后修改p结点和s结点的指针域。操作实现如下所示

  34. 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;

  35. p a1 a1 x H … p … H q … (a) 插入前 q (b) 插入后 s • 在P指针所指向的结点前插入一个元素x 首先设指针q从表头结点开始向后进行查找,直到找到p的前趋结点为止;然后在q结点后(即q所指向的结点前)插入s 结点

  36. /*算法 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?

  37. 在链表中值为x的结点前插人一个值为y的新结点。如果x值不存在,则把新结点插在表尾在链表中值为x的结点前插人一个值为y的新结点。如果x值不存在,则把新结点插在表尾 算法思想 设置一个指针指针p从第一个元素结点开始向后查找,由于是在值为x 的结点前插人,因此还需另设一个指针q 从头结点开始紧跟p (即指向p 的前趋结点)。当指针p 指向x结点,便在q 结点后插入;如果值为x 的结点不在链表中,此时指针q 正好指向尾结点,即可完成插入。

  38. p y x (a) x结点在表中 q s x P==NULL q y ∧ s (b)x结点不在表中

  39. /*算法 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 */

  40. 删除 从单链表中删除一个结点的基本操作过程 • 寻找被删结点的前驱结点 • 修改相应结点的指针域 • 释放被删结点的存储空间

  41. 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结点 */ }

  42. p q ai-1 ai ai+1 • 删除单链表中的第i (i>0)个元素。若删除成功返回值1,否则返回0。 算法思想:先设置一个指针 p 从第一个元素结点开始向后移动,另设一个指针q 从头结点开始紧跟p(即指向p的前趋结点)。当p 移动到第i 个结点时,即可进行删除操作。操作如图所示。

  43. /*算法 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 */

  44. 删除链表中所有值为x的结点,并返回值为x的结点的个数删除链表中所有值为x的结点,并返回值为x的结点的个数 • 算法思想: • 设指针p 从第一个元素结点起逐一查找链表中值为x 的结点 • 设辅助指针q 始终指向它的前趋结点 • 每找到一个结点便进行删除,同时统计被删结点的个数

  45. /*算法 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 */

  46. 从以上的插入和删除算法可见,在单链表中插入或删除一个元素时,都要从链表的头结点开始,先顺链向后寻找插入或删除的位置,然后进行插入或删除。所以若表长为n,则上述算法的时间复杂度均为O(n)。从以上的插入和删除算法可见,在单链表中插入或删除一个元素时,都要从链表的头结点开始,先顺链向后寻找插入或删除的位置,然后进行插入或删除。所以若表长为n,则上述算法的时间复杂度均为O(n)。

  47. 建立带表头结点的单链表的算法 建立链表的过程是一个动态生成的过程,即从“空表”的初始状态起,依次输入数据元素,每输入一个就生成一个新结点并插入到表尾。为了方便新结点的插入,算法中设置一个指针p,使其始终指向当前链表的表尾结点。

  48. /* 顺序输入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 ()*/

  49. a1 a2 an … H 空表: H 2.4 循环链表和双向链表 • 循环链表 将单链表的最后一个结点的指针域指向表头结点

  50. 特点:从表中任一结点出发均可找到表中的其它结点特点:从表中任一结点出发均可找到表中的其它结点 • 基本操作类似于普通链表,差别在于算法中循环条件不再是p或 p->next 是否为空,而是它们是否等于头指针 • 可只设尾指针,那么无论是访问第一个结点还是访问最后一个结点都很方便

More Related