首页 数据结构线性表

数据结构线性表

举报
开通vip

数据结构线性表数据结构线性表 第1讲 线性表 本章主要掌握如下内容: 线性表的定义和基本操作,线性表的实现,线性表的顺序存储结构及链式存储结构,线性表的应用。 知识点分析 (一)线性表的定义和基本操作 1(线性表基本概念 1)定义:是由相同类型的结点组成的有限序列。如:由n个结点组成的线性表 ( a, a, …, a) 12n a是最前结点,a是最后结点。结点也称为数据元素或者记录。 1n 2)线性表的长度:线性表中结点的个数称为其长度。长度为0的线性表称为空表。 3)结点之间的关系:设线性表记为(a,a,„a...

数据结构线性表
数据结构线性 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf 第1讲 线性表 本章主要掌握如下 内容 财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容 : 线性表的定义和基本操作,线性表的实现,线性表的顺序存储结构及链式存储结构,线性表的应用。 知识点 高中化学知识点免费下载体育概论知识点下载名人传知识点免费下载线性代数知识点汇总下载高中化学知识点免费下载 分析 (一)线性表的定义和基本操作 1(线性表基本概念 1)定义:是由相同类型的结点组成的有限序列。如:由n个结点组成的线性表 ( a, a, …, a) 12n a是最前结点,a是最后结点。结点也称为数据元素或者记录。 1n 2)线性表的长度:线性表中结点的个数称为其长度。长度为0的线性表称为空表。 3)结点之间的关系:设线性表记为(a,a,„a , a, a ,„a),称a是a的直接前驱结点(简称前驱),12i-1ii+1ni-1i((((a是a的直接后继结点(简称后继)。 i+1i(((( 4)线性表的性质: ? 线性表结点间的相对位置是固定的,结点间的关系由结点在表中的位置确定。 (( ? 如果两个线性表有相同的数据结点,但它们的结点顺序不一致,该两个线性表也是不相等的。 注意:线性表中结点的类型可以是任何数据(包括简单类型和复杂类型),即结点可以有多个成分,其中能唯一标识表元的成分称为关键字(key),或简称键。以后的讨论都只考虑键,而忽略其它成分,这样有利于把握主要问题,便于理解。 『经典例题解析』 线性表的特点是每个元素都有一个前驱和一个后继。( ) 【答案】错误。 【解析】线性表的第一个数据元素没有前驱,最后一个元素没有后继。其余的所有元素都有一个前 驱和后继。 2(线性表的抽象数据类型 线性表是一个相当灵活的数据结构,其长度可以根据需要增加或减少。从操作上讲,用户不仅可以对线性表的数据元素进行访问操作,还可以进行插入、删除、定位等操作。 1)线性表的基本操作 假设线性表L有 数据对象 D,{a | a?ElemSet,i=1,2,3,„,n,n>=0}, ii 数据元素之间的关系R,{|a,a?D,i=1,2,„,n}, i-1ii-1i 则线性表L的基本操作如下所示: , InitList(&L):其作用是构造一个长度为0的线性表(空线性表); , DestoryList(&L):其作用是销毁当前的线性表L; , ClearList(&L):清空线性表L,使之成为空表; , ListLength(L):返回线性表L的长度,即线性表中数据元素的个数; , ListEmpty(L) :判断线性表L是否为空表,是则返回True,否则返回False; , GetElem(L,i,&e):将线性表L中第i个数据元素的值返回到变量e中; , LocateELem(L,e,compare( )) :判断线性表L中是否存在与e满足compare()条件的数据元 素,有则返回第一个数据元素; , PriorElem(L,cur_e,&pri_e):返回线性表L中数据元素cur_e的前驱结点; , NextElem(L,cur_e,&next_e):返回线性表L中数据元素cur_e的后继结点; , ListInsert(&L,i,e):向线性表L的第i个位置之前插入一个数据元素,其值为e; , ListDelete(&L,i,&e):删除线性表L的第i个数据元素,并将该数据元素的值返回到e中; , ListTraverse(L,visit()):遍历线性表中的每个数据元素。 2)线性表的操作举例 ? 用两个线性表La,Lb分别表示两个集合A、B,现要求两个集合的合集,使得A=AUB。操作如下: 依次取出Lb中的元素,然后到La中去找,如果找不到,则将该元素加入La中,同时修改La的长度,如 果Lb中的元素同La中的元素相同,那么按照集合的概念,不再加入到La中。算法描述为: Void union(List &La , List Lb) { La_len = length(La) ; Lb_len=length(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和Lb中的数据元素按照非递减有序排列,现在要求La和Lb归并为一个新的有序线 性表Lc,使得Lc仍然是非递减有序排列。思想如下: 先设Lc为空表,从La、Lb的开头开始,比较La、Lb当前两个元素的大小,将较小者插入到Lc中。 为了比较方便,我们辅设两个指针i和j,让它们分别指向La和Lb即将参与比较的元素。 将较小元素插入Lc后,该较小元素所在的线性表上辅设的指针向后移动一个位置(,1),另一个指 针不变,继续参与下一轮比较,这样一直比到某一个线性表结束(i>La_length || j>Lb_length)。 最后再将还没有比较完的线性表中剩余的元素全部插入Lc中即可。 算法如下: void MergeList(List La , List Lb , List &Lc) { InitList(Lc) ; i=j=1 ; //两个指针初始化,i指向La的第一个元素,j指向Lb的第一个元素 k,0 ;//用于存储Lc当前元素个数,初始为0 La_Length = length(La) ; Lb_Length= length(Lb); while (i<=La_Length && j<= Lb_Length) { GetElem(La,i,ai) ; GetElem(Lb,j,bj); if (ai<=bj) { ListInsert(Lc,++k ,ai) ; i++ ; } else { ListInsert(Lc,++k,bj) ; j++; } } //while //将La或Lb中剩余所有元素全部插入Lc中,以下两句只可能执行一句。 while (i<=La_len) { GetElem(La,i++ ,ai) ; ListInsert(Lc,++k,ai) }; While (j<= Lb_len) { GetElem(Lb,j++ , bj)} ; ListInsert(Lc,++k ,bj) ); } 『经典例题解析』 假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。 【解析】因为两链表已按元素值递增次序排列,将其合并时,均从第一个结点起进行比较,将小的链入链表中,同时后移链表工作指针。该问题要求结果链表按元素值递减次序排列。故在合并的同时,将链表结点逆置。 LinkedList Union(LinkedList la,lb) ?la,lb分别是带头结点的两个单链表的头指针,链表中的元素值按递增序排列,本算法将两链表合并成 一个按元素值递减次序排列的单链表。 { pa=la->next; pb=lb->next;?pa,pb分别是链表la和lb的工作指针 la->next=null; ?la作结果链表的头指针,先将结果链表初始化为空。 while(pa!=null && pb!=null) ?当两链表均不为空时作 { if(pa->data<=pb->data) { r=pa->next; ?将pa 的后继结点暂存于r。 pa->next=la->next; ?将pa结点链于结果表中,同时逆置。 la->next=pa; pa=r; ?恢复pa为当前待比较结点。 } else { r=pb->next;? 将pb 的后继结点暂存于r。 pb->next=la->next; ?将pb结点链于结果表中,同时逆置。 la->next=pb; pb=r; ?恢复pb为当前待比较结点。 } } while(pa!=null) ?将la或lb表的剩余部分链入结果表,并逆置。 {r=pa->next; pa->next=la->next; la->next=pa; pa=r; } while(pb!=null) {r=pb->next; pb->next=la->next; la->next=pb; pb=r; } }?算法Union结束。 ,算法讨论,上面两链表均不为空的表达式也可简写为while(pa&&pb),两递增有序表合并成递减有序表时,上述算法是边合并边逆置。也可先合并完,再作链表逆置。后者不如前者优化。算法中最后两个while语句,不可能执行两个,只能二者取一,即哪个表尚未到尾,就将其逆置到结果表中,即将剩余结点依次前插到结果表的头结点后面。 (二)线性表的实现 1(顺序存储结构 线性表有两种存储方式:顺序存储和链式存储。顺序存储利用大数组或分配了连续内存空间的指针实现,链式存储利用链表实现。 1) 存储方法:利用一个足够大的数组,从第一个元素开始将线性表的结点依次存储在数组中。我们知道,数组是顺序存储的,利用数组的目的是用数组的顺序存储来体现线性表中结点的先后次序。由此得到的线性表称为顺序表,具有“随机存取”的特点。 (((( 2) 地址表示及计算:线性表的顺序存储指的是用一组地址连续的存储单元依次存储线性表的数据元素。设每个元素占用L个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置,则第i+1个元素的存储位置LOC(a)和第i个元素的存储位置LOC(a)有如下关系: i+1i LOC(a) , LOC(a) ,L i+1i 假设LOC(a)是线性表第一个数据元素a的存储位置(线性表的起始位置),则数据元素a的地址计11i算公式为: LOC(a) , LOC(a) , (i-1)×L i1 假设m>n,则有: LOC(a) , LOC(a) , (m-n)×L mn 3) 线性表的顺序存储结构定义: ,define List_INIT_SIZE 100 //初始分配量 # define LISTINCREMENT 10 //分配增量 typedef struct { ElemType *elem ; //带有连续地址块的指针变量,相当于一维数组(向量) ; //x线性表的当前长度,即当前数据元素的个数,初始值为0 int length int ListSize ; //线性表当前分配的存储容量(以sizeof(ElemType)为单位) ,SqList ; 线性表初始化时,利用下面的语句为指针成员elem分配连续地址空间: L.elem , (ElemType *)malloc(LIST_INIT_SIZE *sizeof(ElemType)) ; (((((( 4) 顺序表的各种操作 ? 初始化线性表 Status Init_Sq(SqList &L) { //构造一个空的线性表 L.elem = (ElemType * ) malloc( List_INIT_Size * sizeof(ElemType) ) ; if (!L.elem) exit(OVERFLOW) ; //存储分配失败 L.length = 0 ; L.ListSize = List_INIT_Size ; return OK ; } 重要说明: , elem为指针变量。在堆上分配了内存空间的指针变量相当于一个数组,而elem则变为指 向数组基地址的指针变量,可用elem[i]来表示数组的第i个元素。(elem可看成是数组的 名字) , !L.elem也可写成L.elem == NULL (((((((((((( , 之所以用给指针变量分配内存空间的方法定义数组,是因为线性表在使用时很可能是动态 的。如果用数组定义的话,其长度是固定的,不利于动态使用,缺少灵活性。 ? 在第i个元素之前插入一个新元素 需要将第i个元素到第n个元素均向后移动一个单位,插入的新元素成为第i个元素,原来的第i个元素成为第i+1个元素,原来的第n个元素成为第n,1个元素,线性表的长度加1。 Status listInsert_Sq(Sqlist &L,int i , ElemType e) { // i 的合法取值范围是:1<=i<=ListLength(L) + 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+1) = *p ; //插入位置以及以后的元素右移 *q = e ; //插入e ++L.length ; //表的长度增1 return OK ; } 重要说明: , 插入操作的主要工作都放在移动元素上。假设线性表中含有n个数据元素,在进行插入操 作时,若假定在n+1个位置上插入元素的可能性均等,则平均移动元素的个数为: 1nn,1E(ni1) ,,,,,isn12,i,1, 除非插入在线性表的最后,否则都要移动元素。 ? 删除第i个元素,并返回其值 Status ListDelete_Sq(SqList &L , int i , ElemType &e) { if ((i<1) || (i>L.length)) return ERROR ; P= &(L.elem[i-1]) ; e = * p ; q = L.elem + L.length – 1 ; //表尾元素位置 for (++p ; p<= q ; ++p) *(p-1) = *p ; //被删除元素之后的所有元素左移 --L.length; return OK ; } 重要说明: , 在进行删除操作时,若假定删除每个元素的可能性均等,则平均移动元素的个数为: 1n1n,E(ni) ,,,,dln2,i15) 顺序存储方式的特点: 优点:能直接访问线性表的任一结点,访问时间几乎不受结点存储位置的影响,这就是所谓的“随 机存取”机制。 缺点:? 数组长度固定,动态性太差; ? 执行结点插入、删除操作将移动大量数据。 6) 重要结论: , 一般情况下,在顺序表的第i(1<=i<=n)个元素之前插入一个元素,需要将第n至i的元素 (共n-i+1个元素)向后移动一个位置。 ((((( , 一般情况下,删除顺序表的第i(1<=i<=n)个元素时需要将第i+1到第n个元素(共n-i个((( 元素)依次向前移动一个位置。 , 在顺序表中插入或者删除一个数据元素,平均约需移动一半元素,设表长为n,则插入算法 和删除算法的时间复杂度为O(n)。 (((( 7) 一个应用:顺序表的合并 有两个线性表La、Lb,其数据元素均是非递减排列的,要求合并La、Lb为一个新的线性表Lc,且Lc也是非递减排列的。 void MergeList_Sq(SqList La , SqList Lb , SqList &Lc) { pa = La.elem ; pb = Lb.elem ; //取得La , Lb的基地址 Lc.ListSize = La.Length + Lb.Length ; pc = Lc.elem = (ElemType *) malloc(Lc.ListSize*sizeof(ElemType)); if (!Lc.elem) exit(OVERFLOW) ; pa_Last = pa + La.length -1 ; pb_Last = pb + Lb.length - 1 ; while (pa <= pa_Last && pb<= pb_Last) 归并 { if (*pa<=*pb) *pc++ = *pa++ ; else *pc++=*pb++ ; } 插入La、Lb的剩余元素 while (pa <= pa_Last) *pc++ = *pa++ ; while (pb <= pb_Last) *pc++ = *pb++ ; } 重要说明: , 由于合并后数据元素要放到Lc中,所以算法的主要操作是“复制”。如果将Lb中的数据插 入到La中,则需要移动La的元素。 , La、Lb均为顺序表,否则无法进行合并操作。如果原来的线性表不是顺序表,需要用排序 算法先进行排序。有关排序算法,后文有专门介绍。 『经典例题解析』 1(下述哪一条是顺序存储结构的优点,( ) A(存储密度大 B(插入运算方便 C(删除运算方便 D(可方便地用于各种逻辑结构的存储表示 【答案】A。 【解析】顺序存储利用物理的邻接关系表示数据元素之间的逻辑关系,因此没有必要设置指针域, 所以其存储密度比链式存储大,但是插入运算和删除运算都需大量移动数据元素,并不方便;D选项并不 是顺序存储结构的优点。 2(下面关于线性表的叙述中,错误的是哪一个,( ) A(线性表采用顺序存储,必须占用一片连续的存储单元。 B(线性表采用顺序存储,便于进行插入和删除操作。 C(线性表采用链接存储,不必占用一片连续的存储单元。 D(线性表采用链接存储,便于插入和删除操作。 【答案】B。 【解析】线性表采用顺序存储,并不便于进行插入和删除操作。 3(若某线性表最常用的操作是存取任一指定序号的元素和在最后进行插入和删除运算,则利用( )存储方式最节省时间。 A(顺序表 B(双链表 C(带头结点的双循环链表 D(单循环链表 。 【答案】A 【解析】“存取任一指定序号”最好的方法是实现“随机存取”,则可采用顺序表。并且,因为插入和删除操作都是在最后进行的,因此无需大量移动数据元素,选项A是最合适的。 4.若长度为n的线性表采用顺序存储结构,在其第i个位置插入一个新元素的算法的时间复杂度为( )(1<=i<=n+1)。 2A. O(0) B. O(1) C. O(n) D. O(n) 【答案】C。 【解析】顺序存储的线性表在插入新元素时,涉及到大量数据元素的移动,其时间复杂度为O(n)。 5. 对于顺序存储的线性表,访问结点和增加、删除结点的时间复杂度为( )。 A(O(n) O(n) B. O(n) O(1) C. O(1) O(n) D. O(1) O(1) 【答案】C。 【解析】顺序存储可以实现“随机存取”,因此访问结点的时间复杂度为O(1),而插入、删除结点由于涉及到大量移动元素,故其时间复杂度为O(n)。 2(链式存储结构 2.1 链式存储结构之一:单链表 利用单链表(也称线性链表)来实现,从链表的第一个数据元素开始,依次将线性表的结点存入。需要注意的是,链表的每个数据元素除了要存储线性表的数据元素信息之外,还要有一个成分存储其后继结点的指针,单链表就是通过这个指针来表明数据元素之间的先后关系的。 单链表在保存时,一般在第一个结点之前辅设一个结点,称为头结点。头结点的数据域可以不存任何信息,也可以存储线性表的长度等附加信息,其指针域中存储指向第一个结点的指针(即第一个元素结点 >next 的存储位置)。故单链表的头指针指向头结点,如果头结点的指针域为空,则说明是空表(head-==NULL)。 a2 a3 ^ a1 头指针 头结点 图1-1 带头结点的单链表 1) 单链表结构 typedef struct LNode{ ElemType data ; struct LNode * next ; }LNode , *LinkList ; 2)单链表的各种操作 ? 取得第i个元素的值 Status GetElem_L(LinkList L , int i , ElemType &e) { //L为带头结点的单链表的头指针 p = L.next ; //p指向第一个结点 j = 1 ; while (p && jnext ; j++ ; } if (!p || j>i) return ERROR ; //不存在 e= p->data ; return OK ; } 说明:读取第i个元素须从头指针开始查找,因此单链表是一种“非随机存取”的数据结构。 ? 单链表的插入操作: 在单链表L的某结点(设该结点由指针p指向)之后插入一个新的数据元素。设该新数据元 素由s指向。操作如下:s->next = p->next ; p->next = s; (注意语句顺序) p a b s x ? 单链表的删除操作: 在单链表L中的某结点(该结点由指针p指向)之后的结点需要删除,则操作为: q = p->next ; p->next = q->next ; free(q) ; p a b c 如果不考虑释放被删除的结点,则下面的操作也是正确的: p->next = p->next->next ; 3) 链式存储的特点 优点:每个数据元素的实际存储位置可以任意,数据元素的插入、删除变得非常容易; 缺点:? 每个数据元素增加了后继指针成分,增加了存储空间,降低了存储密度; ? 不能随机访问线性表的任一结点。(同顺序存储恰恰相反) 4) 静态链表 有时也可以采用一维数组来描述线性链表,其类型说明如下所示。 #define MAXSIZE 100 tyepdef struct { ElemType data ; int cur ; } component , SLinkList[MAXSIZE] ; 可以看出,以上结构中没有出现“指针”类型的指针域,而是利用一个指示器cur来表示结点在数组 中的相对位置。可以将数组的第0分量看成头结点,其指针域(cur)指示链表的第一个结点。这种存储 结构仍需要分配较大的连续空间,但是在进行线性表的插入及删除操作时不需要移动元素,而仅需要修改 指针,因此仍然具有链式存储结构的主要优点。为了和指针描述的线性链表相区别,我们称这种用数组实 现的链表为静态链表。 『经典例题解析』 1.(1) 静态链表既有顺序存储的优点,又有动态链表的优点。所以,它存取表中第i个元素的时间与i无关。(2) 静态链表中能容纳的元素个数的最大数在表定义时就确定了,以后不能增加。 (3) 静态链表与动态链表在元素的插入、删除上类似,不需做元素的移动。以上错误的是( ) A((1),(2) B((1) C((1),(2),(3) D.(2) 【答案】B。 【解析】静态链表使用结构体数组来实现线性链表的功能。因为其用游标cur来指示下一个数据元素的存储位置,因此存取数据时静态链表同线性链表(单链表)是相似的。也就是说,静态链表在存取表中第i个元素的时间同i是相关的。 2(线性表(a,a,„,a)以链接方式存储时,访问第i位置元素的时间复杂性为( ) 12n A(O(i) B(O(1) C(O(n) D(O(i-1) 【答案】C。 【解析】略。 3(非空的循环单链表head的尾结点p?满足( )。 A(p?.link=head B(p?.link=NIL C(p=NIL D(p= head 【答案】A。 【解析】略。 2.2链式存储结构之二:循环链表 表中最后一个结点的指针指向头结点,整个链表形成一个环。其特点为:从表中任何一个结点出发,都可以找到表中其它结点。 H 空 p 图1-2 循环链表 表 设p指向最后一个结点(如上图),则循环结束的条件是:p->next == H,H为线性表的头指针。 ((((((((((( 循环链表在实现时,有时设置尾指针,而不设置头指针,如下图所示。 A rearl B rear2 图1-3 设置尾指针的循环链表 将B接到A的后面,语法为: q = rear2.next ; rear2.next = rear1.next ; rear1.next = q.next ; 链接后,得到: A rearl B q rear2 图1-4 连接两个链表 此时,B中原来的头结点就可以被释放了,如可用free(q) ; 『经典例题解析』 1(某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用( )存储方式最节省运算时间。 A(单链表 B(仅有头指针的单循环链表 C(双链表 D(仅有尾指针的单循环链表 【答案】D。 【解析】仅有尾指针的单循环链表,可以非常方便的找到尾结点,尾结点后面的第一个结点往往是头结点,头结点的下一个结点就是第线性表的第一个结点。对最后一个元素和第一个元素操作对带尾指针的单循环链表是非常方便的。 2.3链式存储结构之三:双向循环链表 单链表的数据结构中只提供一个指向后继的指针域,因此,当需要查找某结点的前驱时,只能从表头指针出发,效果较差。为解决此问题,提出了双向链表的思想。双向链表是为了方便查找结点的前驱而设计的,表中的结点不仅有一个指向其直接后继的指针域,还有一个指向其直接前驱的指针域,如下图所示。 1)双向循环链表的数据结构 Typedef struct DuLNode { ElemType data ; struct DulNode * prior ; //前驱指针 struct DulNode * next ; //后继指针 } DulNode , *DuLinkList ; prior data next A B C L L (a) (b) 图1-5 双向循环链表 设d为指向某结点的指针,则下式成立: d->next ->prior ==d->prior->next == d ((((((((((((((((((((((((((((((((( 2) 双向链表的插入、删除操作 同单链表相比,双向链表的插入、删除操作需同时修改两个指针,因此操作较为复杂。 ? 插入一个结点 p a b c s 步骤:s->prior = p->prior ; p->prior->next = s ; s->next = p ; p->prior = s ; ? 删除一个结点b 步骤:p->prior->next = p->next ; p->next->prior = p->prior ; free(p) ;
本文档为【数据结构线性表】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_014457
暂无简介~
格式:doc
大小:45KB
软件:Word
页数:20
分类:
上传时间:2017-10-08
浏览量:38