370 likes | 501 Views
第 3 章 链式存储结构. 第 3 章 链式存储结构. 3.1 线性表的链式存储结构 3.1.1 单链表上的基本运算 3.1.2 循环链表 3.1.3 双向链表 3.2 线性表的顺序和链式存储结构的比较 3.3 应用举例及分析 习题. 3.1 线性表的链式存储结构. 线性表的链式存储结构特点: 用一组 地址任意 的存储单元存放线性表中的数据元素。. 结点: 数据域 ( 数据元素的信息 ) + 指针域 ( 指示直接后继存储位置 ) = 结点 ( 表示数据元素或数据元素的映象 ). 链表: 以“结点的序列”表示线性表称作 链表。.
E N D
第3章 链式存储结构 3.1线性表的链式存储结构 3.1.1 单链表上的基本运算 3.1.2 循环链表 3.1.3 双向链表 3.2线性表的顺序和链式存储结构的比较 3.3应用举例及分析 习题
3.1线性表的链式存储结构 线性表的链式存储结构特点:用一组地址任意的存储单元存放线性表中的数据元素。 结点: 数据域(数据元素的信息)+指针域(指示直接后继存储位置)=结点(表示数据元素或数据元素的映象) 链表:以“结点的序列”表示线性表称作链表。
3.1线性表的链式存储结构 (a) 线性链表的图示; (b) 线性链表的一般表示 线性链表示意图
a1 a2 … ... an ^ 线性表为空表时, 头结点的指针域为空 头指针 头指针 头指针 头结点 以线性表中第一个数据元素a1的存储地址作为线性表的地址,称作线性表的头指针。 有时为了操作方便,在第一个结点之前虚加一个“头结点”,以指向头结点的指针为链表的头指针。
单链表的数据结构: type struct node {char data; struct node *next }LINKLIST; 结点的申请、释放: 若LINKLIST head,*new;则可用: head=malloc (sizeof (LINKLIST));申请结点 free (head);释放结点
线性表; (b) 单链表 • 线性表和链表的存储结构
3.1.1单链表上的基本运算 一、建立单链表 假设线性表中结点的数据类型是字符,逐个输入这些字符型的结点,并以 ‘$’为输入结束标记。动态地建立单链表的常用方法有如下两种: 1、头插法建表 该方法从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。算法如下:
main( ) { LINKLIST * head , * new ; Char ch ; while ( ( ch=getchar ()) !=’$’) new=malloc ( sizeof ( LINKLIST)) ; new->data=ch ; new->next= head ; head =new ; }
2、尾插法建表 头插法建立链表虽然算法简单,但生成的链表中结点的次序和输入的顺序相反。若希望二者次序一致,可采用尾插法建表。该方法是将新结点插入到当前链表的表尾上,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点。 算法如下:
main( ) {LINKLIST * head , * last , * new ; Char ch ; new=malloc ( sizeof (LINKLIST )) ; head=new ; last=new ; new->next=NULL ; while ( ch=getchar (1) !=’$’) {new=malloc ( sizeof ( LINKLIST)) ; new->data=ch ; last->next=new ; last=new ; new->next=NULL ;}
二、查找运算 1、按序号查找第i个结点 在有头指针的单链表中找第i个结点,算法 为:设head为头,p为当前结点,0为头结点序号,i为计数器,则可使p依次下 移找结点,并使i同时递增记录结点号,找到返回结点地址,找不到返回NULL。
算法如下: LINKLIST *get ( int i , LINKLIST *head ) {int j=0 ; LINKLIST *p=head ; while (cj<i) &&(p->next !=NULL ) {p=p->next ; i++ ; } if (i= =j ) return p ; else return NULL ; }
2、按值查找 按值查找是在链表中,查找是否有结点值等于给定值x的结点,若有,则返回首次找到的其值为x的结点的存储位置;否则返回NULL。查找过程从开始结点出发,顺着链表逐个将结点的值和给定值x作比较。 其查找流程及算法如下:
按值查找算法: LINKLIST * locate (Char x , LINKLIST * head ) {LINKLIST *p ;; p=head->next ; while ( p->data= = x ) return p ; else p=p->next ; } return NULL ; }
三、插入运算 插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。因此,首先找到ai-1的存储位置p,然后生成一个数据域为x的新结点*p,并令结点*p的指针域指向新结点,新结点的指针域指向结点ai。从而实现三个结点ai-1,x和ai之间的逻辑关系的变化。 new=malloc ( ); new.data=x ; new.next=p->next ; p.next=new;
x 在已知结点之后插入结点示意图
四、删除运算 删除运算是将表的第i个结点删去。因为在单链表中结点ai的存储地址是在其直接前趋结点ai-1的指针域next中,所以首先找到ai-1的存储位置p,然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间,将其归还给“存储池”。
1、删除已知结点的后继结点 删除*p结点的后继结点示意图 p
2、在单链表上删除第i个元素 在单链表上删除第i个元素的算法: int delete node ( int ; LINKLIST * head ) { LINKLIST * p , *q ; int j , r ; r=1 j=0 p=head ; while ( (j<i) && (p->next !=NULL ) ) {p=p->next ; j++ ; } if ( p->next =NULL || j>i-1 ) /*i<1 , 比表的头位置还小*/ r=0 ; else { p=p->next ; p->next=q->next ; free (q) ; } return r ;
an head head a1 3.1.2.循环链表 循环链表是一种首尾相接的链表。将单链表的终点的指针改为指向单链表的第一个结点,就成为单链循环链表。 带头结点的非空循环链表 带头结点的空循环链表
an a1 rear 特点:从表中任一结点出发均可找到表中其他所有结点。 因实际需要,经常采用尾指针表示循环链表,这样a1=rear->next->next,an=rear,显然,查找a1和an都很方便。 rear->next->next 用尾指针表示的非空循环单链表
3.1.3 双向链表 概念:在链表的结点中设定两个结点指针域,使链表中有两条不同方向的链,称为双向链表。 数据结构: typedef struct dnode {datatype data ; struct dnode * prior ,* next; } DLINKLIST ; 双向链表中结点的结构:
与单链表类似,双向链表也由头指针唯一确定,将头结点与尾结点链起来构成循环链表称双向循环链表。与单链表类似,双向链表也由头指针唯一确定,将头结点与尾结点链起来构成循环链表称双向循环链表。 双向链表特点:双向链表是一种对称结构,克服了单链表上指针单向性的缺点,既有前向链,又有后向链,这就使得数据元素的插入、删除操作都很方便。 建双向链表后,有 p= ( p->prior ) ->next= ( p->next ) ->prior
x 1、双向链表中插入结点 与单链表的插入和删除操作不同的是,在双链表中插入和删除必须同时修改两个方向上的指针。 ① ③ ④ ② 插入结点示意图
算法如下: t=malloc ( ) ; t->data=x ; t->prior = p->prior ; t->next = p ; ( p->prior ) ->next= t; p->prior = t;
P 2、双链表中删除p指针指向的结点 ① ② 删除双链表中已知结点*P
双链表中删除p指针指向的结点的算法: p->prior->next= p->next ; p->next->prior = p->prior ; free ( p );
3.2线性表的顺序和链式存储结构的比较 1、空间性能的比较 顺序表的存储空间是静态分布的,链表的存储空间是动态分布的。与其它链式存储一样,没有溢出现象。但要牺牲一部分附加空间。 2、时间性能比较 顺序表是随机存储结构,其时间复杂度为O(1),若链表中的操作主要查找、读取时,采用顺序表结构为宜,而操作为删除、插入元素占多时,采用链表比较合适。它不存在搬家问题。
3.3应用举例及分析 例3.1 写出一个计算链表中结点个数的算法,并依次打印出链表中元素的值。 算法如下: Int count (LINKLIST *head) {int i=0; LINKLIST *p; p=head->next; while(p!=NULL) {i++; printf(“%c”,p->data); p=p->next;} return i; }
例3.2 写一算法,将值为x的结点插在链表中第一个值为a的结点之后,如果值为a的结点不存在,则插入在表尾。 其算法如下: Void ifinsert (DATATYPE2 x, DATATYPE2 a,LINKLIST *head) {LINKLIST *p_new,*p; int flag=1; p_new=malloc(sizeof(LINKLIST)); p_new->data=x; p=head; while(p->data!=NULL && flag) {if (p->data==a) {p_new->next=p->next; p->next=p_new; flag=0;} else p=p->next;} if(flag) {p->next=p_new; p_new->next=NULL;} }
例3.3在一个非递减有序顺序表中,插入一个值为x的元素,使插入后的顺序表仍为非递减有序表,用带头结点的单链表结构编写算法。其算法为:例3.3在一个非递减有序顺序表中,插入一个值为x的元素,使插入后的顺序表仍为非递减有序表,用带头结点的单链表结构编写算法。其算法为: insert_order(DATATYPE2 x, LINKLIST * head) { LINKLIST * pr, * pn; pr = head; pn = head->next; While(pn ! = NULL && pn-> data < x) {pr = pn; pn = pn->next;} insertafter(x,pr); }
例3.4将一个用单链表存储的线性表T=(a1,a2,…,am-1,am)置换成(am,am-1,…, a2, a1,),实现的算法中辅助变量只能用指针(单链表不带头结点)。其算法如下: LINKLIST * inverlink(LINKLIST * head) { LINKLIST * p, * q, * r; q = NULL;p = head; While(p ! = NULL) {r = q; q = p; p = p->next; q->next = r;} return q; }
L a b c d 例3.5 写出下列双向链表中逻辑交换结点a和b的算法,不设辅助结点,只设辅助指针。 p = L->next; L->next = p->next; (L->prior)->next = p; p->prior = L->prior; (p->next)->prior = L; L->prior = p; p->next = L; L = p;
a b c d L 例3.6 写出下列双向链表中逻辑交换结点a和b的算法,不设辅助结点,只设辅助指针 p = L->next;L->next = p->next; (L->prior)->next = p;p->prior = L->prior; (p->next)->prior = L; L->prior = p;p->next = L; L = p;
习题 3.1 分析单链表、循环链表和双向链表的相同点和不同点,及各自特点。 3.2 设有两个线性表A和B皆是单链表存储结构,同一个表中的元素各不相同,且递增有序。写一算法,构成一个新的线性表C,使C为A和B的交集,且C中元素也递增有序。 3.3 设有两个按元素值递增有序的单链表A和B,编一程序将A表和B表归并成一个新的递增有序的单链表C(值相同的元素均保留在C表中)。 3.4 设L为带头节点的单链表,按下面两种情况分别编写算法删除表中值相同的多余元素。 (1)顺序表,元素值递增有序; (2)顺序表,元素值无序。